You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by di...@apache.org on 2026/05/18 00:31:46 UTC
(airavata-custos) 06/09: Implemented the service layer for Allocation Change Requests and related events
This is an automated email from the ASF dual-hosted git repository.
DImuthuUpe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit f392978aeb75f4ffcf328f23f003b89c710194ad
Author: DImuthuUpe <di...@gmail.com>
AuthorDate: Sat May 16 21:21:59 2026 -0400
Implemented the service layer for Allocation Change Requests and related events
---
docs/API-Docs.md | 186 +++++++++++++++++
...007_compute_allocation_change_requests.down.sql | 19 ++
...00007_compute_allocation_change_requests.up.sql | 49 +++++
internal/server/server.go | 126 ++++++++++++
...ompute_allocation_change_request_event_store.go | 97 +++++++++
.../compute_allocation_change_request_store.go | 109 ++++++++++
internal/store/store.go | 38 ++++
pkg/models/allocation.go | 28 +--
pkg/service/compute_allocation_change_request.go | 219 +++++++++++++++++++++
.../compute_allocation_change_request_event.go | 119 +++++++++++
pkg/service/service.go | 8 +
11 files changed, 984 insertions(+), 14 deletions(-)
diff --git a/docs/API-Docs.md b/docs/API-Docs.md
index 69674e582..7854758d0 100644
--- a/docs/API-Docs.md
+++ b/docs/API-Docs.md
@@ -767,6 +767,192 @@ Return the most recent diff (highest `timestamp`) for the given allocation.
---
+## Compute Allocation Change Requests
+
+A change request represents a user- or admin-initiated proposal to mutate a
+compute allocation — e.g. asking for additional Service Units or to change
+its status. Each request carries a lifecycle (`change_status`: `PENDING`,
+`APPROVED`, `REJECTED`, etc.). Change requests are cascade-deleted when their
+parent allocation is deleted. Every create, update, and delete of a change
+request transactionally appends an entry to its event log (see below); the
+event log is intentionally **not** cascade-deleted so the audit trail
+survives the deletion of the parent change request.
+
+### `POST /compute-allocation-change-requests`
+
+Submit a new change request.
+
+**Required fields:** `compute_allocation_id`, `requester_id`
+**Optional fields:** `id`, `requested_su_amount`, `requested_status`, `reason`, `change_status` (defaults to `PENDING`), `approver_id`, `timestamp` (defaults to the server's current UTC time)
+
+**Request**
+
+```json
+{
+ "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56",
+ "requested_su_amount": 120000,
+ "requested_status": "ACTIVE",
+ "reason": "Need more SUs for upcoming HPC runs",
+ "requester_id": "11112222-3333-4444-5555-666677778888"
+}
+```
+
+**Response 201**
+
+```json
+{
+ "id": "9988aabb-ccdd-eeff-0011-223344556677",
+ "compute_allocation_id": "2f6a8c1d-3e4b-4a7d-8c91-aa12bb34cc56",
+ "requested_su_amount": 120000,
+ "requested_status": "ACTIVE",
+ "reason": "Need more SUs for upcoming HPC runs",
+ "change_status": "PENDING",
+ "requester_id": "11112222-3333-4444-5555-666677778888",
+ "timestamp": "2026-05-16T17:42:11.918Z"
+}
+```
+
+**Errors**
+
+- `400` — required field missing, or `compute_allocation_id` does not exist.
+
+---
+
+### `GET /compute-allocation-change-requests/{id}`
+
+Retrieve a single change request by its ID.
+
+**Errors**
+
+- `404` — no change request matches the supplied ID.
+
+---
+
+### `PUT /compute-allocation-change-requests/{id}`
+
+Replace mutable fields of a change request. Typically used by an approver to
+transition `change_status` (e.g. to `APPROVED` or `REJECTED`) and stamp
+`approver_id`. Omitted fields are preserved from the existing record.
+
+**Request**
+
+```json
+{
+ "change_status": "APPROVED",
+ "approver_id": "aaaa-bbbb-cccc-dddd-eeee"
+}
+```
+
+**Errors**
+
+- `400` — request id missing.
+- `404` — no change request matches the supplied ID.
+
+---
+
+### `DELETE /compute-allocation-change-requests/{id}`
+
+Remove a change request and (cascading) its event log.
+
+**Response 204** — empty body on success.
+
+---
+
+### `GET /compute-allocations/{id}/change-requests`
+
+List every change request ever submitted against the given allocation,
+ordered by `timestamp` ascending.
+
+---
+
+### `GET /users/{id}/change-requests`
+
+List every change request submitted by the given user, ordered by
+`timestamp` ascending.
+
+---
+
+## Compute Allocation Change Request Events
+
+Events are an append-only audit trail of state transitions applied to a
+change request — typically `CREATED`, `APPROVED`, `REJECTED`, `UPDATED`,
+`DELETED`, or arbitrary workflow markers. Create / update / delete of a
+change request each emit an event automatically; clients may also append
+custom events via the endpoint below. Events are **not** cascade-deleted
+when their parent change request is removed, so the audit trail is
+preserved indefinitely.
+
+### `POST /compute-allocation-change-request-events`
+
+Append a new event to a change request.
+
+**Required fields:** `compute_allocation_change_request_id`, `event_type`
+**Optional fields:** `id`, `description`, `timestamp` (defaults to the server's current UTC time)
+
+**Request**
+
+```json
+{
+ "compute_allocation_change_request_id": "9988aabb-ccdd-eeff-0011-223344556677",
+ "event_type": "APPROVED",
+ "description": "Change request approved by admin"
+}
+```
+
+**Response 201**
+
+```json
+{
+ "id": "ee11ff22-3344-5566-7788-99aabbccddee",
+ "compute_allocation_change_request_id": "9988aabb-ccdd-eeff-0011-223344556677",
+ "event_type": "APPROVED",
+ "description": "Change request approved by admin",
+ "timestamp": "2026-05-16T18:00:00.000Z"
+}
+```
+
+**Errors**
+
+- `400` — required field missing, or `compute_allocation_change_request_id` does not exist.
+
+---
+
+### `GET /compute-allocation-change-request-events/{id}`
+
+Retrieve a single event by its ID.
+
+**Errors**
+
+- `404` — no event matches the supplied ID.
+
+---
+
+### `DELETE /compute-allocation-change-request-events/{id}`
+
+Remove an event record. Intended for administrative cleanup; events are
+otherwise append-only.
+
+**Response 204** — empty body on success.
+
+---
+
+### `GET /compute-allocation-change-requests/{id}/events`
+
+List every event recorded against the given change request, ordered by
+`timestamp` ascending.
+
+---
+
+### `GET /compute-allocation-change-requests/{id}/events/latest`
+
+Return the most recent event for the given change request.
+
+**Errors**
+
+- `404` — the change request has no events recorded.
+
+---
+
## End-to-end example
```bash
diff --git a/internal/db/migrations/000007_compute_allocation_change_requests.down.sql b/internal/db/migrations/000007_compute_allocation_change_requests.down.sql
new file mode 100644
index 000000000..8140a01cb
--- /dev/null
+++ b/internal/db/migrations/000007_compute_allocation_change_requests.down.sql
@@ -0,0 +1,19 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you 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.
+
+DROP TABLE IF EXISTS compute_allocation_change_request_events;
+DROP TABLE IF EXISTS compute_allocation_change_requests;
diff --git a/internal/db/migrations/000007_compute_allocation_change_requests.up.sql b/internal/db/migrations/000007_compute_allocation_change_requests.up.sql
new file mode 100644
index 000000000..aa04a8571
--- /dev/null
+++ b/internal/db/migrations/000007_compute_allocation_change_requests.up.sql
@@ -0,0 +1,49 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you 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.
+
+CREATE TABLE IF NOT EXISTS compute_allocation_change_requests
+(
+ id VARCHAR(255) NOT NULL,
+ compute_allocation_id VARCHAR(255) NOT NULL,
+ requested_su_amount BIGINT NOT NULL DEFAULT 0,
+ requested_status VARCHAR(64) NOT NULL,
+ reason TEXT NOT NULL,
+ change_status VARCHAR(64) NOT NULL,
+ requester_id VARCHAR(255) NOT NULL,
+ approver_id VARCHAR(255) NOT NULL DEFAULT '',
+ timestamp TIMESTAMP(6) NOT NULL,
+ created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ PRIMARY KEY (id),
+ KEY idx_compute_allocation_change_requests_allocation (compute_allocation_id, timestamp),
+ KEY idx_compute_allocation_change_requests_status (change_status),
+ KEY idx_compute_allocation_change_requests_requester (requester_id),
+ CONSTRAINT fk_compute_allocation_change_requests_allocation FOREIGN KEY (compute_allocation_id)
+ REFERENCES compute_allocations (id) ON DELETE CASCADE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS compute_allocation_change_request_events
+(
+ id VARCHAR(255) NOT NULL,
+ compute_allocation_change_request_id VARCHAR(255) NOT NULL,
+ event_type VARCHAR(64) NOT NULL,
+ description TEXT NOT NULL,
+ timestamp TIMESTAMP(6) NOT NULL,
+ created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ PRIMARY KEY (id),
+ KEY idx_compute_allocation_change_request_events_request (compute_allocation_change_request_id, timestamp),
+ KEY idx_compute_allocation_change_request_events_type (event_type)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
diff --git a/internal/server/server.go b/internal/server/server.go
index 3638c0751..70f1e5948 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -86,6 +86,19 @@ func (s *Server) routes() {
s.mux.HandleFunc("DELETE /compute-allocation-diffs/{id}", s.deleteComputeAllocationDiff)
s.mux.HandleFunc("GET /compute-allocations/{id}/diffs", s.listDiffsForAllocation)
s.mux.HandleFunc("GET /compute-allocations/{id}/diffs/latest", s.getLatestDiffForAllocation)
+
+ s.mux.HandleFunc("POST /compute-allocation-change-requests", s.createComputeAllocationChangeRequest)
+ s.mux.HandleFunc("GET /compute-allocation-change-requests/{id}", s.getComputeAllocationChangeRequest)
+ s.mux.HandleFunc("PUT /compute-allocation-change-requests/{id}", s.updateComputeAllocationChangeRequest)
+ s.mux.HandleFunc("DELETE /compute-allocation-change-requests/{id}", s.deleteComputeAllocationChangeRequest)
+ s.mux.HandleFunc("GET /compute-allocations/{id}/change-requests", s.listChangeRequestsForAllocation)
+ s.mux.HandleFunc("GET /users/{id}/change-requests", s.listChangeRequestsByRequester)
+
+ s.mux.HandleFunc("POST /compute-allocation-change-request-events", s.createComputeAllocationChangeRequestEvent)
+ s.mux.HandleFunc("GET /compute-allocation-change-request-events/{id}", s.getComputeAllocationChangeRequestEvent)
+ s.mux.HandleFunc("DELETE /compute-allocation-change-request-events/{id}", s.deleteComputeAllocationChangeRequestEvent)
+ s.mux.HandleFunc("GET /compute-allocation-change-requests/{id}/events", s.listEventsForChangeRequest)
+ s.mux.HandleFunc("GET /compute-allocation-change-requests/{id}/events/latest", s.getLatestEventForChangeRequest)
}
func (s *Server) healthz(w http.ResponseWriter, _ *http.Request) {
@@ -391,6 +404,119 @@ func (s *Server) getLatestDiffForAllocation(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusOK, diff)
}
+func (s *Server) createComputeAllocationChangeRequest(w http.ResponseWriter, r *http.Request) {
+ var req models.ComputeAllocationChangeRequest
+ if err := decodeJSON(r, &req); err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+ created, err := s.svc.CreateComputeAllocationChangeRequest(r.Context(), &req)
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusCreated, created)
+}
+
+func (s *Server) getComputeAllocationChangeRequest(w http.ResponseWriter, r *http.Request) {
+ req, err := s.svc.GetComputeAllocationChangeRequest(r.Context(), r.PathValue("id"))
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, req)
+}
+
+func (s *Server) updateComputeAllocationChangeRequest(w http.ResponseWriter, r *http.Request) {
+ var req models.ComputeAllocationChangeRequest
+ if err := decodeJSON(r, &req); err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+ req.ID = r.PathValue("id")
+ updated, err := s.svc.UpdateComputeAllocationChangeRequest(r.Context(), &req)
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, updated)
+}
+
+func (s *Server) deleteComputeAllocationChangeRequest(w http.ResponseWriter, r *http.Request) {
+ if err := s.svc.DeleteComputeAllocationChangeRequest(r.Context(), r.PathValue("id")); err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ w.WriteHeader(http.StatusNoContent)
+}
+
+func (s *Server) listChangeRequestsForAllocation(w http.ResponseWriter, r *http.Request) {
+ rows, err := s.svc.ListChangeRequestsForAllocation(r.Context(), r.PathValue("id"))
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, rows)
+}
+
+func (s *Server) listChangeRequestsByRequester(w http.ResponseWriter, r *http.Request) {
+ rows, err := s.svc.ListChangeRequestsByRequester(r.Context(), r.PathValue("id"))
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, rows)
+}
+
+func (s *Server) createComputeAllocationChangeRequestEvent(w http.ResponseWriter, r *http.Request) {
+ var evt models.ComputeAllocationChangeRequestEvent
+ if err := decodeJSON(r, &evt); err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+ created, err := s.svc.CreateComputeAllocationChangeRequestEvent(r.Context(), &evt)
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusCreated, created)
+}
+
+func (s *Server) getComputeAllocationChangeRequestEvent(w http.ResponseWriter, r *http.Request) {
+ evt, err := s.svc.GetComputeAllocationChangeRequestEvent(r.Context(), r.PathValue("id"))
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, evt)
+}
+
+func (s *Server) deleteComputeAllocationChangeRequestEvent(w http.ResponseWriter, r *http.Request) {
+ if err := s.svc.DeleteComputeAllocationChangeRequestEvent(r.Context(), r.PathValue("id")); err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ w.WriteHeader(http.StatusNoContent)
+}
+
+func (s *Server) listEventsForChangeRequest(w http.ResponseWriter, r *http.Request) {
+ rows, err := s.svc.ListEventsForChangeRequest(r.Context(), r.PathValue("id"))
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, rows)
+}
+
+func (s *Server) getLatestEventForChangeRequest(w http.ResponseWriter, r *http.Request) {
+ evt, err := s.svc.GetLatestEventForChangeRequest(r.Context(), r.PathValue("id"))
+ if err != nil {
+ writeServiceError(w, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, evt)
+}
+
// LoggingMiddleware logs every request once it completes.
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/store/compute_allocation_change_request_event_store.go b/internal/store/compute_allocation_change_request_event_store.go
new file mode 100644
index 000000000..087a696d5
--- /dev/null
+++ b/internal/store/compute_allocation_change_request_event_store.go
@@ -0,0 +1,97 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 store
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+
+ "github.com/jmoiron/sqlx"
+
+ "github.com/apache/airavata-custos/pkg/models"
+)
+
+const computeAllocationChangeRequestEventColumns = "id, compute_allocation_change_request_id, event_type, description, timestamp"
+
+type mysqlComputeAllocationChangeRequestEventStore struct {
+ db *sqlx.DB
+}
+
+// NewComputeAllocationChangeRequestEventStore returns a MySQL-backed
+// ComputeAllocationChangeRequestEventStore.
+func NewComputeAllocationChangeRequestEventStore(db *sqlx.DB) ComputeAllocationChangeRequestEventStore {
+ return &mysqlComputeAllocationChangeRequestEventStore{db: db}
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationChangeRequestEvent, error) {
+ var e models.ComputeAllocationChangeRequestEvent
+ err := s.db.GetContext(ctx, &e,
+ `SELECT `+computeAllocationChangeRequestEventColumns+` FROM compute_allocation_change_request_events WHERE id = ?`, id)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return &e, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) FindByChangeRequest(ctx context.Context, changeRequestID string) ([]models.ComputeAllocationChangeRequestEvent, error) {
+ var rows []models.ComputeAllocationChangeRequestEvent
+ err := s.db.SelectContext(ctx, &rows,
+ `SELECT `+computeAllocationChangeRequestEventColumns+`
+ FROM compute_allocation_change_request_events
+ WHERE compute_allocation_change_request_id = ?
+ ORDER BY timestamp`, changeRequestID)
+ if err != nil {
+ return nil, err
+ }
+ return rows, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) FindLatestByChangeRequest(ctx context.Context, changeRequestID string) (*models.ComputeAllocationChangeRequestEvent, error) {
+ var e models.ComputeAllocationChangeRequestEvent
+ err := s.db.GetContext(ctx, &e,
+ `SELECT `+computeAllocationChangeRequestEventColumns+`
+ FROM compute_allocation_change_request_events
+ WHERE compute_allocation_change_request_id = ?
+ ORDER BY timestamp DESC
+ LIMIT 1`, changeRequestID)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return &e, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) Create(ctx context.Context, tx *sql.Tx, e *models.ComputeAllocationChangeRequestEvent) error {
+ _, err := tx.ExecContext(ctx,
+ `INSERT INTO compute_allocation_change_request_events
+ (id, compute_allocation_change_request_id, event_type, description, timestamp)
+ VALUES (?, ?, ?, ?, ?)`,
+ e.ID, e.ComputeAllocationChangeRequestID, e.EventType, e.Description, e.Timestamp)
+ return err
+}
+
+func (s *mysqlComputeAllocationChangeRequestEventStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_change_request_events WHERE id = ?`, id)
+ return err
+}
diff --git a/internal/store/compute_allocation_change_request_store.go b/internal/store/compute_allocation_change_request_store.go
new file mode 100644
index 000000000..b0b87fe8f
--- /dev/null
+++ b/internal/store/compute_allocation_change_request_store.go
@@ -0,0 +1,109 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 store
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+
+ "github.com/jmoiron/sqlx"
+
+ "github.com/apache/airavata-custos/pkg/models"
+)
+
+const computeAllocationChangeRequestColumns = "id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp"
+
+type mysqlComputeAllocationChangeRequestStore struct {
+ db *sqlx.DB
+}
+
+// NewComputeAllocationChangeRequestStore returns a MySQL-backed
+// ComputeAllocationChangeRequestStore.
+func NewComputeAllocationChangeRequestStore(db *sqlx.DB) ComputeAllocationChangeRequestStore {
+ return &mysqlComputeAllocationChangeRequestStore{db: db}
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationChangeRequest, error) {
+ var c models.ComputeAllocationChangeRequest
+ err := s.db.GetContext(ctx, &c,
+ `SELECT `+computeAllocationChangeRequestColumns+` FROM compute_allocation_change_requests WHERE id = ?`, id)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return &c, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationChangeRequest, error) {
+ var rows []models.ComputeAllocationChangeRequest
+ err := s.db.SelectContext(ctx, &rows,
+ `SELECT `+computeAllocationChangeRequestColumns+`
+ FROM compute_allocation_change_requests
+ WHERE compute_allocation_id = ?
+ ORDER BY timestamp`, allocationID)
+ if err != nil {
+ return nil, err
+ }
+ return rows, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) FindByRequester(ctx context.Context, requesterID string) ([]models.ComputeAllocationChangeRequest, error) {
+ var rows []models.ComputeAllocationChangeRequest
+ err := s.db.SelectContext(ctx, &rows,
+ `SELECT `+computeAllocationChangeRequestColumns+`
+ FROM compute_allocation_change_requests
+ WHERE requester_id = ?
+ ORDER BY timestamp`, requesterID)
+ if err != nil {
+ return nil, err
+ }
+ return rows, nil
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) Create(ctx context.Context, tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error {
+ _, err := tx.ExecContext(ctx,
+ `INSERT INTO compute_allocation_change_requests
+ (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ c.ID, c.ComputeAllocationID, c.RequestedSUAmount, string(c.RequestedStatus), c.Reason, c.ChangeStatus, c.RequesterID, c.ApproverID, c.Timestamp)
+ return err
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) Update(ctx context.Context, tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error {
+ _, err := tx.ExecContext(ctx,
+ `UPDATE compute_allocation_change_requests
+ SET compute_allocation_id = ?,
+ requested_su_amount = ?,
+ requested_status = ?,
+ reason = ?,
+ change_status = ?,
+ requester_id = ?,
+ approver_id = ?,
+ timestamp = ?
+ WHERE id = ?`,
+ c.ComputeAllocationID, c.RequestedSUAmount, string(c.RequestedStatus), c.Reason, c.ChangeStatus, c.RequesterID, c.ApproverID, c.Timestamp, c.ID)
+ return err
+}
+
+func (s *mysqlComputeAllocationChangeRequestStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_change_requests WHERE id = ?`, id)
+ return err
+}
diff --git a/internal/store/store.go b/internal/store/store.go
index 6624d8f5a..54a367c49 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -170,3 +170,41 @@ type ComputeAllocationDiffStore interface {
// Delete removes a diff by ID within the provided transaction.
Delete(ctx context.Context, tx *sql.Tx, id string) error
}
+
+// ComputeAllocationChangeRequestStore defines persistence operations for
+// user/admin requests to change a compute allocation (e.g. asking for more
+// SUs or to change its status).
+type ComputeAllocationChangeRequestStore interface {
+ // FindByID returns the change request with the given ID, or nil if it does not exist.
+ FindByID(ctx context.Context, id string) (*models.ComputeAllocationChangeRequest, error)
+ // FindByAllocation returns every change request ever recorded against the
+ // given allocation, ordered by timestamp ascending.
+ FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationChangeRequest, error)
+ // FindByRequester returns every change request made by the given user,
+ // ordered by timestamp ascending.
+ FindByRequester(ctx context.Context, requesterID string) ([]models.ComputeAllocationChangeRequest, error)
+ // Create inserts a new change request within the provided transaction.
+ Create(ctx context.Context, tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error
+ // Update replaces mutable fields of an existing change request within the provided transaction.
+ Update(ctx context.Context, tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error
+ // Delete removes a change request by ID within the provided transaction.
+ Delete(ctx context.Context, tx *sql.Tx, id string) error
+}
+
+// ComputeAllocationChangeRequestEventStore defines persistence operations
+// for the append-only audit trail of state transitions applied to a
+// ComputeAllocationChangeRequest.
+type ComputeAllocationChangeRequestEventStore interface {
+ // FindByID returns the event with the given ID, or nil if it does not exist.
+ FindByID(ctx context.Context, id string) (*models.ComputeAllocationChangeRequestEvent, error)
+ // FindByChangeRequest returns every event recorded against the given
+ // change request, ordered by timestamp ascending.
+ FindByChangeRequest(ctx context.Context, changeRequestID string) ([]models.ComputeAllocationChangeRequestEvent, error)
+ // FindLatestByChangeRequest returns the most recent event for the given
+ // change request, or nil if none exist.
+ FindLatestByChangeRequest(ctx context.Context, changeRequestID string) (*models.ComputeAllocationChangeRequestEvent, error)
+ // Create inserts a new event within the provided transaction.
+ Create(ctx context.Context, tx *sql.Tx, e *models.ComputeAllocationChangeRequestEvent) error
+ // Delete removes an event by ID within the provided transaction.
+ Delete(ctx context.Context, tx *sql.Tx, id string) error
+}
diff --git a/pkg/models/allocation.go b/pkg/models/allocation.go
index b3e284927..a6003f987 100644
--- a/pkg/models/allocation.go
+++ b/pkg/models/allocation.go
@@ -58,23 +58,23 @@ type ComputeAllocationDiff struct { // Diff will occur either through a change r
}
type ComputeAllocationChangeRequest struct { // Represents a request to change the allocation, e.g., requesting more SUs, requesting a reduction in SUs, etc from users or admins.
- ID string `json:"id"`
- ComputeAllocationID string `json:"compute_allocation_id"`
- RequestedSUAmount int64 `json:"requested_su_amount"` // The requested allocation amount in SUs, e.g., 1200 SUs, etc.
- RequestedStatus AllocationStatus `json:"requested_status"` // ACTIVE, INACTIVE, DELETED, etc.
- Reason string `json:"reason"` // The reason for the change request, e.g., "Need more SUs for upcoming jobs", "Requesting reduction in SUs due to project completion", etc.
- ChangeStatus string `json:"change_status"` // "PENDING", "APPROVED", "REJECTED", etc.
- RequesterID string `json:"requester_id"` // The ID of the user who made the change request.
- ApproverID string `json:"approver_id,omitempty"` // The ID of the user who approved/rejected the change request, if applicable.
- Timestamp time.Time `json:"timestamp"` // The time when the change request was made.
+ ID string `json:"id" db:"id"`
+ ComputeAllocationID string `json:"compute_allocation_id" db:"compute_allocation_id"`
+ RequestedSUAmount int64 `json:"requested_su_amount" db:"requested_su_amount"` // The requested allocation amount in SUs, e.g., 1200 SUs, etc.
+ RequestedStatus AllocationStatus `json:"requested_status" db:"requested_status"` // ACTIVE, INACTIVE, DELETED, etc.
+ Reason string `json:"reason" db:"reason"` // The reason for the change request, e.g., "Need more SUs for upcoming jobs", "Requesting reduction in SUs due to project completion", etc.
+ ChangeStatus string `json:"change_status" db:"change_status"` // "PENDING", "APPROVED", "REJECTED", etc.
+ RequesterID string `json:"requester_id" db:"requester_id"` // The ID of the user who made the change request.
+ ApproverID string `json:"approver_id,omitempty" db:"approver_id"` // The ID of the user who approved/rejected the change request, if applicable.
+ Timestamp time.Time `json:"timestamp" db:"timestamp"` // The time when the change request was made.
}
type ComputeAllocationChangeRequestEvent struct {
- ID string `json:"id"`
- ComputeAllocationChangeRequestID string `json:"compute_allocation_change_request_id"`
- EventType string `json:"event_type"` // "CREATED", "APPROVED", "REJECTED", etc.
- Description string `json:"description,omitempty"` // Optional description of the event, e.g., "Change request created by user", "Change request approved by admin", etc.
- Timestamp time.Time `json:"timestamp"` // The time when the event occurred.
+ ID string `json:"id" db:"id"`
+ ComputeAllocationChangeRequestID string `json:"compute_allocation_change_request_id" db:"compute_allocation_change_request_id"`
+ EventType string `json:"event_type" db:"event_type"` // "CREATED", "APPROVED", "REJECTED", etc.
+ Description string `json:"description,omitempty" db:"description"` // Optional description of the event, e.g., "Change request created by user", "Change request approved by admin", etc.
+ Timestamp time.Time `json:"timestamp" db:"timestamp"` // The time when the event occurred.
}
type ComputeAllocationUsage struct { // Represents the usage of a compute allocation, e.g., when a job consumes some of the allocated SUs, etc.
diff --git a/pkg/service/compute_allocation_change_request.go b/pkg/service/compute_allocation_change_request.go
new file mode 100644
index 000000000..06bda3024
--- /dev/null
+++ b/pkg/service/compute_allocation_change_request.go
@@ -0,0 +1,219 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 service
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+
+ "github.com/apache/airavata-custos/pkg/models"
+)
+
+// CreateComputeAllocationChangeRequest records a new change request against a
+// compute allocation. The referenced allocation must exist and a requester
+// must be supplied. ChangeStatus defaults to "PENDING" and Timestamp to the
+// server's current UTC time. A "CREATED" event is appended to the change
+// request's audit log in the same transaction.
+func (s *Service) CreateComputeAllocationChangeRequest(ctx context.Context, req *models.ComputeAllocationChangeRequest) (*models.ComputeAllocationChangeRequest, error) {
+ if req == nil {
+ return nil, fmt.Errorf("%w: compute allocation change request is nil", ErrInvalidInput)
+ }
+ if req.ComputeAllocationID == "" {
+ return nil, fmt.Errorf("%w: compute_allocation_id is required", ErrInvalidInput)
+ }
+ if req.RequesterID == "" {
+ return nil, fmt.Errorf("%w: requester_id is required", ErrInvalidInput)
+ }
+
+ if alloc, err := s.allocs.FindByID(ctx, req.ComputeAllocationID); err != nil {
+ return nil, fmt.Errorf("lookup compute allocation: %w", err)
+ } else if alloc == nil {
+ return nil, fmt.Errorf("%w: compute allocation %q not found", ErrInvalidInput, req.ComputeAllocationID)
+ }
+
+ if req.ID == "" {
+ req.ID = newID()
+ }
+ if req.ChangeStatus == "" {
+ req.ChangeStatus = "PENDING"
+ }
+ if req.Timestamp.IsZero() {
+ req.Timestamp = nowUTC()
+ }
+
+ if err := s.inTx(ctx, func(tx *sql.Tx) error {
+ if err := s.changeRequests.Create(ctx, tx, req); err != nil {
+ return err
+ }
+ return s.changeEvents.Create(ctx, tx, &models.ComputeAllocationChangeRequestEvent{
+ ID: newID(),
+ ComputeAllocationChangeRequestID: req.ID,
+ EventType: "CREATED",
+ Description: "Change request created",
+ Timestamp: req.Timestamp,
+ })
+ }); err != nil {
+ return nil, fmt.Errorf("create compute allocation change request: %w", err)
+ }
+ return req, nil
+}
+
+// GetComputeAllocationChangeRequest retrieves a change request by its ID.
+// Returns ErrNotFound when no request matches.
+func (s *Service) GetComputeAllocationChangeRequest(ctx context.Context, id string) (*models.ComputeAllocationChangeRequest, error) {
+ c, err := s.changeRequests.FindByID(ctx, id)
+ if err != nil {
+ return nil, fmt.Errorf("get compute allocation change request: %w", err)
+ }
+ if c == nil {
+ return nil, ErrNotFound
+ }
+ return c, nil
+}
+
+// ListChangeRequestsForAllocation returns every change request ever recorded
+// against the given allocation, ordered chronologically by timestamp.
+func (s *Service) ListChangeRequestsForAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationChangeRequest, error) {
+ if allocationID == "" {
+ return nil, fmt.Errorf("%w: compute_allocation_id is required", ErrInvalidInput)
+ }
+ rows, err := s.changeRequests.FindByAllocation(ctx, allocationID)
+ if err != nil {
+ return nil, fmt.Errorf("list change requests for allocation: %w", err)
+ }
+ return rows, nil
+}
+
+// ListChangeRequestsByRequester returns every change request submitted by the
+// given user, ordered chronologically by timestamp.
+func (s *Service) ListChangeRequestsByRequester(ctx context.Context, requesterID string) ([]models.ComputeAllocationChangeRequest, error) {
+ if requesterID == "" {
+ return nil, fmt.Errorf("%w: requester_id is required", ErrInvalidInput)
+ }
+ rows, err := s.changeRequests.FindByRequester(ctx, requesterID)
+ if err != nil {
+ return nil, fmt.Errorf("list change requests by requester: %w", err)
+ }
+ return rows, nil
+}
+
+// UpdateComputeAllocationChangeRequest replaces mutable fields of an existing
+// change request — typically used to transition change_status and record the
+// approver. An audit event is appended in the same transaction: when
+// change_status transitions to a new value the event_type is the new status
+// (e.g. "APPROVED"), otherwise "UPDATED".
+func (s *Service) UpdateComputeAllocationChangeRequest(ctx context.Context, req *models.ComputeAllocationChangeRequest) (*models.ComputeAllocationChangeRequest, error) {
+ if req == nil || req.ID == "" {
+ return nil, fmt.Errorf("%w: compute allocation change request id is required", ErrInvalidInput)
+ }
+ existing, err := s.changeRequests.FindByID(ctx, req.ID)
+ if err != nil {
+ return nil, fmt.Errorf("lookup compute allocation change request: %w", err)
+ }
+ if existing == nil {
+ return nil, ErrNotFound
+ }
+ if req.ComputeAllocationID == "" {
+ req.ComputeAllocationID = existing.ComputeAllocationID
+ }
+ if req.RequesterID == "" {
+ req.RequesterID = existing.RequesterID
+ }
+ if req.ChangeStatus == "" {
+ req.ChangeStatus = existing.ChangeStatus
+ }
+ if req.Timestamp.IsZero() {
+ req.Timestamp = existing.Timestamp
+ }
+
+ eventType := "UPDATED"
+ eventDescription := "Change request updated"
+ if req.ChangeStatus != existing.ChangeStatus {
+ eventType = req.ChangeStatus
+ eventDescription = fmt.Sprintf("Change request transitioned from %s to %s", existing.ChangeStatus, req.ChangeStatus)
+ }
+
+ // When a change request transitions into APPROVED, materialise the
+ // approved change as a ComputeAllocationDiff so the allocation's audit
+ // log reflects the agreed-upon new SU amount and status.
+ approved := req.ChangeStatus == "APPROVED" && existing.ChangeStatus != "APPROVED"
+
+ if err := s.inTx(ctx, func(tx *sql.Tx) error {
+ if err := s.changeRequests.Update(ctx, tx, req); err != nil {
+ return err
+ }
+ return s.changeEvents.Create(ctx, tx, &models.ComputeAllocationChangeRequestEvent{
+ ID: newID(),
+ ComputeAllocationChangeRequestID: req.ID,
+ EventType: eventType,
+ Description: eventDescription,
+ Timestamp: nowUTC(),
+ })
+ }); err != nil {
+ return nil, fmt.Errorf("update compute allocation change request: %w", err)
+ }
+
+ if approved {
+ diffStatus := req.RequestedStatus
+ if diffStatus == "" {
+ diffStatus = models.ACTIVE
+ }
+ if _, err := s.CreateComputeAllocationDiff(ctx, &models.ComputeAllocationDiff{
+ ComputeAllocationID: req.ComputeAllocationID,
+ DiffType: "CHANGE_REQUEST_APPROVED",
+ NewSUAmount: req.RequestedSUAmount,
+ Status: diffStatus,
+ Description: fmt.Sprintf("Applied approved change request %s", req.ID),
+ }); err != nil {
+ return nil, fmt.Errorf("record approved change request diff: %w", err)
+ }
+ }
+ return req, nil
+}
+
+// DeleteComputeAllocationChangeRequest removes a change request by ID. A
+// "DELETED" event is appended to the audit log first so the trail survives
+// the parent record's removal.
+func (s *Service) DeleteComputeAllocationChangeRequest(ctx context.Context, id string) error {
+ if id == "" {
+ return fmt.Errorf("%w: compute allocation change request id is required", ErrInvalidInput)
+ }
+ existing, err := s.changeRequests.FindByID(ctx, id)
+ if err != nil {
+ return fmt.Errorf("lookup compute allocation change request: %w", err)
+ }
+ if existing == nil {
+ return ErrNotFound
+ }
+ if err := s.inTx(ctx, func(tx *sql.Tx) error {
+ if err := s.changeEvents.Create(ctx, tx, &models.ComputeAllocationChangeRequestEvent{
+ ID: newID(),
+ ComputeAllocationChangeRequestID: id,
+ EventType: "DELETED",
+ Description: "Change request deleted",
+ Timestamp: nowUTC(),
+ }); err != nil {
+ return err
+ }
+ return s.changeRequests.Delete(ctx, tx, id)
+ }); err != nil {
+ return fmt.Errorf("delete compute allocation change request: %w", err)
+ }
+ return nil
+}
diff --git a/pkg/service/compute_allocation_change_request_event.go b/pkg/service/compute_allocation_change_request_event.go
new file mode 100644
index 000000000..7dd277796
--- /dev/null
+++ b/pkg/service/compute_allocation_change_request_event.go
@@ -0,0 +1,119 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 service
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+
+ "github.com/apache/airavata-custos/pkg/models"
+)
+
+// CreateComputeAllocationChangeRequestEvent records a new event against a
+// change request. The referenced change request must exist. If Timestamp is
+// zero the server's current UTC time is used. Event records are append-only
+// and have no update operation.
+func (s *Service) CreateComputeAllocationChangeRequestEvent(ctx context.Context, evt *models.ComputeAllocationChangeRequestEvent) (*models.ComputeAllocationChangeRequestEvent, error) {
+ if evt == nil {
+ return nil, fmt.Errorf("%w: compute allocation change request event is nil", ErrInvalidInput)
+ }
+ if evt.ComputeAllocationChangeRequestID == "" {
+ return nil, fmt.Errorf("%w: compute_allocation_change_request_id is required", ErrInvalidInput)
+ }
+ if evt.EventType == "" {
+ return nil, fmt.Errorf("%w: event_type is required", ErrInvalidInput)
+ }
+
+ if cr, err := s.changeRequests.FindByID(ctx, evt.ComputeAllocationChangeRequestID); err != nil {
+ return nil, fmt.Errorf("lookup compute allocation change request: %w", err)
+ } else if cr == nil {
+ return nil, fmt.Errorf("%w: compute allocation change request %q not found", ErrInvalidInput, evt.ComputeAllocationChangeRequestID)
+ }
+
+ if evt.ID == "" {
+ evt.ID = newID()
+ }
+ if evt.Timestamp.IsZero() {
+ evt.Timestamp = nowUTC()
+ }
+
+ if err := s.inTx(ctx, func(tx *sql.Tx) error {
+ return s.changeEvents.Create(ctx, tx, evt)
+ }); err != nil {
+ return nil, fmt.Errorf("create compute allocation change request event: %w", err)
+ }
+ return evt, nil
+}
+
+// GetComputeAllocationChangeRequestEvent retrieves an event by its ID.
+// Returns ErrNotFound when no event matches.
+func (s *Service) GetComputeAllocationChangeRequestEvent(ctx context.Context, id string) (*models.ComputeAllocationChangeRequestEvent, error) {
+ e, err := s.changeEvents.FindByID(ctx, id)
+ if err != nil {
+ return nil, fmt.Errorf("get compute allocation change request event: %w", err)
+ }
+ if e == nil {
+ return nil, ErrNotFound
+ }
+ return e, nil
+}
+
+// ListEventsForChangeRequest returns every event recorded against the given
+// change request, ordered chronologically by timestamp.
+func (s *Service) ListEventsForChangeRequest(ctx context.Context, changeRequestID string) ([]models.ComputeAllocationChangeRequestEvent, error) {
+ if changeRequestID == "" {
+ return nil, fmt.Errorf("%w: compute_allocation_change_request_id is required", ErrInvalidInput)
+ }
+ rows, err := s.changeEvents.FindByChangeRequest(ctx, changeRequestID)
+ if err != nil {
+ return nil, fmt.Errorf("list events for change request: %w", err)
+ }
+ return rows, nil
+}
+
+// GetLatestEventForChangeRequest returns the most recent event for the given
+// change request. Returns ErrNotFound when the change request has no events.
+func (s *Service) GetLatestEventForChangeRequest(ctx context.Context, changeRequestID string) (*models.ComputeAllocationChangeRequestEvent, error) {
+ if changeRequestID == "" {
+ return nil, fmt.Errorf("%w: compute_allocation_change_request_id is required", ErrInvalidInput)
+ }
+ e, err := s.changeEvents.FindLatestByChangeRequest(ctx, changeRequestID)
+ if err != nil {
+ return nil, fmt.Errorf("get latest event for change request: %w", err)
+ }
+ if e == nil {
+ return nil, ErrNotFound
+ }
+ return e, nil
+}
+
+// DeleteComputeAllocationChangeRequestEvent removes an event record by ID.
+// This is intended for administrative cleanup of erroneous entries; the
+// event log is otherwise append-only.
+func (s *Service) DeleteComputeAllocationChangeRequestEvent(ctx context.Context, id string) error {
+ if id == "" {
+ return fmt.Errorf("%w: compute allocation change request event id is required", ErrInvalidInput)
+ }
+ if err := s.inTx(ctx, func(tx *sql.Tx) error {
+ return s.changeEvents.Delete(ctx, tx, id)
+ }); err != nil {
+ return fmt.Errorf("delete compute allocation change request event: %w", err)
+ }
+ return nil
+}
diff --git a/pkg/service/service.go b/pkg/service/service.go
index 90de9e238..1942451be 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -43,6 +43,8 @@ type Service struct {
resourceMappings store.ComputeAllocationResourceMappingStore
resourceRates store.ComputeAllocationResourceRateStore
allocDiffs store.ComputeAllocationDiffStore
+ changeRequests store.ComputeAllocationChangeRequestStore
+ changeEvents store.ComputeAllocationChangeRequestEventStore
}
// New constructs a Service backed by the supplied database handle.
@@ -59,6 +61,8 @@ func New(database *sqlx.DB) *Service {
resourceMappings: store.NewComputeAllocationResourceMappingStore(database),
resourceRates: store.NewComputeAllocationResourceRateStore(database),
allocDiffs: store.NewComputeAllocationDiffStore(database),
+ changeRequests: store.NewComputeAllocationChangeRequestStore(database),
+ changeEvents: store.NewComputeAllocationChangeRequestEventStore(database),
}
}
@@ -76,6 +80,8 @@ func NewWithStores(
resourceMappings store.ComputeAllocationResourceMappingStore,
resourceRates store.ComputeAllocationResourceRateStore,
allocDiffs store.ComputeAllocationDiffStore,
+ changeRequests store.ComputeAllocationChangeRequestStore,
+ changeEvents store.ComputeAllocationChangeRequestEventStore,
) *Service {
return &Service{
db: database,
@@ -88,6 +94,8 @@ func NewWithStores(
resourceMappings: resourceMappings,
resourceRates: resourceRates,
allocDiffs: allocDiffs,
+ changeRequests: changeRequests,
+ changeEvents: changeEvents,
}
}