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 d486a0c422e4b686304ea4b6e06f588bd35f75e2 Author: DImuthuUpe <[email protected]> AuthorDate: Sat May 16 20:46:00 2026 -0400 Implemented Resource Rate APIs --- docs/API-Docs.md | 130 +++++++++++++++++++ ...0005_compute_allocation_resource_rates.down.sql | 18 +++ ...000005_compute_allocation_resource_rates.up.sql | 32 +++++ internal/server/server.go | 55 ++++++++ .../compute_allocation_resource_rate_store.go | 110 ++++++++++++++++ internal/store/store.go | 21 +++ pkg/models/allocation.go | 10 +- pkg/service/compute_allocation_resource_rate.go | 144 +++++++++++++++++++++ pkg/service/service.go | 4 + 9 files changed, 519 insertions(+), 5 deletions(-) diff --git a/docs/API-Docs.md b/docs/API-Docs.md index f509a3000..cec6e6331 100644 --- a/docs/API-Docs.md +++ b/docs/API-Docs.md @@ -549,6 +549,123 @@ List every compute allocation that has the given resource attached. --- +## Compute Allocation Resource Rates + +A rate captures how many Service Units (SUs) are charged per unit of a +compute allocation resource over a bounded time window. Multiple rates can +exist for the same resource; usage at any instant is charged using the rate +whose `[start_time, end_time)` window contains that instant. + +Rates are cascade-deleted when their parent resource is deleted. + +### `POST /compute-allocation-resource-rates` + +Create a new rate for a compute allocation resource. + +**Required fields:** `compute_allocation_resource_id`, `rate`, `start_time`, `end_time` +**Optional fields:** `id` + +Validation: + +- `compute_allocation_resource_id` must reference an existing resource. +- `rate` must be ≥ 0. +- `start_time` must be strictly before `end_time`. + +**Request** + +```json +{ + "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", + "rate": 2.0, + "start_time": "2026-01-01T00:00:00Z", + "end_time": "2026-12-31T23:59:59Z" +} +``` + +**Response 201** + +```json +{ + "id": "55aa66bb-77cc-88dd-99ee-001122334455", + "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", + "rate": 2.0, + "start_time": "2026-01-01T00:00:00Z", + "end_time": "2026-12-31T23:59:59Z" +} +``` + +**Errors** + +- `400` — required field missing, invalid time window, negative `rate`, or unknown `compute_allocation_resource_id`. + +--- + +### `GET /compute-allocation-resource-rates/{id}` + +Retrieve a rate by its ID. + +**Errors** + +- `404` — no rate matches the supplied ID. + +--- + +### `GET /compute-allocation-resources/{id}/rates` + +List every rate ever defined for the given compute allocation resource, +ordered by `start_time` ascending. + +**Response 200** + +```json +[ + { + "id": "55aa66bb-77cc-88dd-99ee-001122334455", + "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", + "rate": 2.0, + "start_time": "2026-01-01T00:00:00Z", + "end_time": "2026-12-31T23:59:59Z" + } +] +``` + +--- + +### `GET /compute-allocation-resources/{id}/rates/effective` + +Return the rate currently in effect for the given resource. By default the +server uses the current time; supply `?at=<RFC 3339 timestamp>` to query an +arbitrary instant. + +A rate is "effective" at instant *t* when `start_time <= t < end_time`. If +multiple rates overlap *t*, the one with the most recent `start_time` wins. + +**Examples** + +```http +GET /compute-allocation-resources/c0a1.../rates/effective +GET /compute-allocation-resources/c0a1.../rates/effective?at=2026-05-16T12:00:00Z +``` + +**Response 200** + +```json +{ + "id": "55aa66bb-77cc-88dd-99ee-001122334455", + "compute_allocation_resource_id": "c0a1b2c3-d4e5-46f7-8899-aabbccddeeff", + "rate": 2.0, + "start_time": "2026-01-01T00:00:00Z", + "end_time": "2026-12-31T23:59:59Z" +} +``` + +**Errors** + +- `400` — `at` query parameter is not a valid RFC 3339 timestamp. +- `404` — no rate is effective for the resource at the supplied instant. + +--- + ## End-to-end example ```bash @@ -587,6 +704,19 @@ curl -s -X POST $BASE/compute-allocations/$ALLOC_ID/resources \ -H 'Content-Type: application/json' \ -d "{\"compute_allocation_resource_id\":\"$RES_ID\"}" | jq +# Define a rate for the resource. +curl -s -X POST $BASE/compute-allocation-resource-rates \ + -H 'Content-Type: application/json' \ + -d "{ + \"compute_allocation_resource_id\":\"$RES_ID\", + \"rate\":2.0, + \"start_time\":\"2026-01-01T00:00:00Z\", + \"end_time\":\"2026-12-31T23:59:59Z\" + }" | jq + +# Look up the currently-effective rate. +curl -s $BASE/compute-allocation-resources/$RES_ID/rates/effective | jq + # Bidirectional lookups. curl -s $BASE/compute-allocations/$ALLOC_ID/resources | jq curl -s $BASE/compute-allocation-resources/$RES_ID/allocations | jq diff --git a/internal/db/migrations/000005_compute_allocation_resource_rates.down.sql b/internal/db/migrations/000005_compute_allocation_resource_rates.down.sql new file mode 100644 index 000000000..b6938f6ea --- /dev/null +++ b/internal/db/migrations/000005_compute_allocation_resource_rates.down.sql @@ -0,0 +1,18 @@ +-- 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_resource_rates; diff --git a/internal/db/migrations/000005_compute_allocation_resource_rates.up.sql b/internal/db/migrations/000005_compute_allocation_resource_rates.up.sql new file mode 100644 index 000000000..e49346c5e --- /dev/null +++ b/internal/db/migrations/000005_compute_allocation_resource_rates.up.sql @@ -0,0 +1,32 @@ +-- 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_resource_rates +( + id VARCHAR(255) NOT NULL, + compute_allocation_resource_id VARCHAR(255) NOT NULL, + rate DOUBLE NOT NULL DEFAULT 0, + start_time TIMESTAMP(6) NOT NULL, + end_time TIMESTAMP(6) NOT NULL, + created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (id), + KEY idx_carr_rates_resource (compute_allocation_resource_id), + KEY idx_carr_rates_window (compute_allocation_resource_id, start_time, end_time), + CONSTRAINT fk_carr_rates_resource FOREIGN KEY (compute_allocation_resource_id) + REFERENCES compute_allocation_resources (id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; diff --git a/internal/server/server.go b/internal/server/server.go index 5207c724d..89eb31946 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -75,6 +75,11 @@ func (s *Server) routes() { s.mux.HandleFunc("POST /compute-allocations/{id}/resources", s.attachResourceToAllocation) s.mux.HandleFunc("DELETE /compute-allocations/{id}/resources/{resourceId}", s.detachResourceFromAllocation) s.mux.HandleFunc("GET /compute-allocation-resources/{id}/allocations", s.listAllocationsForResource) + + s.mux.HandleFunc("POST /compute-allocation-resource-rates", s.createComputeAllocationResourceRate) + s.mux.HandleFunc("GET /compute-allocation-resource-rates/{id}", s.getComputeAllocationResourceRate) + s.mux.HandleFunc("GET /compute-allocation-resources/{id}/rates", s.listRatesForResource) + s.mux.HandleFunc("GET /compute-allocation-resources/{id}/rates/effective", s.getEffectiveRateForResource) } func (s *Server) healthz(w http.ResponseWriter, _ *http.Request) { @@ -281,6 +286,56 @@ func (s *Server) listAllocationsForResource(w http.ResponseWriter, r *http.Reque writeJSON(w, http.StatusOK, allocs) } +func (s *Server) createComputeAllocationResourceRate(w http.ResponseWriter, r *http.Request) { + var rate models.ComputeAllocationResourceRate + if err := decodeJSON(r, &rate); err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + created, err := s.svc.CreateComputeAllocationResourceRate(r.Context(), &rate) + if err != nil { + writeServiceError(w, err) + return + } + writeJSON(w, http.StatusCreated, created) +} + +func (s *Server) getComputeAllocationResourceRate(w http.ResponseWriter, r *http.Request) { + rate, err := s.svc.GetComputeAllocationResourceRate(r.Context(), r.PathValue("id")) + if err != nil { + writeServiceError(w, err) + return + } + writeJSON(w, http.StatusOK, rate) +} + +func (s *Server) listRatesForResource(w http.ResponseWriter, r *http.Request) { + rates, err := s.svc.ListRatesForResource(r.Context(), r.PathValue("id")) + if err != nil { + writeServiceError(w, err) + return + } + writeJSON(w, http.StatusOK, rates) +} + +func (s *Server) getEffectiveRateForResource(w http.ResponseWriter, r *http.Request) { + var at time.Time + if raw := r.URL.Query().Get("at"); raw != "" { + parsed, err := time.Parse(time.RFC3339Nano, raw) + if err != nil { + writeError(w, http.StatusBadRequest, errors.New("invalid 'at' query parameter; expected RFC 3339")) + return + } + at = parsed + } + rate, err := s.svc.GetEffectiveRateForResource(r.Context(), r.PathValue("id"), at) + if err != nil { + writeServiceError(w, err) + return + } + writeJSON(w, http.StatusOK, rate) +} + // 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_resource_rate_store.go b/internal/store/compute_allocation_resource_rate_store.go new file mode 100644 index 000000000..de9426797 --- /dev/null +++ b/internal/store/compute_allocation_resource_rate_store.go @@ -0,0 +1,110 @@ +// 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" + "time" + + "github.com/jmoiron/sqlx" + + "github.com/apache/airavata-custos/pkg/models" +) + +const computeAllocationResourceRateColumns = "id, compute_allocation_resource_id, rate, start_time, end_time" + +type mysqlComputeAllocationResourceRateStore struct { + db *sqlx.DB +} + +// NewComputeAllocationResourceRateStore returns a MySQL-backed +// ComputeAllocationResourceRateStore. +func NewComputeAllocationResourceRateStore(db *sqlx.DB) ComputeAllocationResourceRateStore { + return &mysqlComputeAllocationResourceRateStore{db: db} +} + +func (s *mysqlComputeAllocationResourceRateStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationResourceRate, error) { + var r models.ComputeAllocationResourceRate + err := s.db.GetContext(ctx, &r, + `SELECT `+computeAllocationResourceRateColumns+` + FROM compute_allocation_resource_rates WHERE id = ?`, id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, err + } + return &r, nil +} + +func (s *mysqlComputeAllocationResourceRateStore) FindByResource(ctx context.Context, resourceID string) ([]models.ComputeAllocationResourceRate, error) { + var rates []models.ComputeAllocationResourceRate + err := s.db.SelectContext(ctx, &rates, + `SELECT `+computeAllocationResourceRateColumns+` + FROM compute_allocation_resource_rates + WHERE compute_allocation_resource_id = ? + ORDER BY start_time`, resourceID) + if err != nil { + return nil, err + } + return rates, nil +} + +func (s *mysqlComputeAllocationResourceRateStore) FindEffective(ctx context.Context, resourceID string, at time.Time) (*models.ComputeAllocationResourceRate, error) { + var r models.ComputeAllocationResourceRate + err := s.db.GetContext(ctx, &r, + `SELECT `+computeAllocationResourceRateColumns+` + FROM compute_allocation_resource_rates + WHERE compute_allocation_resource_id = ? + AND start_time <= ? + AND end_time > ? + ORDER BY start_time DESC + LIMIT 1`, resourceID, at, at) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, err + } + return &r, nil +} + +func (s *mysqlComputeAllocationResourceRateStore) Create(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResourceRate) error { + _, err := tx.ExecContext(ctx, + `INSERT INTO compute_allocation_resource_rates + (id, compute_allocation_resource_id, rate, start_time, end_time) + VALUES (?, ?, ?, ?, ?)`, + r.ID, r.ComputeAllocationResourceID, r.Rate, r.StartTime, r.EndTime) + return err +} + +func (s *mysqlComputeAllocationResourceRateStore) Update(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResourceRate) error { + _, err := tx.ExecContext(ctx, + `UPDATE compute_allocation_resource_rates + SET rate = ?, start_time = ?, end_time = ? + WHERE id = ?`, + r.Rate, r.StartTime, r.EndTime, r.ID) + return err +} + +func (s *mysqlComputeAllocationResourceRateStore) Delete(ctx context.Context, tx *sql.Tx, id string) error { + _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_resource_rates WHERE id = ?`, id) + return err +} diff --git a/internal/store/store.go b/internal/store/store.go index 013f948d1..2ad427c1f 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -20,6 +20,7 @@ package store import ( "context" "database/sql" + "time" "github.com/apache/airavata-custos/pkg/models" ) @@ -131,3 +132,23 @@ type ComputeAllocationResourceMappingStore interface { // DeleteByPair removes the mapping for a (allocation, resource) pair within the provided transaction. DeleteByPair(ctx context.Context, tx *sql.Tx, allocationID, resourceID string) error } + +// ComputeAllocationResourceRateStore defines persistence operations for +// the time-windowed rate at which a compute allocation resource is charged +// in Service Units. +type ComputeAllocationResourceRateStore interface { + // FindByID returns the rate with the given ID, or nil if it does not exist. + FindByID(ctx context.Context, id string) (*models.ComputeAllocationResourceRate, error) + // FindByResource returns every rate ever defined for the given resource, + // ordered by start_time ascending. + FindByResource(ctx context.Context, resourceID string) ([]models.ComputeAllocationResourceRate, error) + // FindEffective returns the rate effective for the given resource at the + // supplied instant (start_time <= at < end_time), or nil if none applies. + FindEffective(ctx context.Context, resourceID string, at time.Time) (*models.ComputeAllocationResourceRate, error) + // Create inserts a new rate within the provided transaction. + Create(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResourceRate) error + // Update replaces mutable fields of an existing rate within the provided transaction. + Update(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResourceRate) error + // Delete removes a rate 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 8d879af1a..d532d0177 100644 --- a/pkg/models/allocation.go +++ b/pkg/models/allocation.go @@ -40,11 +40,11 @@ type ComputeAllocationResourceMapping struct { } type ComputeAllocationResourceRate struct { - ID string `json:"id"` - ComputeAllocationResourceID string `json:"compute_allocation_resource_id"` - Rate float64 `json:"rate"` // The rate for the resource in SUs per unit, e.g., 0.5 SU per CPU hour, 2 SU per GPU hour, etc. - StartTime time.Time `json:"start_time"` // The time when this rate becomes effective. - EndTime time.Time `json:"end_time"` // The time when this rate expires. + ID string `json:"id" db:"id"` + ComputeAllocationResourceID string `json:"compute_allocation_resource_id" db:"compute_allocation_resource_id"` + Rate float64 `json:"rate" db:"rate"` // The rate for the resource in SUs per unit, e.g., 0.5 SU per CPU hour, 2 SU per GPU hour, etc. + StartTime time.Time `json:"start_time" db:"start_time"` // The time when this rate becomes effective. + EndTime time.Time `json:"end_time" db:"end_time"` // The time when this rate expires. } type ComputeAllocationDiff struct { // Diff will occur either through a change reqest or automated workflow like ACCESS AIME diff --git a/pkg/service/compute_allocation_resource_rate.go b/pkg/service/compute_allocation_resource_rate.go new file mode 100644 index 000000000..efd3a3c70 --- /dev/null +++ b/pkg/service/compute_allocation_resource_rate.go @@ -0,0 +1,144 @@ +// 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" + "time" + + "github.com/apache/airavata-custos/pkg/models" +) + +// CreateComputeAllocationResourceRate persists a new rate for a compute +// allocation resource. The referenced resource must exist, start_time must be +// strictly before end_time, and rate must be non-negative. +func (s *Service) CreateComputeAllocationResourceRate(ctx context.Context, rate *models.ComputeAllocationResourceRate) (*models.ComputeAllocationResourceRate, error) { + if rate == nil { + return nil, fmt.Errorf("%w: compute allocation resource rate is nil", ErrInvalidInput) + } + if rate.ComputeAllocationResourceID == "" { + return nil, fmt.Errorf("%w: compute_allocation_resource_id is required", ErrInvalidInput) + } + if rate.Rate < 0 { + return nil, fmt.Errorf("%w: rate must be non-negative", ErrInvalidInput) + } + if rate.StartTime.IsZero() || rate.EndTime.IsZero() { + return nil, fmt.Errorf("%w: start_time and end_time are required", ErrInvalidInput) + } + if !rate.StartTime.Before(rate.EndTime) { + return nil, fmt.Errorf("%w: start_time must be before end_time", ErrInvalidInput) + } + + if res, err := s.resources.FindByID(ctx, rate.ComputeAllocationResourceID); err != nil { + return nil, fmt.Errorf("lookup compute allocation resource: %w", err) + } else if res == nil { + return nil, fmt.Errorf("%w: compute allocation resource %q not found", ErrInvalidInput, rate.ComputeAllocationResourceID) + } + + if rate.ID == "" { + rate.ID = newID() + } + + if err := s.inTx(ctx, func(tx *sql.Tx) error { + return s.resourceRates.Create(ctx, tx, rate) + }); err != nil { + return nil, fmt.Errorf("create compute allocation resource rate: %w", err) + } + return rate, nil +} + +// GetComputeAllocationResourceRate retrieves a rate by its ID. Returns +// ErrNotFound when no rate matches. +func (s *Service) GetComputeAllocationResourceRate(ctx context.Context, id string) (*models.ComputeAllocationResourceRate, error) { + r, err := s.resourceRates.FindByID(ctx, id) + if err != nil { + return nil, fmt.Errorf("get compute allocation resource rate: %w", err) + } + if r == nil { + return nil, ErrNotFound + } + return r, nil +} + +// ListRatesForResource returns every rate ever defined for the given resource, +// ordered chronologically by start_time. +func (s *Service) ListRatesForResource(ctx context.Context, resourceID string) ([]models.ComputeAllocationResourceRate, error) { + if resourceID == "" { + return nil, fmt.Errorf("%w: compute_allocation_resource_id is required", ErrInvalidInput) + } + rates, err := s.resourceRates.FindByResource(ctx, resourceID) + if err != nil { + return nil, fmt.Errorf("list rates for resource: %w", err) + } + return rates, nil +} + +// GetEffectiveRateForResource returns the rate effective for the given resource +// at the supplied instant. Returns ErrNotFound when no rate applies. +func (s *Service) GetEffectiveRateForResource(ctx context.Context, resourceID string, at time.Time) (*models.ComputeAllocationResourceRate, error) { + if resourceID == "" { + return nil, fmt.Errorf("%w: compute_allocation_resource_id is required", ErrInvalidInput) + } + if at.IsZero() { + at = nowUTC() + } + r, err := s.resourceRates.FindEffective(ctx, resourceID, at) + if err != nil { + return nil, fmt.Errorf("get effective rate for resource: %w", err) + } + if r == nil { + return nil, ErrNotFound + } + return r, nil +} + +// UpdateComputeAllocationResourceRate persists changes to an existing rate. +// The referenced resource and ID are immutable from this call's perspective — +// only rate, start_time, and end_time are updated. +func (s *Service) UpdateComputeAllocationResourceRate(ctx context.Context, rate *models.ComputeAllocationResourceRate) error { + if rate == nil || rate.ID == "" { + return fmt.Errorf("%w: compute allocation resource rate id is required", ErrInvalidInput) + } + if rate.Rate < 0 { + return fmt.Errorf("%w: rate must be non-negative", ErrInvalidInput) + } + if !rate.StartTime.IsZero() && !rate.EndTime.IsZero() && !rate.StartTime.Before(rate.EndTime) { + return fmt.Errorf("%w: start_time must be before end_time", ErrInvalidInput) + } + if err := s.inTx(ctx, func(tx *sql.Tx) error { + return s.resourceRates.Update(ctx, tx, rate) + }); err != nil { + return fmt.Errorf("update compute allocation resource rate: %w", err) + } + return nil +} + +// DeleteComputeAllocationResourceRate removes a rate by ID. +func (s *Service) DeleteComputeAllocationResourceRate(ctx context.Context, id string) error { + if id == "" { + return fmt.Errorf("%w: compute allocation resource rate id is required", ErrInvalidInput) + } + if err := s.inTx(ctx, func(tx *sql.Tx) error { + return s.resourceRates.Delete(ctx, tx, id) + }); err != nil { + return fmt.Errorf("delete compute allocation resource rate: %w", err) + } + return nil +} diff --git a/pkg/service/service.go b/pkg/service/service.go index e249fbe7e..119f12483 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -41,6 +41,7 @@ type Service struct { allocs store.ComputeAllocationStore resources store.ComputeAllocationResourceStore resourceMappings store.ComputeAllocationResourceMappingStore + resourceRates store.ComputeAllocationResourceRateStore } // New constructs a Service backed by the supplied database handle. @@ -55,6 +56,7 @@ func New(database *sqlx.DB) *Service { allocs: store.NewComputeAllocationStore(database), resources: store.NewComputeAllocationResourceStore(database), resourceMappings: store.NewComputeAllocationResourceMappingStore(database), + resourceRates: store.NewComputeAllocationResourceRateStore(database), } } @@ -70,6 +72,7 @@ func NewWithStores( allocs store.ComputeAllocationStore, resources store.ComputeAllocationResourceStore, resourceMappings store.ComputeAllocationResourceMappingStore, + resourceRates store.ComputeAllocationResourceRateStore, ) *Service { return &Service{ db: database, @@ -80,6 +83,7 @@ func NewWithStores( allocs: allocs, resources: resources, resourceMappings: resourceMappings, + resourceRates: resourceRates, } }
