diff --git a/api/pkg/api/handler/task.go b/api/pkg/api/handler/task.go new file mode 100644 index 00000000..d93b0b9d --- /dev/null +++ b/api/pkg/api/handler/task.go @@ -0,0 +1,198 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package handler + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/labstack/echo/v4" + temporalEnums "go.temporal.io/api/enums/v1" + tClient "go.temporal.io/sdk/client" + tp "go.temporal.io/sdk/temporal" + + "github.com/nvidia/bare-metal-manager-rest/api/internal/config" + "github.com/nvidia/bare-metal-manager-rest/api/pkg/api/handler/util/common" + "github.com/nvidia/bare-metal-manager-rest/api/pkg/api/model" + sc "github.com/nvidia/bare-metal-manager-rest/api/pkg/client/site" + auth "github.com/nvidia/bare-metal-manager-rest/auth/pkg/authorization" + cutil "github.com/nvidia/bare-metal-manager-rest/common/pkg/util" + cdb "github.com/nvidia/bare-metal-manager-rest/db/pkg/db" + cdbm "github.com/nvidia/bare-metal-manager-rest/db/pkg/db/model" + rlav1 "github.com/nvidia/bare-metal-manager-rest/workflow-schema/rla/protobuf/v1" + "github.com/nvidia/bare-metal-manager-rest/workflow/pkg/queue" +) + +// ~~~~~ Get Task Handler ~~~~~ // + +// GetTaskHandler is the API Handler for getting a Task by ID +type GetTaskHandler struct { + dbSession *cdb.Session + tc tClient.Client + scp *sc.ClientPool + cfg *config.Config + tracerSpan *cutil.TracerSpan +} + +// NewGetTaskHandler initializes and returns a new handler for getting a Task +func NewGetTaskHandler(dbSession *cdb.Session, tc tClient.Client, scp *sc.ClientPool, cfg *config.Config) GetTaskHandler { + return GetTaskHandler{ + dbSession: dbSession, + tc: tc, + scp: scp, + cfg: cfg, + tracerSpan: cutil.NewTracerSpan(), + } +} + +// Handle godoc +// @Summary Get a Task +// @Description Get a Task by UUID +// @Tags rack +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param org path string true "Name of NGC organization" +// @Param uuid path string true "UUID of the Task" +// @Param siteId query string true "ID of the Site" +// @Success 200 {object} model.APITask +// @Router /v2/org/{org}/carbide/rack/task/{uuid} [get] +func (gth GetTaskHandler) Handle(c echo.Context) error { + org, dbUser, ctx, logger, handlerSpan := common.SetupHandler("Task", "Get", c, gth.tracerSpan) + if handlerSpan != nil { + defer handlerSpan.End() + } + + var apiRequest model.APIGetTaskRequest + if err := common.ValidateKnownQueryParams(c.QueryParams(), apiRequest); err != nil { + return cutil.NewAPIErrorResponse(c, http.StatusBadRequest, err.Error(), nil) + } + if err := c.Bind(&apiRequest); err != nil { + return cutil.NewAPIErrorResponse(c, http.StatusBadRequest, "Failed to parse request data", nil) + } + if err := apiRequest.Validate(); err != nil { + return cutil.NewAPIErrorResponse(c, http.StatusBadRequest, err.Error(), nil) + } + + if dbUser == nil { + logger.Error().Msg("invalid User object found in request context") + return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve current user", nil) + } + + ok, err := auth.ValidateOrgMembership(dbUser, org) + if !ok { + if err != nil { + logger.Error().Err(err).Msg("error validating org membership for User in request") + } else { + logger.Warn().Msg("could not validate org membership for user, access denied") + } + return cutil.NewAPIErrorResponse(c, http.StatusForbidden, fmt.Sprintf("Failed to validate membership for org: %s", org), nil) + } + + ok = auth.ValidateUserRoles(dbUser, org, nil, auth.ProviderAdminRole) + if !ok { + logger.Warn().Msg("user does not have Provider Admin role, access denied") + return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "User does not have Provider Admin role with org", nil) + } + + infrastructureProvider, err := common.GetInfrastructureProviderForOrg(ctx, nil, gth.dbSession, org) + if err != nil { + logger.Warn().Err(err).Msg("error getting infrastructure provider for org") + return cutil.NewAPIErrorResponse(c, http.StatusBadRequest, "Failed to retrieve Infrastructure Provider for org", nil) + } + + taskUUID := c.Param("uuid") + + site, err := common.GetSiteFromIDString(ctx, nil, apiRequest.SiteID, gth.dbSession) + if err != nil { + if errors.Is(err, common.ErrInvalidID) { + return cutil.NewAPIErrorResponse(c, http.StatusBadRequest, "Failed to validate Site specified in request: invalid ID", nil) + } + if errors.Is(err, cdb.ErrDoesNotExist) { + return cutil.NewAPIErrorResponse(c, http.StatusBadRequest, "Site specified in request does not exist", nil) + } + logger.Error().Err(err).Msg("error retrieving Site from DB") + return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve Site specified in request due to DB error", nil) + } + + if site.InfrastructureProviderID != infrastructureProvider.ID { + return cutil.NewAPIErrorResponse(c, http.StatusForbidden, "Site specified in request doesn't belong to current org's Provider", nil) + } + + siteConfig := &cdbm.SiteConfig{} + if site.Config != nil { + siteConfig = site.Config + } + + if !siteConfig.RackLevelAdministration { + logger.Warn().Msg("site does not have Rack Level Administration enabled") + return cutil.NewAPIErrorResponse(c, http.StatusPreconditionFailed, "Site does not have Rack Level Administration enabled", nil) + } + + stc, err := gth.scp.GetClientByID(site.ID) + if err != nil { + logger.Error().Err(err).Msg("failed to retrieve Temporal client for Site") + return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to retrieve client for Site", nil) + } + + rlaRequest := &rlav1.GetTasksByIDsRequest{ + TaskIds: []*rlav1.UUID{{Id: taskUUID}}, + } + + workflowOptions := tClient.StartWorkflowOptions{ + ID: fmt.Sprintf("task-get-%s", taskUUID), + WorkflowIDReusePolicy: temporalEnums.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE, + WorkflowIDConflictPolicy: temporalEnums.WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING, + WorkflowExecutionTimeout: cutil.WorkflowExecutionTimeout, + TaskQueue: queue.SiteTaskQueue, + } + + ctx, cancel := context.WithTimeout(ctx, cutil.WorkflowContextTimeout) + defer cancel() + + we, err := stc.ExecuteWorkflow(ctx, workflowOptions, "GetTaskByID", rlaRequest) + if err != nil { + logger.Error().Err(err).Msg("failed to execute GetTaskByID workflow") + return cutil.NewAPIErrorResponse(c, http.StatusInternalServerError, "Failed to get Task details", nil) + } + + var rlaResponse rlav1.GetTasksByIDsResponse + err = we.Get(ctx, &rlaResponse) + if err != nil { + var timeoutErr *tp.TimeoutError + if errors.As(err, &timeoutErr) || err == context.DeadlineExceeded || ctx.Err() != nil { + return common.TerminateWorkflowOnTimeOut(c, logger, stc, fmt.Sprintf("task-get-%s", taskUUID), err, "Task", "GetTaskByID") + } + code, err := common.UnwrapWorkflowError(err) + logger.Error().Err(err).Msg("failed to get result from GetTaskByID workflow") + return cutil.NewAPIErrorResponse(c, code, fmt.Sprintf("Failed to get Task details: %s", err), nil) + } + + tasks := rlaResponse.GetTasks() + if len(tasks) == 0 { + return cutil.NewAPIErrorResponse(c, http.StatusNotFound, "Task not found", nil) + } + + apiTask := model.NewAPITask(tasks[0]) + + logger.Info().Msg("finishing API handler") + + return c.JSON(http.StatusOK, apiTask) +} diff --git a/api/pkg/api/handler/task_test.go b/api/pkg/api/handler/task_test.go new file mode 100644 index 00000000..a297c2bb --- /dev/null +++ b/api/pkg/api/handler/task_test.go @@ -0,0 +1,211 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package handler + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "github.com/nvidia/bare-metal-manager-rest/api/pkg/api/handler/util/common" + "github.com/nvidia/bare-metal-manager-rest/api/pkg/api/model" + sc "github.com/nvidia/bare-metal-manager-rest/api/pkg/client/site" + "github.com/nvidia/bare-metal-manager-rest/common/pkg/otelecho" + cdbm "github.com/nvidia/bare-metal-manager-rest/db/pkg/db/model" + rlav1 "github.com/nvidia/bare-metal-manager-rest/workflow-schema/rla/protobuf/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + oteltrace "go.opentelemetry.io/otel/trace" + tmocks "go.temporal.io/sdk/mocks" +) + +func TestGetTaskHandler_Handle(t *testing.T) { + e := echo.New() + dbSession := testRackInitDB(t) + defer dbSession.Close() + + cfg := common.GetTestConfig() + tcfg, _ := cfg.GetTemporalConfig() + scp := sc.NewClientPool(tcfg) + + org := "test-org" + _, site, _ := testRackSetupTestData(t, dbSession, org) + + siteNoRLA := &cdbm.Site{ + ID: uuid.New(), + Name: "test-site-no-rla", + Org: org, + InfrastructureProviderID: site.InfrastructureProviderID, + Status: cdbm.SiteStatusRegistered, + Config: &cdbm.SiteConfig{}, + } + _, err := dbSession.DB.NewInsert().Model(siteNoRLA).Exec(context.Background()) + assert.Nil(t, err) + + providerUser := testRackBuildUser(t, dbSession, "provider-user-task-get", org, []string{"FORGE_PROVIDER_ADMIN"}) + tenantUser := testRackBuildUser(t, dbSession, "tenant-user-task-get", org, []string{"FORGE_TENANT_ADMIN"}) + + handler := NewGetTaskHandler(dbSession, nil, scp, cfg) + + taskUUID := uuid.New().String() + + mockTask := &rlav1.Task{ + Id: &rlav1.UUID{Id: taskUUID}, + Operation: "power_on", + RackId: &rlav1.UUID{Id: uuid.New().String()}, + Description: "Power on rack", + Status: rlav1.TaskStatus_TASK_STATUS_RUNNING, + Message: "Processing", + } + + tracer := oteltrace.NewNoopTracerProvider().Tracer("test") + ctx := context.Background() + + tests := []struct { + name string + reqOrg string + user *cdbm.User + taskUUID string + queryParams map[string]string + mockTasks []*rlav1.Task + expectedStatus int + }{ + { + name: "success - get task by ID", + reqOrg: org, + user: providerUser, + taskUUID: taskUUID, + queryParams: map[string]string{ + "siteId": site.ID.String(), + }, + mockTasks: []*rlav1.Task{mockTask}, + expectedStatus: http.StatusOK, + }, + { + name: "failure - task not found (empty result)", + reqOrg: org, + user: providerUser, + taskUUID: taskUUID, + queryParams: map[string]string{ + "siteId": site.ID.String(), + }, + mockTasks: []*rlav1.Task{}, + expectedStatus: http.StatusNotFound, + }, + { + name: "failure - RLA not enabled on site", + reqOrg: org, + user: providerUser, + taskUUID: taskUUID, + queryParams: map[string]string{ + "siteId": siteNoRLA.ID.String(), + }, + expectedStatus: http.StatusPreconditionFailed, + }, + { + name: "failure - missing siteId", + reqOrg: org, + user: providerUser, + taskUUID: taskUUID, + queryParams: map[string]string{ + // no siteId + }, + expectedStatus: http.StatusBadRequest, + }, + { + name: "failure - invalid siteId", + reqOrg: org, + user: providerUser, + taskUUID: taskUUID, + queryParams: map[string]string{ + "siteId": uuid.New().String(), + }, + expectedStatus: http.StatusBadRequest, + }, + { + name: "failure - tenant access denied", + reqOrg: org, + user: tenantUser, + taskUUID: taskUUID, + queryParams: map[string]string{ + "siteId": site.ID.String(), + }, + expectedStatus: http.StatusForbidden, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockTemporalClient := &tmocks.Client{} + mockWorkflowRun := &tmocks.WorkflowRun{} + mockWorkflowRun.On("GetID").Return("test-workflow-id") + if tt.mockTasks != nil { + mockWorkflowRun.Mock.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + resp := args.Get(1).(*rlav1.GetTasksByIDsResponse) + resp.Tasks = tt.mockTasks + }).Return(nil) + } + mockTemporalClient.Mock.On("ExecuteWorkflow", mock.Anything, mock.Anything, "GetTaskByID", mock.Anything).Return(mockWorkflowRun, nil) + scp.IDClientMap[site.ID.String()] = mockTemporalClient + + q := url.Values{} + for k, v := range tt.queryParams { + q.Set(k, v) + } + path := fmt.Sprintf("/v2/org/%s/carbide/rack/task/%s?%s", tt.reqOrg, tt.taskUUID, q.Encode()) + + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + + ec := e.NewContext(req, rec) + ec.SetParamNames("orgName", "uuid") + ec.SetParamValues(tt.reqOrg, tt.taskUUID) + ec.Set("user", tt.user) + + ctx = context.WithValue(ctx, otelecho.TracerKey, tracer) + ec.SetRequest(ec.Request().WithContext(ctx)) + + err := handler.Handle(ec) + + if tt.expectedStatus != rec.Code { + t.Errorf("GetTaskHandler.Handle() status = %v, want %v, response: %v, err: %v", rec.Code, tt.expectedStatus, rec.Body.String(), err) + } + + require.Equal(t, tt.expectedStatus, rec.Code) + if tt.expectedStatus != http.StatusOK { + return + } + + var apiTask model.APITask + err = json.Unmarshal(rec.Body.Bytes(), &apiTask) + assert.NoError(t, err) + assert.Equal(t, taskUUID, apiTask.ID) + assert.Equal(t, "running", apiTask.Status) + assert.Equal(t, "Power on rack", apiTask.Description) + assert.Equal(t, "Processing", apiTask.Message) + }) + } +} diff --git a/api/pkg/api/model/task.go b/api/pkg/api/model/task.go new file mode 100644 index 00000000..741aea66 --- /dev/null +++ b/api/pkg/api/model/task.go @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package model + +import ( + "fmt" + + rlav1 "github.com/nvidia/bare-metal-manager-rest/workflow-schema/rla/protobuf/v1" +) + +var ProtoToAPITaskStatusName = map[rlav1.TaskStatus]string{ + rlav1.TaskStatus_TASK_STATUS_UNKNOWN: "unknown", + rlav1.TaskStatus_TASK_STATUS_PENDING: "pending", + rlav1.TaskStatus_TASK_STATUS_RUNNING: "running", + rlav1.TaskStatus_TASK_STATUS_COMPLETED: "succeeded", + rlav1.TaskStatus_TASK_STATUS_FAILED: "failed", +} + +// APITask is the API response model for a task. +type APITask struct { + ID string `json:"id"` + Status string `json:"status"` + Description string `json:"description"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + Message string `json:"message"` + Metadata map[string]string `json:"metadata"` +} + +func (t *APITask) FromProto(task *rlav1.Task) { + if task == nil { + return + } + if task.GetId() != nil { + t.ID = task.GetId().GetId() + } + t.Status = enumOr(ProtoToAPITaskStatusName, task.GetStatus(), "unknown") + t.Description = task.GetDescription() + t.Message = task.GetMessage() +} + +func NewAPITask(task *rlav1.Task) *APITask { + t := &APITask{} + t.FromProto(task) + return t +} + +// APIGetTaskRequest captures query parameters for getting a task by ID. +type APIGetTaskRequest struct { + SiteID string `query:"siteId"` +} + +func (r *APIGetTaskRequest) Validate() error { + if r.SiteID == "" { + return fmt.Errorf("siteId query parameter is required") + } + return nil +} diff --git a/api/pkg/api/model/task_test.go b/api/pkg/api/model/task_test.go new file mode 100644 index 00000000..c534f00f --- /dev/null +++ b/api/pkg/api/model/task_test.go @@ -0,0 +1,170 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package model + +import ( + "testing" + + rlav1 "github.com/nvidia/bare-metal-manager-rest/workflow-schema/rla/protobuf/v1" + "github.com/stretchr/testify/assert" +) + +func TestNewAPITask(t *testing.T) { + tests := []struct { + name string + task *rlav1.Task + expected *APITask + }{ + { + name: "nil task returns empty APITask", + task: nil, + expected: &APITask{}, + }, + { + name: "task with all fields", + task: &rlav1.Task{ + Id: &rlav1.UUID{Id: "task-123"}, + Operation: "power_on", + RackId: &rlav1.UUID{Id: "rack-456"}, + Description: "Power on rack components", + Status: rlav1.TaskStatus_TASK_STATUS_RUNNING, + Message: "Processing 3 of 5 components", + }, + expected: &APITask{ + ID: "task-123", + Status: "running", + Description: "Power on rack components", + Message: "Processing 3 of 5 components", + }, + }, + { + name: "task with pending status", + task: &rlav1.Task{ + Id: &rlav1.UUID{Id: "task-001"}, + Description: "Firmware upgrade", + Status: rlav1.TaskStatus_TASK_STATUS_PENDING, + }, + expected: &APITask{ + ID: "task-001", + Status: "pending", + Description: "Firmware upgrade", + }, + }, + { + name: "task with completed status maps to succeeded", + task: &rlav1.Task{ + Id: &rlav1.UUID{Id: "task-002"}, + Description: "Bring up rack", + Status: rlav1.TaskStatus_TASK_STATUS_COMPLETED, + Message: "All components ready", + }, + expected: &APITask{ + ID: "task-002", + Status: "succeeded", + Description: "Bring up rack", + Message: "All components ready", + }, + }, + { + name: "task with failed status", + task: &rlav1.Task{ + Id: &rlav1.UUID{Id: "task-003"}, + Description: "Power off rack", + Status: rlav1.TaskStatus_TASK_STATUS_FAILED, + Message: "BMC unreachable", + }, + expected: &APITask{ + ID: "task-003", + Status: "failed", + Description: "Power off rack", + Message: "BMC unreachable", + }, + }, + { + name: "task with unknown status", + task: &rlav1.Task{ + Id: &rlav1.UUID{Id: "task-004"}, + Status: rlav1.TaskStatus_TASK_STATUS_UNKNOWN, + }, + expected: &APITask{ + ID: "task-004", + Status: "unknown", + }, + }, + { + name: "task with nil ID", + task: &rlav1.Task{ + Description: "Orphan task", + Status: rlav1.TaskStatus_TASK_STATUS_PENDING, + }, + expected: &APITask{ + Status: "pending", + Description: "Orphan task", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := NewAPITask(tt.task) + assert.NotNil(t, result) + assert.Equal(t, tt.expected.ID, result.ID) + assert.Equal(t, tt.expected.Status, result.Status) + assert.Equal(t, tt.expected.Description, result.Description) + assert.Equal(t, tt.expected.Message, result.Message) + assert.Empty(t, result.StartTime) + assert.Empty(t, result.EndTime) + assert.Nil(t, result.Metadata) + }) + } +} + +func TestAPIGetTaskRequest_Validate(t *testing.T) { + tests := []struct { + name string + request APIGetTaskRequest + wantErr bool + }{ + { + name: "valid request", + request: APIGetTaskRequest{SiteID: "550e8400-e29b-41d4-a716-446655440000"}, + wantErr: false, + }, + { + name: "missing siteId", + request: APIGetTaskRequest{}, + wantErr: true, + }, + { + name: "empty siteId", + request: APIGetTaskRequest{SiteID: ""}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.request.Validate() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/api/pkg/api/routes.go b/api/pkg/api/routes.go index d721e22a..6a1f3f82 100644 --- a/api/pkg/api/routes.go +++ b/api/pkg/api/routes.go @@ -820,6 +820,11 @@ func NewAPIRoutes(dbSession *cdb.Session, tc tClient.Client, tnc tClient.Namespa Handler: apiHandler.NewGetSkuHandler(dbSession, tc, cfg), }, // Rack endpoints (RLA) + { + Path: apiPathPrefix + "/rack/task/:uuid", + Method: http.MethodGet, + Handler: apiHandler.NewGetTaskHandler(dbSession, tc, scp, cfg), + }, { Path: apiPathPrefix + "/rack", Method: http.MethodGet, diff --git a/api/pkg/api/routes_test.go b/api/pkg/api/routes_test.go index e657552b..e684fd99 100644 --- a/api/pkg/api/routes_test.go +++ b/api/pkg/api/routes_test.go @@ -80,7 +80,7 @@ func TestNewAPIRoutes(t *testing.T) { "machine-validation": 11, "dpu-extension-service": 7, "sku": 2, - "rack": 10, + "rack": 11, "tray": 8, "stats": 4, } diff --git a/docs/index.html b/docs/index.html index c8b203ef..b1fbfe7d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -437,7 +437,7 @@ -

Error response when user is not authorized to call an endpoint or retrieve/modify objects

Request samples

Content type
application/json
Example
{
  • "siteId": "550e8400-e29b-41d4-a716-446655440000"
}

Response samples

Content type
application/json
{
  • "taskIds": [
    ]
}

Tray

https://carbide-rest-api.carbide.svc.cluster.local/v2/org/{org}/carbide/rack/{id}/bringup

Request samples

Content type
application/json
Example
{
  • "siteId": "550e8400-e29b-41d4-a716-446655440000"
}

Response samples

Content type
application/json
{
  • "taskIds": [
    ]
}

Retrieve a Task

Get an RLA Task by UUID.

+

Org must have an Infrastructure Provider entity. User must have FORGE_PROVIDER_ADMIN authorization role.

+
Authorizations:
JWTBearerToken
path Parameters
org
required
string

Name of the Org

+
uuid
required
string <uuid>

UUID of the Task

+
query Parameters
siteId
required
string <uuid>

ID of the Site

+

Responses

Response samples

Content type
application/json
{
  • "id": "550e8400-e29b-41d4-a716-446655440000",
  • "status": "running",
  • "description": "Power on rack components",
  • "message": "Processing 3 of 5 components"
}

Tray

Tray operations

Retrieve all Trays