liquidapi

package
v0.0.0-...-8135694 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 24, 2025 License: Apache-2.0 Imports: 27 Imported by: 2

Documentation

Overview

Package liquidapi provides a runtime library for servers and clients implementing the LIQUID protocol: <https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid>

  • func Run() provides a full-featured runtime that handles OpenStack credentials, authorization, and more.
  • type Client is a specialized gophercloud.ServiceClient for use in Limes and limesctl.
  • The other functions in this package contain various numeric algorithms that are useful for LIQUID implementations.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AtLeastZero

func AtLeastZero(x int) uint64

AtLeastZero safely converts int values (which often appear in Gophercloud types) to uint64 by clamping negative values to 0.

func DistributeDemandFairly

func DistributeDemandFairly[K comparable](total uint64, demands map[K]liquid.ResourceDemandInAZ, balance map[K]float64) map[K]uint64

DistributeDemandFairly is used to distribute cluster capacity or cluster-wide usage between different resources. Each tier of demand is distributed fairly (while supplies last).

Then anything not yet distributed is split according to the given balance numbers. For example, if balance = { "foo": 3, "bar": 1 }, then "foo" gets 3/4 of the remaining capacity, "bar" gets 1/4, and all other resources do not get anything extra.

func DistributeFairly

func DistributeFairly[K comparable](total uint64, requested map[K]uint64) map[K]uint64

DistributeFairly takes a number of resource requests, as well as a total available capacity, and tries to fulfil all requests as fairly as possible.

If the sum of all requests exceeds the available total, this uses the <https://en.wikipedia.org/wiki/Largest_remainder_method>.

func ForeachOptionTypeInLIQUID

func ForeachOptionTypeInLIQUID[T any](action func(...any) T) []T

ForeachOptionTypeInLIQUID calls action with every Option[] type that appears in the LIQUID API and returns a slice with the results. This is intended for use with the cmpopts.EquateComparable() function when using github.com/google/go-cmp/cmp.

func RestrictToKnownAZs

func RestrictToKnownAZs[T any](input map[liquid.AvailabilityZone][]T, allAZs []liquid.AvailabilityZone) map[liquid.AvailabilityZone][]T

RestrictToKnownAZs takes a mapping of objects sorted by AZ, and moves all objects in unknown AZs into the pseudo-AZ "unknown".

The resulting map will have an entry for each known AZ (possibly a nil slice), and at most one additional key (the well-known value "unknown").

func Run

func Run(ctx context.Context, logic Logic, opts RunOpts) error

Run spawns an HTTP server that serves the LIQUID API, using the provided Logic to answer requests.

It will connect to OpenStack using the standard OS_* environment variables. The provided credentials need to have enough access to execute all the API requests that the Logic type wants to do.

Incoming requests will be authorized using oslo.policy config loaded from a policy file at $LIQUID_POLICY_PATH. The following rules must be defined:

  • "liquid:get_info"
  • "liquid:get_capacity"
  • "liquid:get_usage" (object parameter "project_uuid")
  • "liquid:set_quota" (object parameter "project_uuid")
  • "liquid:change_commitments"

Please refer to the documentation on type RunOpts for various other behaviors that this function provides.

func SaturatingSub

func SaturatingSub[T interface{ int | uint64 }](lhs, rhs T) uint64

SaturatingSub is like `lhs - rhs`, but never underflows below 0.

Types

type Client

type Client struct {
	gophercloud.ServiceClient
}

Client provides structured access to a LIQUID API.

func NewClient

func NewClient(client *gophercloud.ProviderClient, endpointOpts gophercloud.EndpointOpts, opts ClientOpts) (*Client, error)

NewClient creates a Client for interacting with a liquid.

func (*Client) ChangeCommitments

func (c *Client) ChangeCommitments(ctx context.Context, req liquid.CommitmentChangeRequest) (result liquid.CommitmentChangeResponse, err error)

ChangeCommitments executes POST /v1/change-commitments.

func (*Client) GetCapacityReport

func (c *Client) GetCapacityReport(ctx context.Context, req liquid.ServiceCapacityRequest) (result liquid.ServiceCapacityReport, err error)

GetCapacityReport executes POST /v1/report-capacity.

func (*Client) GetInfo

func (c *Client) GetInfo(ctx context.Context) (result liquid.ServiceInfo, err error)

GetInfo executes GET /v1/info.

func (*Client) GetUsageReport

func (c *Client) GetUsageReport(ctx context.Context, projectUUID string, req liquid.ServiceUsageRequest) (result liquid.ServiceUsageReport, err error)

GetUsageReport executes POST /v1/projects/:uuid/report-usage.

func (*Client) PutQuota

func (c *Client) PutQuota(ctx context.Context, projectUUID string, req liquid.ServiceQuotaRequest) (err error)

