Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/kind-runtime-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraMounts:
- hostPath: /dev/kvm
containerPath: /dev/kvm
- hostPath: /tmp/smolvm-kind-var-lib
containerPath: /var/lib/smolvm
- hostPath: /tmp/smolvm-kind-var-run
containerPath: /var/run/smolvm
127 changes: 39 additions & 88 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ jobs:
go-version-file: go.mod
cache: true
- name: Run controller-runtime envtest
run: make test
run: make test-report
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: envtest-reports
path: reports/

kind-e2e:
name: kind e2e
name: kind topology e2e
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -31,103 +37,48 @@ jobs:
- uses: helm/kind-action@v1
with:
cluster_name: kind
- name: Run one deployment e2e test
- name: Run deployed-topology e2e test
env:
RUN_E2E: "true"
KIND_CLUSTER: kind
run: make test-e2e
run: make test-e2e-report
- name: Upload e2e reports
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-reports
path: reports/

smolvm-runtime-smoke:
name: smolvm runtime smoke
smolvm-runtime-e2e:
name: smolvm runtime lifecycle e2e
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- uses: helm/kind-action@v1
with:
cluster_name: runtime
- name: Install smolvm
run: |
sudo apt-get update
sudo apt-get install -y libvirglrenderer1 libepoxy0 libgbm1
curl -fsSL -o /tmp/smolvm.tar.gz https://github.com/smol-machines/smolvm/releases/download/v0.8.1/smolvm-0.8.1-linux-x86_64.tar.gz
mkdir -p /tmp/smolvm
tar -xzf /tmp/smolvm.tar.gz -C /tmp/smolvm --strip-components=1
echo /tmp/smolvm >> "$GITHUB_PATH"
/tmp/smolvm/smolvm --version
- name: Run operator against real smolvm runtime
- name: Prepare KVM-backed kind mounts
run: |
set -euxo pipefail
test -e /dev/kvm
sudo chmod 666 /dev/kvm
export LD_LIBRARY_PATH="/tmp/smolvm/lib:${LD_LIBRARY_PATH:-}"
export LD_PRELOAD="$(find /usr/lib -name 'libvirglrenderer.so.1' | head -1)"
smolvm serve start --listen 127.0.0.1:8080 > smolvm-serve.log 2>&1 &
serve_pid=$!
trap 'kubectl delete smolvm runtime-smoke --ignore-not-found=true --wait=false || true; kill $serve_pid || true; cat smolvm-serve.log || true' EXIT

for i in {1..60}; do
curl -fsS http://127.0.0.1:8080/api/v1/machines && break
sleep 1
done

make install
SMOLVM_API_URL=http://127.0.0.1:8080 go run ./cmd/main.go \
--metrics-bind-address=0 \
--health-probe-bind-address=:8081 > manager.log 2>&1 &
manager_pid=$!
trap 'kubectl delete smolvm runtime-smoke --ignore-not-found=true --wait=false || true; kill $manager_pid || true; kill $serve_pid || true; cat manager.log || true; cat smolvm-serve.log || true' EXIT

cat <<'EOF' | kubectl apply -f -
apiVersion: vm.smolvm.dev/v1alpha1
kind: SmolVM
metadata:
name: runtime-smoke
spec:
running: true
image: alpine:latest
resources:
cpus: 1
memoryMiB: 128
EOF

ready=false
for i in {1..48}; do
echo "--- poll $i ---"
kubectl get smolvm runtime-smoke -o yaml || true
curl -fsS http://127.0.0.1:8080/api/v1/machines || true
status=$(kubectl get smolvm runtime-smoke -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true)
if [ "$status" = "True" ]; then
ready=true
break
fi
sleep 10
done
if [ "$ready" != "true" ]; then
echo "runtime-smoke did not become Ready"
kubectl get smolvm runtime-smoke -o yaml || true
curl -fsS http://127.0.0.1:8080/api/v1/machines || true
exit 1
fi
test "$(kubectl get smolvm runtime-smoke -o jsonpath='{.status.conditions[?(@.type=="RuntimeReady")].status}')" = "True"
test "$(kubectl get smolvm runtime-smoke -o jsonpath='{.status.conditions[?(@.type=="GuestReady")].status}')" = "Unknown"

