Files
cozystack/packages/system/migration-controller/tests/migration_controller_test.yaml
T
Andrei Kvapil 54621bc046 feat(migration): tenant self-service VM import from VMware vSphere
Hidora's customers are leaving VMware now, and a tenant has to be able to
import their own machines without a platform administrator. The chart
approach could not express that: a tenant can neither create the Secret
holding vCenter credentials nor name the VDDK image, so both had to come
from somewhere they cannot reach.

Two CRDs in a new `forklift.cozystack.io` group, split by lifecycle. A
VMImportSource is a long-lived connection -- type, endpoint, credentials,
readiness -- and a VMImportTask is the one-shot operation that names
machines and produces VMDisks and VMInstances. The outputs carry no owner
reference back to the task, so deleting a finished import leaves the
machines alone. Konveyor Forklift does the transfer underneath; the
controller renders its objects, mirrors its verdicts, and hands each
finished volume into a VMDisk without copying it.

What a live vCenter migration forced out, and no amount of source reading
would have:

- A guest copied verbatim keeps the drivers it had under VMware and no
  virtio, so on a virtio disk it does not fail the import -- it succeeds
  and then does not boot. Imported disks go on SATA.
- VDDK connects straight to the ESXi host at the address vCenter
  advertises, which is routinely unreachable, and on the first cluster
  collided with the Service CIDR. `spec.hosts` redirects per host.
- Firmware must be carried across or a UEFI guest imports as BIOS and
  never boots (stacked on #3002).
- Deleting a task mid-transfer orphaned its DataVolume; a finalizer now
  removes the engine's volumes and leaves the outputs.

The console gains a Migration section: connections are editable, and the
VM field is a picker built from a machine list the controller publishes,
since the aggregated API cannot reach the inventory and should not hold a
source's credentials to try. Two general mechanisms fall out of that --
an option source may take an argument, and one named in a CRD annotation
may reference a sibling field.

Design: cozystack/community#62. Docs: cozystack/website#673.

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-09-07 17:03:44 +02:00

154 lines
5.4 KiB
YAML

suite: migration-controller packaging
release:
name: migration-controller
namespace: cozy-migration-controller
tests:
# The VDDK reference is the one piece of platform configuration this
# controller takes. It must arrive as an environment variable rather than a
# flag default baked into the image, so an operator can set it without a
# rebuild — and it must be present even when empty, because empty is a
# meaningful, documented state (the vSphere path reports itself unavailable).
- it: passes the operator-configured VDDK image to the controller
templates:
- templates/deployment.yaml
set:
vddkImage: registry.example.com/vddk:8.0.3
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: MIGRATION_VDDK_IMAGE
value: registry.example.com/vddk:8.0.3
- it: still declares the VDDK variable when the platform has not set one
templates:
- templates/deployment.yaml
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: MIGRATION_VDDK_IMAGE
value: ""
- it: runs with leader election so a second replica cannot race a handoff
templates:
- templates/deployment.yaml
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: --leader-elect
# Both CRDs must ship with the controller that reconciles them: a cluster
# that installs one without the other has an API nothing serves, or a
# controller watching a kind that does not exist.
- it: ships both custom resource definitions
templates:
- templates/crds.yaml
asserts:
- containsDocument:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
name: vmimportsources.forklift.cozystack.io
any: true
- containsDocument:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
name: vmimporttasks.forklift.cozystack.io
any: true
# Tenant access to a new API group travels with the package that owns the
# group. Losing these labels does not break anything visibly — it silently
# makes the whole feature invisible to every tenant, which is exactly the
# kind of regression worth a test.
- it: aggregates read access into the tenant view role
templates:
- templates/tenant-clusterroles.yaml
documentSelector:
path: metadata.name
value: cozy:migration:view
asserts:
- equal:
path: metadata.labels["rbac.cozystack.io/aggregate-to-tenant-view"]
value: "true"
- contains:
path: rules[0].resources
content: vmimportsources
- contains:
path: rules[0].resources
content: vmimporttasks
- contains:
path: rules[0].verbs
content: watch
- it: aggregates write access into the tenant admin role
templates:
- templates/tenant-clusterroles.yaml
documentSelector:
path: metadata.name
value: cozy:migration:admin
asserts:
- equal:
path: metadata.labels["rbac.cozystack.io/aggregate-to-tenant-admin"]
value: "true"
- contains:
path: rules[0].verbs
content: create
- contains:
path: rules[0].verbs
content: delete
# Tenants must never reach the Forklift objects a task builds: those are
# internal machinery, and a tenant editing a Plan mid-transfer is a support
# case nobody wants. They see the migration API's status and nothing else.
- it: grants tenants no access to the Forklift API
templates:
- templates/tenant-clusterroles.yaml
asserts:
- notContains:
path: rules[0].apiGroups
content: forklift.konveyor.io
documentSelector:
path: metadata.name
value: cozy:migration:view
- notContains:
path: rules[0].apiGroups
content: forklift.konveyor.io
documentSelector:
path: metadata.name
value: cozy:migration:admin
# The imported disk and instance are the point of the whole operation and
# belong to the tenant from the moment they exist. The controller creates
# them and must not be able to modify or remove them afterwards — that is
# what makes deleting a finished task safe.
- it: lets the controller create imported objects but never delete them
templates:
- templates/rbac.yaml
asserts:
- notContains:
path: rules
content:
apiGroups: ["apps.cozystack.io"]
resources: ["vmdisks", "vminstances"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- contains:
path: rules
content:
apiGroups: ["apps.cozystack.io"]
resources: ["vmdisks", "vminstances"]
verbs: ["get", "list", "watch", "create"]
# No list or watch on Secrets: the controller reads the credentials Secrets it
# owns by name, and a cluster-wide Secret informer would both need RBAC this
# package refuses to grant and hold every tenant's Secret in memory.
- it: never grants list or watch on Secrets
templates:
- templates/rbac.yaml
asserts:
- contains:
path: rules
content:
apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "update", "patch", "delete"]