Skip to content

Commit

Permalink
Add reservation middleware (#193)
Browse files Browse the repository at this point in the history
* Moved reservation code to the api package to make it easier to test and also there was no real point of separating it  
* Added middleware that checks for X-GO-IOS-RESERVE header for endpoints on which it is applied  
* Added a reservation admin UUID  
* Reworked reservation endpoints to be more in line with the other device endpoints  
* Added tests for the middleware and also cleaned up reservation tests code a bit
  • Loading branch information
shamanec authored Nov 9, 2022
1 parent 173b973 commit 0200143
Show file tree
Hide file tree
Showing 5 changed files with 445 additions and 253 deletions.
23 changes: 21 additions & 2 deletions restapi/api/middleware.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package api

import (
"github.com/danielpaulus/go-ios/ios"
"github.com/gin-gonic/gin"
"net/http"
"strings"
"sync"

"github.com/danielpaulus/go-ios/ios"
"github.com/gin-gonic/gin"
)

// DeviceMiddleware makes sure a udid was specified and that a device with that UDID
Expand Down Expand Up @@ -68,3 +69,21 @@ func StreamingHeaderMiddleware() gin.HandlerFunc {
c.Next()
}
}

func ReserveDevicesMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
udid := c.Param("udid")
reservationID := c.Request.Header.Get("X-GO-IOS-RESERVE")
if reservationID == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, GenericResponse{Error: "Device reservation token empty or missing. This operation requires you to reserve the device using the /reservations endpoint and then pass the reservation token with the X-GO-IOS-RESERVE header"})
return
}

err := checkDeviceReserved(udid, reservationID)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, GenericResponse{Error: err.Error()})
return
}
c.Next()
}
}
208 changes: 0 additions & 208 deletions restapi/api/reservation/reserve_endpoints_test.go

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package reservation
package api

import (
"errors"
"net/http"
"sync"
"time"
Expand All @@ -11,7 +12,8 @@ import (

var reservedDevicesMap = make(map[string]*reservedDevice)
var reserveMutex sync.Mutex
var ReservedDevicesTimeout time.Duration = 5
var reservedDevicesTimeout time.Duration = 5
var reserveAdminUUID = "go-admin"

type reservedDevice struct {
Message string `json:"message,omitempty"`
Expand All @@ -20,33 +22,15 @@ type reservedDevice struct {
LastUsedTimestamp int64 `json:"lastUsed,omitempty"`
}

func CleanReservationsCRON() {
defer reserveMutex.Unlock()

// Every minute loop through the map of reserved devices and check if a reserved device last used timestamp was more than 5 minutes(300000 ms) ago
// If any, remove them from the map
for range time.Tick(time.Second * 60) {
reserveMutex.Lock()
for udid, reservedDevice := range reservedDevicesMap {
currentTimestamp := time.Now().UnixMilli()
diff := currentTimestamp - reservedDevice.LastUsedTimestamp

if diff > (time.Minute * ReservedDevicesTimeout).Milliseconds() {
delete(reservedDevicesMap, udid)
}
}
reserveMutex.Unlock()
}
}

// Reserve device access
// List godoc
// @Summary Reserve a device
// @Description Reserve a device by provided UDID
// @Tags reserve
// @Tags reservations
// @Param udid path string true "device udid"
// @Produce json
// @Success 200 {object} reservedDevice
// @Router /reserve/:udid [post]
// @Router /{udid}/reservations [post]
func ReserveDevice(c *gin.Context) {
udid := c.Param("udid")
reservationID := uuid.New().String()
Expand All @@ -71,36 +55,37 @@ func ReserveDevice(c *gin.Context) {
// List godoc
// @Summary Release a device
// @Description Release a device by provided UDID
// @Tags reserve
// @Tags reservations
// @Param reservationID path string true "reservation ID generated when reserving device"
// @Produce json
// @Success 200 {object} reservedDevice
// @Failure 404 {object} reservedDevice
// @Router /reserve/:udid [delete]
// @Router /reservations/{reservationID} [delete]
func ReleaseDevice(c *gin.Context) {
udid := c.Param("udid")
reservationID := c.Param("reservationID")

reserveMutex.Lock()
defer reserveMutex.Unlock()

// Check if there is a reserved device for the respective UDID
device := reservedDevicesMap[udid]
if device == nil {
c.IndentedJSON(http.StatusNotFound, reservedDevice{Message: "Not reserved"})
return
for udid, device := range reservedDevicesMap {
if device.ReservationID == reservationID {
delete(reservedDevicesMap, udid)
c.IndentedJSON(http.StatusOK, reservedDevice{Message: "Successfully released"})
return
}
}

delete(reservedDevicesMap, udid)
c.IndentedJSON(http.StatusOK, reservedDevice{Message: "Successfully released"})
c.IndentedJSON(http.StatusNotFound, reservedDevice{Message: "Not reserved or wrong reservationID"})
}

// Get all reserved devices
// List godoc
// @Summary Get a list of reserved devices
// @Description Get a list of reserved devices with UDID, ReservationID and last used timestamp
// @Tags reserve
// @Tags reservations
// @Produce json
// @Success 200 {object} []reservedDevice
// @Router /reserved-devices [get]
// @Router /reservations [get]
func GetReservedDevices(c *gin.Context) {
reserveMutex.Lock()
defer reserveMutex.Unlock()
Expand All @@ -123,3 +108,38 @@ func GetReservedDevices(c *gin.Context) {

c.IndentedJSON(http.StatusOK, reserved_devices)
}

func cleanReservationsCRON() {
defer reserveMutex.Unlock()

// Every minute loop through the map of reserved devices and check if a reserved device last used timestamp was more than X minutes ago
// If any, remove them from the map
for range time.Tick(time.Second * 60) {
reserveMutex.Lock()
for udid, reservedDevice := range reservedDevicesMap {
currentTimestamp := time.Now().UnixMilli()
diff := currentTimestamp - reservedDevice.LastUsedTimestamp

if diff > (time.Minute * reservedDevicesTimeout).Milliseconds() {
delete(reservedDevicesMap, udid)
}
}
reserveMutex.Unlock()
}
}

func checkDeviceReserved(deviceUDID string, reservationID string) error {
reserveMutex.Lock()
defer reserveMutex.Unlock()

reservedDevice, exists := reservedDevicesMap[deviceUDID]

if exists {
if reservedDevice.ReservationID == reservationID || reserveAdminUUID == reservationID {
reservedDevice.LastUsedTimestamp = time.Now().UnixMilli()
return nil
}
return errors.New("Device is already reserved with another reservationID")
}
return errors.New("You need to reserve the device before using it")
}
Loading

0 comments on commit 0200143

Please sign in to comment.