kubectl patch smolvm runtime-smoke --type=merge -p '{"spec":{"running":false}}'
kubectl wait smolvm runtime-smoke \
--for=jsonpath='{.status.phase}'=Stopped \
--timeout=5m

machine=$(kubectl get smolvm runtime-smoke -o jsonpath='{.status.machineName}')
kubectl delete smolvm runtime-smoke --wait=true --timeout=5m
for i in {1..30}; do
if ! curl -fsS http://127.0.0.1:8080/api/v1/machines | grep -q "$machine"; then
exit 0
fi
sleep 2
done
echo "runtime machine $machine still exists after CR deletion"
curl -fsS http://127.0.0.1:8080/api/v1/machines || true
exit 1
sudo mkdir -p /tmp/smolvm-kind-var-lib /tmp/smolvm-kind-var-run
sudo chmod 777 /tmp/smolvm-kind-var-lib /tmp/smolvm-kind-var-run
- uses: helm/kind-action@v1
with:
cluster_name: runtime
config: .github/kind-runtime-config.yaml
- name: Run runtime lifecycle e2e
env:
RUN_E2E: "true"
KIND_CLUSTER: runtime
RUNTIME_E2E: "true"
run: make test-runtime-e2e-report
- name: Upload runtime logs
if: always()
uses: actions/upload-artifact@v4
with:
name: runtime-e2e-logs
path: reports/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*.dylib
bin/*
Dockerfile.cross
reports/

# Test binary, built with `go test -c`
*.test
Expand Down
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ COPY go.sum go.sum
RUN go mod download

# Copy the go source
COPY cmd/main.go cmd/main.go
COPY cmd/ cmd/
COPY api/ api/
COPY internal/controller/ internal/controller/
COPY internal/smolvm/ internal/smolvm/
Expand All @@ -22,13 +22,15 @@ COPY internal/smolvm/ internal/smolvm/
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go && \
CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o runtime-agent cmd/runtime-agent/main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
COPY --from=builder /workspace/runtime-agent .
USER 65532:65532

ENTRYPOINT ["/manager"]
35 changes: 35 additions & 0 deletions Dockerfile.runtime
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM golang:1.25-bookworm AS agent-builder
WORKDIR /workspace
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download
COPY cmd/ cmd/
COPY api/ api/
COPY internal/smolvm/ internal/smolvm/
RUN CGO_ENABLED=0 go build -a -o /runtime-agent cmd/runtime-agent/main.go

FROM debian:bookworm-slim AS smolvm-release
ARG TARGETARCH
ARG SMOLVM_VERSION=0.8.1
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl tar && rm -rf /var/lib/apt/lists/*
WORKDIR /download
RUN case "${TARGETARCH}" in \
amd64) SMOLVM_ARCH=x86_64 ;; \
arm64) SMOLVM_ARCH=aarch64 ;; \
*) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \
esac && \
curl -fsSL -o smolvm.tar.gz "https://github.com/smol-machines/smolvm/releases/download/v${SMOLVM_VERSION}/smolvm-${SMOLVM_VERSION}-linux-${SMOLVM_ARCH}.tar.gz" && \
mkdir -p /opt/smolvm && \
tar -xzf smolvm.tar.gz -C /opt/smolvm --strip-components=1

FROM ubuntu:24.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends bash ca-certificates libepoxy0 libgbm1 libgcc-s1 libvirglrenderer1 && rm -rf /var/lib/apt/lists/*

WORKDIR /
COPY --from=agent-builder /runtime-agent /runtime-agent
COPY --from=smolvm-release /opt/smolvm/ /opt/smolvm/
RUN chmod 0755 /runtime-agent /opt/smolvm/smolvm /opt/smolvm/smolvm-bin && \
ln -sf /opt/smolvm/smolvm /usr/local/bin/smolvm && \
mkdir -p /var/lib/smolvm /var/run/smolvm

ENTRYPOINT ["/runtime-agent"]
36 changes: 34 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

# Image URL to use all building/pushing image targets
IMG ?= controller:latest
RUNTIME_IMG ?= smolvm-runtime:latest
SMOLVM_VERSION ?= 0.8.1
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.29.0

Expand Down Expand Up @@ -64,11 +66,26 @@ vet: ## Run go vet against code.
test: manifests generate fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out

.PHONY: test-report
test-report: manifests generate fmt vet envtest gotestsum ## Run tests and write JUnit/coverage reports under reports/.
mkdir -p reports
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" $(GOTESTSUM) --format testname --junitfile reports/unit.xml -- -coverprofile reports/cover.out $$(go list ./... | grep -v /e2e)

# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up.
test-e2e:
go test ./test/e2e/ -v -ginkgo.v

.PHONY: test-e2e-report
test-e2e-report: gotestsum ## Run e2e tests and write a JUnit report under reports/.
mkdir -p reports
$(GOTESTSUM) --format testname --junitfile reports/e2e.xml -- ./test/e2e/ -v -ginkgo.v

.PHONY: test-runtime-e2e-report
test-runtime-e2e-report: ## Run runtime lifecycle e2e tests and write a Ginkgo JUnit report under reports/.
mkdir -p reports
go test ./test/e2e -v -ginkgo.v -ginkgo.label-filter=runtime -ginkgo.junit-report=reports/runtime.xml

.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter & yamllint
$(GOLANGCI_LINT) run
Expand All @@ -82,6 +99,7 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
.PHONY: build
build: manifests generate fmt vet ## Build manager binary.
go build -o bin/manager cmd/main.go
go build -o bin/runtime-agent cmd/runtime-agent/main.go

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
Expand All @@ -91,12 +109,17 @@ run: manifests generate fmt vet ## Run a controller from your host.
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
.PHONY: docker-build
docker-build: ## Build docker image with the manager.
docker-build: ## Build docker image with the manager and runtime-agent.
$(CONTAINER_TOOL) build -t ${IMG} .

.PHONY: docker-build-runtime
docker-build-runtime: ## Build the smolvm runtime image, including the released smolvm binary.
$(CONTAINER_TOOL) build -f Dockerfile.runtime --build-arg SMOLVM_VERSION=$(SMOLVM_VERSION) -t ${RUNTIME_IMG} .

.PHONY: docker-push
docker-push: ## Push docker image with the manager.
docker-push: ## Push docker images.
$(CONTAINER_TOOL) push ${IMG}
$(CONTAINER_TOOL) push ${RUNTIME_IMG}

# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
Expand All @@ -123,6 +146,7 @@ build-installer: manifests generate kustomize ## Generate a consolidated YAML wi
fi
echo "---" >> dist/install.yaml # Add a document separator before appending
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
cd config/runtime && $(KUSTOMIZE) edit set image smolvm-runtime=${RUNTIME_IMG}
$(KUSTOMIZE) build config/default >> dist/install.yaml

##@ Deployment
Expand All @@ -142,6 +166,7 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified
.PHONY: deploy
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
cd config/runtime && $(KUSTOMIZE) edit set image smolvm-runtime=${RUNTIME_IMG}
$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -

.PHONY: undeploy
Expand All @@ -161,12 +186,14 @@ KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION)
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION)
ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION)
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION)
GOTESTSUM ?= $(LOCALBIN)/gotestsum-$(GOTESTSUM_VERSION)

## Tool Versions
KUSTOMIZE_VERSION ?= v5.3.0
CONTROLLER_TOOLS_VERSION ?= v0.19.0
ENVTEST_VERSION ?= latest
GOLANGCI_LINT_VERSION ?= v1.54.2
GOTESTSUM_VERSION ?= v1.13.0

.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
Expand All @@ -188,6 +215,11 @@ golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,${GOLANGCI_LINT_VERSION})

.PHONY: gotestsum
gotestsum: $(GOTESTSUM) ## Download gotestsum locally if necessary.
$(GOTESTSUM): $(LOCALBIN)
$(call go-install-tool,$(GOTESTSUM),gotest.tools/gotestsum,${GOTESTSUM_VERSION})

# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary (ideally with version)
# $2 - package url which can be installed
Expand Down
Loading
Loading