PutQuota executes PUT /v1/projects/:uuid/quota.

type ClientOpts

type ClientOpts struct {
	// The service type of the liquid in the Keystone catalog.
	// Required if EndpointOverride is not given.
	ServiceType string

	// Skips inspecting the Keystone catalog and assumes that the liquid's API is
	// located at this base URL. Required if ServiceType is not given.
	EndpointOverride string
}

ClientOpts contains additional options for NewClient().

type Logic

type Logic interface {
	// Init is called once at the start of Run(). The logic can use this to
	// obtain its gophercloud.ServiceClient instances.
	Init(ctx context.Context, provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) error

	// BuildServiceInfo will be called once directly after Init(), and then
	// periodically (as configured in RunOpts). The previous ServiceInfo will be
	// served until this call returns, so it's not a big deal if this call takes
	// a long time.
	//
	// If any long-lived state is computed here and needs to be preserved,
	// in addition to the information stored in the ServiceInfo instance,
	// we recommend adding fields of type liquidapi.State to the Logic object.
	// Then, only fill these state slots immediately before returning success from BuildServiceInfo.
	BuildServiceInfo(ctx context.Context) (liquid.ServiceInfo, error)

	// These methods represent all the API endpoints of LIQUID that do actual work.
	//
	// The latest ServiceInfo is provided for reference. Only a shallow copy is
	// provided, so implementations must make sure to not edit the ServiceInfo in
	// order to uphold thread-safety.
	//
	// All these functions should not store long-lived state in the Logic object.
	// Long-lived state should only be computed and updated during BuildServiceInfo().
	//
	// ReviewCommitmentChange() does not need to check the req.InfoVersion value.
	// The caller will already have done so.
	ScanCapacity(ctx context.Context, req liquid.ServiceCapacityRequest, serviceInfo liquid.ServiceInfo) (liquid.ServiceCapacityReport, error)
	ScanUsage(ctx context.Context, projectUUID string, req liquid.ServiceUsageRequest, serviceInfo liquid.ServiceInfo) (liquid.ServiceUsageReport, error)
	SetQuota(ctx context.Context, projectUUID string, req liquid.ServiceQuotaRequest, serviceInfo liquid.ServiceInfo) error
	ReviewCommitmentChange(ctx context.Context, req liquid.CommitmentChangeRequest, serviceInfo liquid.ServiceInfo) (liquid.CommitmentChangeResponse, error)
}

Logic is the interface for types that implement the core logic of a liquid.

Besides Init, all methods may be called in parallel, so the implementation must make sure to protect shared data with mutexes. Note that gophercloud.ServiceClient instances are generally safe to use concurrently.

type RunOpts

type RunOpts struct {
	// If set, the file at $LIQUID_CONFIG_PATH will be json.Unmarshal()ed into
	// the Logic instance to supply configuration to it, before Init() is called.
	TakesConfiguration bool

	// If set, when the runtime loads its oslo.policy from $LIQUID_POLICY_PATH,
	// YAML will be supported in addition to JSON. This is an explicit dependency
	// injection slot to allow the caller to choose their YAML library.
	YAMLUnmarshal func(in []byte, out any) error

	// How often the runtime will call BuildServiceInfo() to refresh the
	// ServiceInfo of the liquid. The zero value can be used for liquids with
	// static ServiceInfo; no polling will be performed then.
	ServiceInfoRefreshInterval time.Duration

	// How many HTTP requests may be served concurrently. If set, the runtime
	// will ensure that no more than that many calls of ScanCapacity, ScanUsage
	// or SetQuota are ongoing at any time. The default (0) imposes no limit.
	MaxConcurrentRequests int

	// (Required.) Where the HTTP server will listen by default, e.g. ":8080".
	// Can be overridden at runtime by setting $LIQUID_LISTEN_ADDRESS.
	DefaultListenAddress string

	// If set, the server will be run with TLS support. Can be overridden at
	// runtime by setting $LIQUID_TLS_CERT_PATH and $LIQUID_TLS_KEY_PATH.
	DefaultTLSCertificatePath string
	DefaultTLSPrivateKeyPath  string
}

RunOpts provides configuration to func Run().

type State

type State[T any] struct {
	// contains filtered or unexported fields
}

State contains data that is guarded by an RWMutex, such that the data cannot be accessed without using the mutex. A zero-initialized State contains a zero-initialized piece of data.

This is provided here for implementations of the Logic interface that compute state during BuildServiceInfo(). See documentation on type Logic.

func (*State[T]) Get

func (s *State[T]) Get() T

Get returns a shallow copy of the contained value.

func (*State[T]) Set

func (s *State[T]) Set(value T)

Set replaces the contained value.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL