The compute service exposes a higher-level Instance abstraction backed by
hidden compute-server lifecycle provided by the
Region service.
Users interact with ComputeInstance resources and the /api/v2/instances
API. The compute controller then realizes that desired state as hidden
region.Server lifecycle, projects server status back onto the instance, and
coordinates quota/accounting at the instance abstraction boundary.
Historically this service is close to the Kubernetes service, and where possible type and API parity are still useful for UX tooling and shared service integration. That historical similarity is not the main architectural fact about this repository though: the important distinction is the visible instance-versus-hidden-server split.
Package-level architecture and lifecycle documentation lives under
pkg/README.md.
Recommended entry points:
pkg/README.mdfor the service-level package graph and lifecycle summarypkg/apis/unikorn/v1alpha1/README.mdfor the persistedComputeInstanceresource modelpkg/server/handler/instance/README.mdfor the publicInstanceAPI behaviourpkg/provisioners/managers/instance/README.mdfor the controller-side realization of hidden backingregion.Serverlifecycle
To use the Compute service you first need to install:
- The identity service to provide API authentication and authorization.
- The region service to provide provider agnostic cloud services (e.g. images, flavors and identity management).
The compute server component has a couple prerequisites that are required for correct functionality. If not installing the server component, skip to the next section.
You'll need to install:
- cert-manager (used to generate keying material for JWE/JWS and for ingress TLS)
- nginx-ingress (to perform routing, avoiding CORS, and TLS termination)
Helm
Create a values.yaml for the server component:
A typical values.yaml that uses cert-manager and ACME, and external DNS might look like:
global:
identity:
host: https://identity.unikorn-cloud.org
region:
host: https://region.unikorn-cloud.org
compute:
host: https://compute.unikorn-cloud.orghelm install unikorn-compute charts/compute --namespace unikorn-compute --create-namespace --values values.yamlArgoCD
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: unikorn-compute
namespace: argocd
spec:
project: default
source:
repoURL: https://unikorn-cloud.github.io/compute
chart: compute
targetRevision: v0.1.0
destination:
namespace: unikorn
server: https://kubernetes.default.svc
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueThe Identity Service describes how to configure a service organization, groups and role mappings for services that require them.
This service requires access to both identity and region APIs in order to:
- authorize instance operations
- charge and release project-scoped resource allocations
- validate region-owned resources such as networks, flavors, images, security groups, and SSH certificate authorities
- create, update, delete, and operate the hidden backing
region.Serverassociated with each instance
This service defines the unikorn-compute user that will need to be added to a group in the service organization.
It will need the built in role infra-manager-service that allows:
- Access to allocation endpoints in
identityto create, update, and delete compute-related resource allocations - Read access to
regionresources used to validate and scope instances, such as networks, flavors, images, security groups, and SSH certificate authorities - Create/Read/Update/Delete and operational access to
regionserversendpoints in order to realize and operate hidden backing server lifecycle for compute instances
For the code-level architecture behind that split, start with
pkg/README.md.
The compute service includes comprehensive API integration tests that validate instance operations, security, and authentication.
Tests are configured via environment variables using a .env file in the test/ directory.
Setup:
-
Set up your environment configuration:
Copy the example config and update with your values:
cp test/.env.example test/.env
Or create environment-specific files (not tracked in git):
# Create .env.dev with your dev credentials cp test/.env.example test/.env.dev # Edit test/.env.dev with dev values # Create .env.uat with your UAT credentials cp test/.env.example test/.env.uat # Edit test/.env.uat with UAT values # Use the appropriate environment cp test/.env.dev test/.env # For dev environment cp test/.env.uat test/.env # For UAT environment
-
Configure the required values in
test/.env:API_BASE_URL- Compute API server URLAPI_AUTH_TOKEN- Service token from consoleTEST_ORG_ID,TEST_PROJECT_ID- Test organization and project IDsTEST_REGION_ID- Test region IDTEST_NETWORK_ID- Test network IDTEST_FLAVOR_ID,TEST_IMAGE_ID- Test flavor and image IDs
Note: All test/.env and test/.env.* files are gitignored and contain sensitive credentials. They should never be committed to the repository. You can use either test/.env directly or create environment-specific files like test/.env.dev, test/.env.uat, etc.
Run all tests:
make test-apiRun all tests in parallel:
make test-api-parallelRun specific test suite using focus:
# Example: run only instance operations tests
make test-api-focus FOCUS="Instance Operations"Run specific test spec using focus:
# Example: run only a specific test spec by name
make test-api-focus FOCUS="should successfully stop a running instance"Advanced Ginkgo options:
# Run with different parallel workers
cd test/api/suites && ginkgo run --procs=8 --json-report=test-results.json
# Run with verbose output
cd test/api/suites && ginkgo run -v --show-node-events
# Skip specific tests
cd test/api/suites && ginkgo run --skip="Security and Authentication"
# Randomize test order
cd test/api/suites && ginkgo run --randomize-allThe API tests can be triggered manually via GitHub Actions using workflow_dispatch:
Workflow Inputs:
| Input | Type | Description | Default |
|---|---|---|---|
run_dev |
boolean | Run Dev environment tests | true |
run_uat |
boolean | Run UAT environment tests | false |
use_staging_constellation |
boolean | Use the staged constellation tag for UAT checkout | true |
skip_slack_notifications |
boolean | Skip Slack notifications for this run | false |
focus |
choice | Test suite to run | All |
region_id_override |
string | Override region ID for manual runs | unset |
flavor_id_override |
string | Override flavor ID when overriding region | unset |
image_id_override |
string | Override image ID when overriding region | unset |
network_id_override |
string | Override network ID when overriding region | unset |
Available Test Suite Options:
All- Run all test suitesInstance Operations- Instance lifecycle and power operation testsSecurity and Authentication- Authentication and input validation tests
If region_id_override is set, flavor_id_override, image_id_override, and
network_id_override must also be provided.
Scheduled UAT runs check out the staged constellation tag resolved by the
workflow. Manual UAT runs use the same staged constellation lookup by default.
To run UAT against the branch or tag selected in GitHub's manual workflow
picker instead, set use_staging_constellation to false. Disabling
use_staging_constellation is enough to trigger the UAT job for that selected
ref.
Triggering Manually:
- Navigate to Actions tab in GitHub
- Select API Tests workflow
- Click Run workflow
- Select which environments to test:
- Run Dev tests (checked by default)
- Run UAT tests (unchecked by default)
- Choose test suite from the focus dropdown
- To test UAT from the selected workflow branch or tag, set
use_staging_constellation to
false - To suppress Slack messages for the run, set
skip_slack_notifications to
true - Click Run workflow
Test Artifacts:
After each run, test results are uploaded as artifacts per environment:
api-test-results-dev/api-test-results-uat- JSON format test resultsapi-test-junit-dev/api-test-junit-uat- JUnit XML format for CI integration
make test-api-cleanThe compute service uses consumer-driven contract testing to validate interactions with dependent services (e.g., uni-region, uni-identity) without requiring full service deployments.
Install Pact FFI Library:
Consumer contract tests require the Pact FFI library to be installed locally.
macOS:
brew tap pact-foundation/pact-ruby-standalone
brew install pact-ruby-standalone
mkdir -p $HOME/Library/pact
cp /usr/local/opt/pact-ruby-standalone/libexec/lib/*.dylib $HOME/Library/pact/Start Pact Broker:
The Pact Broker is required for publishing and managing contracts. Reference the uni-core repository's make target for starting a local broker instance, or run:
docker run -d --name pact-broker \
-p 9292:9292 \
-e PACT_BROKER_DATABASE_URL=sqlite:///pact_broker.sqlite \
pactfoundation/pact-broker:latestRun all consumer tests:
make test-contracts-consumerPublish pacts to broker:
make publish-pactsAvailable make targets:
test-contracts-consumer- Run consumer contract testspublish-pacts- Publish generated pacts to Pact Brokercan-i-deploy- Check if service version is safe to deployrecord-deployment- Record deployment to an environmentclean-contracts- Clean generated pact files
Configuration:
Broker settings can be configured via environment variables or Makefile defaults:
PACT_BROKER_URL- Broker base URL (default:http://localhost:9292)PACT_BROKER_USERNAME- Broker username (default:pact)PACT_BROKER_PASSWORD- Broker password (default:pact)
Consumer tests follow a standard pattern:
- Create a Pact mock provider using
contract.NewV4Pact() - Define interactions with
Given(),UponReceiving(),WithRequest(),WillRespondWith() - Execute the test using your actual client code against the mock server
- Verify expectations using Gomega matchers (you can and should use the OpenAPI spec as a guide here for building tests)
Example structure:
var _ = Describe("Provider Service Contract", func() {
var pact *consumer.V4HTTPMockProvider
BeforeEach(func() {
pact, _ = contract.NewV4Pact(contract.PactConfig{
Consumer: "uni-compute",
Provider: "uni-region",
PactDir: "../pacts",
})
})
It("returns expected response", func() {
pact.AddInteraction().
GivenWithParameter(...).
UponReceiving("a request").
WithRequest("GET", "/api/v1/endpoint").
WillRespondWith(200, func(b *consumer.V4ResponseBuilder) {
b.JSONBody(...)
})
test := func(config consumer.MockServerConfig) error {
// Use actual client code here
return nil
}
Expect(pact.ExecuteTest(testingT, test)).To(Succeed())
})
})For complete examples, see:
test/contracts/consumer/region/regions_test.go- Region service consumer teststest/contracts/consumer/identity/identity_test.go- Identity service consumer tests
The compute service defines consumer contracts for interactions with the identity service resource allocation API:
- Create allocation - Track resource usage when instances are created
- Update allocation - Update resource counts when scaling operations occur
- Delete allocation - Release resource allocations during cleanup
In exceptional circumstances (e.g. a hotfix that can't wait for contract tests to be updated), contract testing can be bypassed by adding the skip-contract-tests label to a PR.
When this label is present, both the ConsumerContractTests and CanIDeploy CI jobs are skipped. Skipped jobs show as neutral (green) in GitHub and satisfy required status checks, so the PR can still be merged.
This label should only be used as a last resort. Its use is visible in the PR timeline and auditable. After merging, the contract tests must be updated and the label removed before the next PR.