Skip to content

Commit

Permalink
GPTEINFRA-7693 Update handlers
Browse files Browse the repository at this point in the history
* Add new endpoints and patch existing handlers.
* Update functional tests
* Update OpenAPI schema
  • Loading branch information
fridim committed Sep 27, 2023
1 parent 7b21a3c commit c842725
Show file tree
Hide file tree
Showing 9 changed files with 431 additions and 43 deletions.
175 changes: 174 additions & 1 deletion cmd/sandbox-api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ func (h *BaseHandler) CreatePlacementHandler(w http.ResponseWriter, r *http.Requ
switch request.Kind {
case "AwsSandbox", "AwsAccount", "aws_account":
// Create the placement in AWS
accounts, err := h.accountProvider.Request(placementRequest.ServiceUuid, request.Count, placementRequest.Annotations)
accounts, err := h.accountProvider.Request(
placementRequest.ServiceUuid,
placementRequest.Reservation,
request.Count,
placementRequest.Annotations,
)
if err != nil {
if err == models.ErrNoEnoughAccountsAvailable {
w.WriteHeader(http.StatusInsufficientStorage)
Expand Down Expand Up @@ -967,3 +972,171 @@ func (h *BaseHandler) DeleteReservationHandler(w http.ResponseWriter, r *http.Re
HTTPStatusCode: http.StatusAccepted,
})
}

// UpdateReservationHandler updates a reservation
func (h *BaseHandler) UpdateReservationHandler(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")

reservation, err := models.GetReservationByName(h.dbpool, name)

if err != nil {
if err == pgx.ErrNoRows {
w.WriteHeader(http.StatusNotFound)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusNotFound,
Message: "Reservation not found",
})
return
}

w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
Err: err,
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error getting reservation",
})
log.Logger.Error("UpdateReservationHandler", "error", err)
return
}

reservationReq := models.ReservationRequest{}

// Decode the request body
if err := render.Bind(r, &reservationReq); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Render(w, r, &v1.Error{
Err: err,
HTTPStatusCode: http.StatusBadRequest,
Message: "Error decoding request body",
})
log.Logger.Error("UpdateReservationHandler", "error", err)
return
}

// Validate the request
if message, err := reservationReq.Validate(h.accountProvider); err != nil {
w.WriteHeader(http.StatusBadRequest)
render.Render(w, r, &v1.Error{
Err: err,
HTTPStatusCode: http.StatusBadRequest,
Message: message,
})
log.Logger.Error("UpdateReservationHandler", "error", err)
return
}

// Update the status
if err := reservation.UpdateStatus(h.dbpool, "updating"); err != nil {
log.Logger.Error("Error updating reservation status", "error", err)
w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
Err: err,
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error updating reservation status",
})
return
}

// Async Update the reservation
go reservation.Update(h.dbpool, h.accountProvider, reservationReq)

w.WriteHeader(http.StatusAccepted)
render.Render(w, r, &v1.ReservationResponse{
Reservation: reservation,
Message: "Reservation update request created",
HTTPStatusCode: http.StatusAccepted,
})
}

// GetReservationHandler gets a reservation
func (h *BaseHandler) GetReservationHandler(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")

reservation, err := models.GetReservationByName(h.dbpool, name)

if err != nil {
if err == pgx.ErrNoRows {
w.WriteHeader(http.StatusNotFound)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusNotFound,
Message: "Reservation not found",
})
return
}

w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
Err: err,
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error getting reservation",
})
log.Logger.Error("GetReservationHandler", "error", err)
return
}

w.WriteHeader(http.StatusOK)
render.Render(w, r, &v1.ReservationResponse{
Reservation: reservation,
Message: "Reservation found",
HTTPStatusCode: http.StatusOK,
})
}

// GetReservationResourcesHandler gets the resources of a reservation
func (h *BaseHandler) GetReservationResourcesHandler(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")

reservation, err := models.GetReservationByName(h.dbpool, name)

if err != nil {
if err == pgx.ErrNoRows {
w.WriteHeader(http.StatusNotFound)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusNotFound,
Message: "Reservation not found",
})
return
}

w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
Err: err,
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error getting reservation",
})
log.Logger.Error("GetReservationHandler", "error", err)
return
}

accounts, err := h.accountProvider.FetchAllByReservation(reservation.Name)

if err != nil {
log.Logger.Error("GET accounts", "error", err)

w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error reading account",
})
return
}

if len(accounts) == 0 {
w.WriteHeader(http.StatusNotFound)
} else {
w.WriteHeader(http.StatusOK)
}

// Response with accounts
resources := []any{}
for _, account := range accounts {
resources = append(resources, account)
}

render.Render(w, r, &v1.ResourcesResponse{
Count: len(accounts),
Resources: resources,
Message: "Accounts found",
HTTPStatusCode: http.StatusOK,
})
}
5 changes: 5 additions & 0 deletions cmd/sandbox-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ func main() {
r.Put("/api/v1/placements/{uuid}/status", baseHandler.LifeCyclePlacementHandler("status"))
r.Get("/api/v1/placements/{uuid}/status", baseHandler.GetStatusPlacementHandler)
r.Get("/api/v1/requests/{id}/status", baseHandler.GetStatusRequestHandler)
r.Get("/api/v1/reservations/{name}", baseHandler.GetReservationHandler)
r.Get("/api/v1/reservations/{name}/resources", baseHandler.GetReservationResourcesHandler)
})

// ---------------------------------------------------------------------
Expand All @@ -217,7 +219,10 @@ func main() {
r.Post("/api/v1/admin/jwt", adminHandler.IssueLoginJWTHandler)
r.Get("/api/v1/admin/jwt", baseHandler.GetJWTHandler)
r.Put("/api/v1/admin/jwt/{id}/invalidate", baseHandler.InvalidateTokenHandler)

// Reservations
r.Post("/api/v1/reservations", baseHandler.CreateReservationHandler)
r.Put("/api/v1/reservations/{name}", baseHandler.UpdateReservationHandler)
r.Delete("/api/v1/reservations/{name}", baseHandler.DeleteReservationHandler)
})

Expand Down
71 changes: 71 additions & 0 deletions docs/api-reference/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,77 @@ paths:
message: Bad request
http_code: 400

'401':
description: unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
message: unauthorized
http_code: 401
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
put:
parameters:
- in: header
name: Authorization
description: Access JTW Token
required: true
schema:
type: string
example: Bearer <ACCESS_TOKEN>
- name: name
in: path
required: true
description: The name of the reservation
schema:
type: string
pattern: '^[\w\d_-]+$'
example: summit
summary: Reserve resources
description: |-
Update a reservation.
Scale up or down a reservation. Add or remove resources.
operationId: updateReservation
tags:
- admin
requestBody:
description: JSON object to specify the reservation.
content:
application/json:
schema:
$ref: "#/components/schemas/Reservation"
example:
name: summit
resources:
- kind: AwsSandbox
count: 200
responses:
'202':
description: Reservation update request created
content:
application/json:
schema:
type: object
example:
message: reservation update request created

'400':
description: Wrong request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
message: reservation already exists
http_code: 400

'401':
description: unauthorized
content:
Expand Down
18 changes: 15 additions & 3 deletions internal/api/v1/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type HealthCheckResult struct {

type PlacementRequest struct {
ServiceUuid string `json:"service_uuid"`
Reservation string `json:"reservation"`
Resources []ResourceRequest `json:"resources"`
Annotations map[string]string `json:"annotations"`
}
Expand Down Expand Up @@ -72,6 +73,17 @@ type PlacementStatusResponse struct {
Status []models.Status `json:"status,omitempty"`
}

type ResourcesResponse struct {
HTTPStatusCode int `json:"http_code,omitempty"` // http response status code
Message string `json:"message"`
Resources []any `json:"resources,omitempty"`
Count int `json:"count,omitempty"`
}

func (o *ResourcesResponse) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}

func (p *AccountStatusResponse) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}
Expand All @@ -93,9 +105,9 @@ type ResourceRequest struct {
}

type ReservationResponse struct {
HTTPStatusCode int `json:"http_code,omitempty"` // http response status code
Message string `json:"message"`
Reservation models.Reservation
HTTPStatusCode int `json:"http_code,omitempty"` // http response status code
Message string `json:"message"`
Reservation models.Reservation `json:"reservation"`
}

func (p *PlacementRequest) Bind(r *http.Request) error {
Expand Down
11 changes: 10 additions & 1 deletion internal/dynamodb/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ func (a *AwsAccountDynamoDBProvider) FetchAllSorted(by string) ([]models.AwsAcco
}

// Request reserve accounts for a service
func (a *AwsAccountDynamoDBProvider) Request(service_uuid string, count int, annotations map[string]string) ([]models.AwsAccountWithCreds, error) {
func (a *AwsAccountDynamoDBProvider) Request(service_uuid string, reservation string, count int, annotations map[string]string) ([]models.AwsAccountWithCreds, error) {
if count <= 0 {
return []models.AwsAccountWithCreds{}, errors.New("count must be > 0")
}
Expand All @@ -428,6 +428,10 @@ func (a *AwsAccountDynamoDBProvider) Request(service_uuid string, count int, ann
And(expression.Name("hosted_zone_id").AttributeExists()).
And(expression.Name("account_id").AttributeExists())

if reservation != "" {
filter = filter.And(expression.Name("reservation").Equal(expression.Value(reservation)))
}

// get 10 spare accounts in case of concurrency doublebooking
accounts, err := GetAccounts(a.Svc, filter, count+10)

Expand All @@ -443,6 +447,11 @@ func (a *AwsAccountDynamoDBProvider) Request(service_uuid string, count int, ann
And(expression.Name("aws_secret_access_key").AttributeExists()).
And(expression.Name("hosted_zone_id").AttributeExists()).
And(expression.Name("account_id").AttributeExists())

if reservation != "" {
filter = filter.And(expression.Name("reservation").Equal(expression.Value(reservation)))
}

accounts, err = GetAccounts(a.Svc, filter, count+10)

if err != nil {
Expand Down
13 changes: 12 additions & 1 deletion internal/models/aws_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package models
import (
"context"
"errors"
"net/http"

"github.com/jackc/pgx/v4/pgxpool"
"github.com/rhpds/sandbox/internal/log"
Expand Down Expand Up @@ -39,6 +40,16 @@ type AwsAccount struct {
ConanHostname string `json:"conan_hostname,omitempty"`
}

func (a *AwsAccount) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}

type AwsAccounts []AwsAccount

func (a *AwsAccounts) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}

type AwsAccountWithCreds struct {
AwsAccount

Expand All @@ -65,7 +76,7 @@ type AwsAccountProvider interface {
FetchAllByServiceUuidWithCreds(serviceUuid string) ([]AwsAccountWithCreds, error)
FetchAllActiveByServiceUuidWithCreds(serviceUuid string) ([]AwsAccountWithCreds, error)
FetchAllByReservation(reservation string) ([]AwsAccount, error)
Request(service_uuid string, count int, annotations map[string]string) ([]AwsAccountWithCreds, error)
Request(service_uuid string, reservation string, count int, annotations map[string]string) ([]AwsAccountWithCreds, error)
Reserve(reservation string, count int) ([]AwsAccount, error)
ScaleDownReservation(reservation string, count int) error
MarkForCleanup(name string) error
Expand Down
Loading

0 comments on commit c842725

Please sign in to comment.