diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3b853f0c..a53aa36a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,5 +8,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: '1.16' + - run: go version - name: Test run: make test \ No newline at end of file diff --git a/.gitignore b/.gitignore index e6e4a9a9..af5ffef3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,8 @@ # Output of the go coverage tool coverage.out -# GoReleaser dist folder -dist/ +# Out folder +out/ # Bin bin/ diff --git a/Makefile b/Makefile index cd94e05d..0b71e712 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ BUF_VERSION := v0.43.2 # Directories REPO_ROOT := $(shell git rev-parse --show-toplevel) BIN_DIR := bin +OUT_DIR := out REIGNITED_CMD := cmd/reignited TOOLS_DIR := hack/tools TOOLS_BIN_DIR := $(TOOLS_DIR)/bin @@ -28,6 +29,9 @@ $(TOOLS_SHARE_DIR): $(BIN_DIR): mkdir -p $@ +$(OUT_DIR): + mkdir -p $@ + # Binaries GOLANGCI_LINT := $(TOOLS_BIN_DIR)/golangci-lint GINKGO := $(TOOLS_BIN_DIR)/ginkgo @@ -73,7 +77,14 @@ lint: $(GOLANGCI_LINT) $(BUF) ## Lint .PHONY: test test: ## Run unit tests - go test ./... + go test -v ./... + +.PHONY: test-int +test-int: $(OUT_DIR) ## Run tests (including intengration tests) + CTR_ROOT_DIR=$(OUT_DIR)/containerd + mkdir -p $(CTR_ROOT_DIR) + sudo go test -v -count=1 ./... + sudo rm -rf $(CTR_ROOT_DIR) .PHONY: test-e2e test-e2e: ## Run e2e tests diff --git a/cmd/dev-helper/main.go b/cmd/dev-helper/main.go new file mode 100644 index 00000000..bb55153c --- /dev/null +++ b/cmd/dev-helper/main.go @@ -0,0 +1,291 @@ +//nolint +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "github.com/sirupsen/logrus" + + _ "github.com/containerd/containerd/api/events" + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/leases" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/typeurl" + + ctr "github.com/containerd/containerd" + + "github.com/weaveworks/reignite/core/models" + "github.com/weaveworks/reignite/core/ports" + "github.com/weaveworks/reignite/infrastructure/containerd" + rlog "github.com/weaveworks/reignite/pkg/log" +) + +//NOTE: this is a temporary app to help with development + +const ( + vmName = "vm1" + vmNamespace = "teamabc" + imageName = "docker.io/library/ubuntu:groovy" +) + +func main() { + //socketPath := defaults.ContainerdSocket + socketPath := "/home/richard/code/scratch/containerdlocal/run/containerd.sock" + + ctx, cancel := context.WithCancel(context.Background()) + + rlog.Configure(&rlog.Config{ + Verbosity: 0, + Format: "text", + Output: "stderr", + }) + logger := rlog.GetLogger(ctx) + logger.Infof("reignite dev-helper, using containerd socket: %s", socketPath) + + logger.Info("starting containerd event listener") + go eventListener(ctx, socketPath, logger) + + logger.Infof("Press [enter] to write vmspec to using containerd repo") + fmt.Scanln() + repoTest(ctx, socketPath, logger) + + logger.Infof("Press [enter] to get image %s", imageName) + fmt.Scanln() + imageServiceTest(ctx, socketPath, logger) + + //repoUpdateTest(ctx, socketPath) + //imageLeaseTest(ctx, socketPath) + //contentStoreTest(ctx, socketPath) + + logger.Info("Press [enter] to exit") + fmt.Scanln() + + cancel() +} + +func repoTest(ctx context.Context, socketPath string, logger *logrus.Entry) { + client, err := ctr.New(socketPath) + if err != nil { + log.Fatal(err) + } + + repo := containerd.NewMicroVMRepoWithClient(client) + + vmSpec := getTestSpec() + logger.Infof("saving microvm spec %s/%s", vmSpec.Namespace, vmSpec.ID) + + _, err = repo.Save(ctx, vmSpec) + if err != nil { + log.Fatal(err) + } +} + +func imageServiceTest(ctx context.Context, socketPath string, logger *logrus.Entry) { + cfg := &containerd.Config{ + //Snapshotter: defaults.ContainerdSnapshotter, + //Snapshotter: "overlayfs", + SnapshotterKernel: "native", + SnapshotterVolume: "native", + SocketPath: socketPath, + } + logger.Infof("using snapshotters %s & %s", cfg.SnapshotterKernel, cfg.SnapshotterVolume) + + imageService, err := containerd.NewImageService(cfg) + if err != nil { + log.Fatal(err) + } + + input := ports.GetImageInput{ + ImageName: imageName, + OwnerName: vmName, + OwnerNamespace: vmNamespace, + Use: models.ImageUseVolume, + } + mountPoint, err := imageService.GetAndMount(ctx, input) + if err != nil { + log.Fatal(err) + } + + logger.Infof("mounted image %s to %s (type %s)", imageName, mountPoint[0].Source, mountPoint[0].Type) +} + +func eventListener(ctx context.Context, socketPath string, logger *logrus.Entry) { + client, err := ctr.New(socketPath) + if err != nil { + log.Fatal(err) + } + + es := client.EventService() + ch, errsCh := es.Subscribe(ctx) + + for { + select { + case <-ctx.Done(): + logger.Info("Existing event listener") + case evt := <-ch: + v, err := typeurl.UnmarshalAny(evt.Event) + if err != nil { + logger.Errorf("error unmarshalling: %s", err) + continue + } + out, err := json.Marshal(v) + if err != nil { + logger.Errorf("cannot marshal Any into JSON: %s", err) + continue + } + logger.Infof("event received, ns %s, topic %s, body: %s", evt.Namespace, evt.Topic, string(out)) + case errEvt := <-errsCh: + logger.Errorf("event error received: %s", errEvt) + } + } +} + +func imageLeaseTest(ctx context.Context, socketPath string) { + client, err := ctr.New(socketPath) + if err != nil { + log.Fatal(err) + } + + nsCtx := namespaces.WithNamespace(ctx, vmNamespace) + + leaseManager := client.LeasesService() + l, err := leaseManager.Create(nsCtx, leases.WithID("mytestlease")) + if err != nil { + log.Fatal(err) + } + + leaseCtx := leases.WithLease(nsCtx, l.ID) + + image, err := client.Pull(leaseCtx, imageName, ctr.WithPullUnpack) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%#v\n", image) + fmt.Println("done with pull") +} + +func contentStoreTest(ctx context.Context, socketPath string) { + client, err := ctr.New(socketPath) + if err != nil { + log.Fatal(err) + } + + nsCtx := namespaces.WithNamespace(ctx, vmNamespace) + + leaseManager := client.LeasesService() + l, err := leaseManager.Create(nsCtx, leases.WithID("mytestlease")) + if err != nil { + log.Fatal(err) + } + + vmSpec := getTestSpec() + + leaseCtx := leases.WithLease(nsCtx, l.ID) + + store := client.ContentStore() + + refName := "mytestrefname" + writer, err := store.Writer(leaseCtx, content.WithRef(refName)) + if err != nil { + log.Fatal(err) + } + + data, err := json.Marshal(vmSpec) + if err != nil { + log.Fatal(err) + } + + _, err = writer.Write(data) + if err != nil { + log.Fatal(err) + } + + labels := map[string]string{ + "vmid": vmName, + "ns": vmNamespace, + } + err = writer.Commit(leaseCtx, 0, "", content.WithLabels(labels)) + if err != nil { + log.Fatal(err) + } + + writer.Close() +} + +func repoUpdateTest(ctx context.Context, socketPath string) { + client, err := ctr.New(socketPath) + if err != nil { + log.Fatal(err) + } + + repo := containerd.NewMicroVMRepoWithClient(client) + + vmSpec := getTestSpec() + + _, err = repo.Save(ctx, vmSpec) + if err != nil { + log.Fatal(err) + } + + vmSpec.Spec.MemoryInMb = 8096 + + _, err = repo.Save(ctx, vmSpec) + if err != nil { + log.Fatal(err) + } + + specs, err := repo.GetAll(ctx, vmNamespace) + if err != nil { + log.Fatal(err) + } + + for _, spec := range specs { + log.Printf("spec: %#v\n", spec) + } + +} + +func getTestSpec() *models.MicroVM { + return &models.MicroVM{ + ID: vmName, + Namespace: vmNamespace, + Spec: models.MicroVMSpec{ + MemoryInMb: 2048, + VCPU: 4, + Kernel: models.Kernel{ + Image: "docker.io/linuxkit/kernel:5.4.129", + CmdLine: "console=ttyS0 reboot=k panic=1 pci=off i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd ds=nocloud-net;s=http://169.254.169.254/latest/ network-config=ASDFGFDFG", + }, + NetworkInterfaces: []models.NetworkInterface{ + { + AllowMetadataRequests: false, + GuestMAC: "AA:FF:00:00:00:01", + HostDeviceName: "tap1", + GuestDeviceName: "eth0", + }, + { + AllowMetadataRequests: false, + HostDeviceName: "/dev/tap55", + GuestDeviceName: "eth1", + }, + }, + Volumes: []models.Volume{ + { + ID: "root", + IsRoot: true, + IsReadOnly: false, + MountPoint: "/", + Source: models.VolumeSource{ + Container: &models.ContainerVolumeSource{ + Image: imageName, + }, + }, + Size: 20000, + }, + }, + }, + } +} diff --git a/core/application/app_test.go b/core/application/app_test.go index 708637ac..e5b77107 100644 --- a/core/application/app_test.go +++ b/core/application/app_test.go @@ -10,10 +10,7 @@ import ( "github.com/weaveworks/reignite/core/application" "github.com/weaveworks/reignite/core/events" "github.com/weaveworks/reignite/core/models" - prvmock "github.com/weaveworks/reignite/infrastructure/providers/microvm/mock" - repomock "github.com/weaveworks/reignite/infrastructure/repositories/microvm/mock" - eventmock "github.com/weaveworks/reignite/infrastructure/services/event/mock" - idmock "github.com/weaveworks/reignite/infrastructure/services/id/mock" + "github.com/weaveworks/reignite/infrastructure/mock" "github.com/weaveworks/reignite/pkg/defaults" ) @@ -22,19 +19,19 @@ func TestApp_CreateMicroVM(t *testing.T) { name string specToCreate *models.MicroVM expectError bool - expect func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) + expect func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) }{ { name: "nil spec, should fail", expectError: true, - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { }, }, { name: "spec with no id or namespace, create id/ns and create", specToCreate: createTestSpec("", ""), expectError: false, - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { im.GenerateRandom().Return("id1234", nil) rm.Get( @@ -57,7 +54,7 @@ func TestApp_CreateMicroVM(t *testing.T) { em.Publish( gomock.AssignableToTypeOf(context.Background()), gomock.Eq(defaults.TopicMicroVMEvents), - gomock.Eq(&events.MicroVMCreated{ + gomock.Eq(&events.MicroVMSpecCreated{ ID: "id1234", Namespace: defaults.ContainerdNamespace, }), @@ -68,7 +65,7 @@ func TestApp_CreateMicroVM(t *testing.T) { name: "spec with id or namespace, create", specToCreate: createTestSpec("id1234", "default"), expectError: false, - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { rm.Get( gomock.AssignableToTypeOf(context.Background()), gomock.Eq("id1234"), @@ -89,7 +86,7 @@ func TestApp_CreateMicroVM(t *testing.T) { em.Publish( gomock.AssignableToTypeOf(context.Background()), gomock.Eq(defaults.TopicMicroVMEvents), - gomock.Eq(&events.MicroVMCreated{ + gomock.Eq(&events.MicroVMSpecCreated{ ID: "id1234", Namespace: "default", }), @@ -100,7 +97,7 @@ func TestApp_CreateMicroVM(t *testing.T) { name: "spec already exists, should fail", specToCreate: createTestSpec("id1234", "default"), expectError: true, - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { rm.Get( gomock.AssignableToTypeOf(context.Background()), gomock.Eq("id1234"), @@ -120,10 +117,10 @@ func TestApp_CreateMicroVM(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() - rm := repomock.NewMockMicroVMRepository(mockCtrl) - em := eventmock.NewMockEventService(mockCtrl) - im := idmock.NewMockIDService(mockCtrl) - pm := prvmock.NewMockMicroVMProvider(mockCtrl) + rm := mock.NewMockMicroVMRepository(mockCtrl) + em := mock.NewMockEventService(mockCtrl) + im := mock.NewMockIDService(mockCtrl) + pm := mock.NewMockMicroVMProvider(mockCtrl) tc.expect(rm.EXPECT(), em.EXPECT(), im.EXPECT(), pm.EXPECT()) @@ -145,19 +142,19 @@ func TestApp_UpdateMicroVM(t *testing.T) { name string specToUpdate *models.MicroVM expectError bool - expect func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) + expect func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) }{ { name: "nil spec, should fail", expectError: true, - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { }, }, { name: "spec with no id or namespace, should fail", specToUpdate: createTestSpec("", ""), expectError: true, - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { rm.Get( gomock.AssignableToTypeOf(context.Background()), gomock.Eq(""), @@ -172,7 +169,7 @@ func TestApp_UpdateMicroVM(t *testing.T) { name: "spec is valid and update is valid, update", specToUpdate: createTestSpec("id1234", "default"), expectError: false, - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { rm.Get( gomock.AssignableToTypeOf(context.Background()), gomock.Eq("id1234"), @@ -193,7 +190,7 @@ func TestApp_UpdateMicroVM(t *testing.T) { em.Publish( gomock.AssignableToTypeOf(context.Background()), gomock.Eq(defaults.TopicMicroVMEvents), - gomock.Eq(&events.MicroVMUpdated{ + gomock.Eq(&events.MicroVMSpecUpdated{ ID: "id1234", Namespace: "default", }), @@ -209,10 +206,10 @@ func TestApp_UpdateMicroVM(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() - rm := repomock.NewMockMicroVMRepository(mockCtrl) - em := eventmock.NewMockEventService(mockCtrl) - im := idmock.NewMockIDService(mockCtrl) - pm := prvmock.NewMockMicroVMProvider(mockCtrl) + rm := mock.NewMockMicroVMRepository(mockCtrl) + em := mock.NewMockEventService(mockCtrl) + im := mock.NewMockIDService(mockCtrl) + pm := mock.NewMockMicroVMProvider(mockCtrl) tc.expect(rm.EXPECT(), em.EXPECT(), im.EXPECT(), pm.EXPECT()) @@ -235,14 +232,14 @@ func TestApp_DeleteMicroVM(t *testing.T) { toDeleteID string toDeleteNS string expectError bool - expect func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) + expect func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) }{ { name: "empty id, should fail", expectError: true, toDeleteID: "", toDeleteNS: "default", - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { }, }, { @@ -250,7 +247,7 @@ func TestApp_DeleteMicroVM(t *testing.T) { expectError: false, toDeleteID: "id1234", toDeleteNS: "default", - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { rm.Get( gomock.AssignableToTypeOf(context.Background()), gomock.Eq("id1234"), @@ -268,7 +265,7 @@ func TestApp_DeleteMicroVM(t *testing.T) { em.Publish( gomock.AssignableToTypeOf(context.Background()), gomock.Eq(defaults.TopicMicroVMEvents), - gomock.Eq(&events.MicroVMDeleted{ + gomock.Eq(&events.MicroVMSpecDeleted{ ID: "id1234", Namespace: "default", }), @@ -280,7 +277,7 @@ func TestApp_DeleteMicroVM(t *testing.T) { expectError: false, toDeleteID: "id1234", toDeleteNS: "default", - expect: func(rm *repomock.MockMicroVMRepositoryMockRecorder, em *eventmock.MockEventServiceMockRecorder, im *idmock.MockIDServiceMockRecorder, pm *prvmock.MockMicroVMProviderMockRecorder) { + expect: func(rm *mock.MockMicroVMRepositoryMockRecorder, em *mock.MockEventServiceMockRecorder, im *mock.MockIDServiceMockRecorder, pm *mock.MockMicroVMProviderMockRecorder) { rm.Get( gomock.AssignableToTypeOf(context.Background()), gomock.Eq("id1234"), @@ -300,10 +297,10 @@ func TestApp_DeleteMicroVM(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() - rm := repomock.NewMockMicroVMRepository(mockCtrl) - em := eventmock.NewMockEventService(mockCtrl) - im := idmock.NewMockIDService(mockCtrl) - pm := prvmock.NewMockMicroVMProvider(mockCtrl) + rm := mock.NewMockMicroVMRepository(mockCtrl) + em := mock.NewMockEventService(mockCtrl) + im := mock.NewMockIDService(mockCtrl) + pm := mock.NewMockMicroVMProvider(mockCtrl) tc.expect(rm.EXPECT(), em.EXPECT(), im.EXPECT(), pm.EXPECT()) diff --git a/core/application/commands.go b/core/application/commands.go index 168771a3..763720e3 100644 --- a/core/application/commands.go +++ b/core/application/commands.go @@ -47,7 +47,7 @@ func (a *app) CreateMicroVM(ctx context.Context, mvm *models.MicroVM) (*models.M return nil, fmt.Errorf("saving microvm spec: %w", err) } - if err := a.eventSvc.Publish(ctx, defaults.TopicMicroVMEvents, &events.MicroVMCreated{ + if err := a.eventSvc.Publish(ctx, defaults.TopicMicroVMEvents, &events.MicroVMSpecCreated{ ID: mvm.ID, Namespace: mvm.Namespace, }); err != nil { @@ -84,7 +84,7 @@ func (a *app) UpdateMicroVM(ctx context.Context, mvm *models.MicroVM) (*models.M return nil, fmt.Errorf("updating microvm spec: %w", err) } - if err := a.eventSvc.Publish(ctx, defaults.TopicMicroVMEvents, &events.MicroVMUpdated{ + if err := a.eventSvc.Publish(ctx, defaults.TopicMicroVMEvents, &events.MicroVMSpecUpdated{ ID: mvm.ID, Namespace: mvm.Namespace, }); err != nil { @@ -117,7 +117,7 @@ func (a *app) DeleteMicroVM(ctx context.Context, id, namespace string) error { return fmt.Errorf("deleting microvm from repository: %w", err) } - if err := a.eventSvc.Publish(ctx, defaults.TopicMicroVMEvents, &events.MicroVMDeleted{ + if err := a.eventSvc.Publish(ctx, defaults.TopicMicroVMEvents, &events.MicroVMSpecDeleted{ ID: id, Namespace: namespace, }); err != nil { diff --git a/infrastructure/services/event/errors.go b/core/errors.go similarity index 100% rename from infrastructure/services/event/errors.go rename to core/errors.go diff --git a/core/events/events.go b/core/events/spec.go similarity index 57% rename from core/events/events.go rename to core/events/spec.go index 189dcaaa..99263402 100644 --- a/core/events/events.go +++ b/core/events/spec.go @@ -1,23 +1,23 @@ package events -// MicroVMCreated is an event for when a microvm is created. -type MicroVMCreated struct { +// MicroVMSpecCreated is an event for when a microvm spec is created. +type MicroVMSpecCreated struct { // ID is the identifier of the created microvm. ID string // Namespace is the namespace of the created microvm. Namespace string } -// MicroVMUpdated is an event for when a microvm is updated. -type MicroVMUpdated struct { +// MicroVMSpecUpdated is an event for when a microvm spec is updated. +type MicroVMSpecUpdated struct { // ID is the identifier of the updated microvm. ID string // Namespace is the namespace of the updated microvm. Namespace string } -// MicroVMDeleted is an event for when a microvm is deleted. -type MicroVMDeleted struct { +// MicroVMSpecDeleted is an event for when a microvm spec is deleted. +type MicroVMSpecDeleted struct { // ID is the identifier of the deleted microvm. ID string // Namespace is the namespace of the deleted microvm. diff --git a/core/models/microvm.go b/core/models/microvm.go index 6f28c1b7..3920d72b 100644 --- a/core/models/microvm.go +++ b/core/models/microvm.go @@ -14,8 +14,6 @@ type MicroVM struct { // MicroVMSpec represents the specification of a microvm machine. type MicroVMSpec struct { - // Provider is the name of the microvm provider. Defaults to firecracker. - Provider string `json:"provider,omitempty"` // Kernel specifies the kernel and its argments to use. Kernel Kernel `json:"kernel"` // InitrdImage is an optional initial ramdisk to use. diff --git a/core/models/volumes.go b/core/models/volumes.go new file mode 100644 index 00000000..a4eb7665 --- /dev/null +++ b/core/models/volumes.go @@ -0,0 +1,31 @@ +package models + +// Mount represents a volume mount point. +type Mount struct { + // Type specifies the type of the mount (e.g. device or directory). + Type MountType + // Source is the location of the mounted volume. + Source string +} + +// MountType is a type representing the type of mount. +type MountType string + +const ( + // MountTypeDev represents a mount point that is a block device. + MountTypeDev MountType = "dev" + // MountTypeHostPath represents a mount point that is a directory on the host. + MountTypeHostPath MountType = "hostpath" +) + +// ImageUse is a type representing the how an image will be used. +type ImageUse string + +const ( + // ImageUseVolume represents the usage of af an image for a volume. + ImageUseVolume ImageUse = "volume" + // ImageUseKernel represents the usage of af an image for a kernel. + ImageUseKernel ImageUse = "kernel" + // ImageUseKernel represents the usage of af an image for a initial ramdisk. + ImageUseInitrd ImageUse = "initrd" +) diff --git a/core/ports/services.go b/core/ports/services.go index e87e5472..fff1697d 100644 --- a/core/ports/services.go +++ b/core/ports/services.go @@ -41,3 +41,24 @@ type EventService interface { // Subscribe will subscribe to events on a named topic and will call the relevant handlers. Subscribe(ctx context.Context, topic string, handlers EventHandlers) error } + +// ImageService is a port for a service that interacts with OCI images. +type ImageService interface { + // Get will get (i.e. pull) the image for a specific owner. + Get(ctx context.Context, input GetImageInput) error + // GetAndMount will get (i.e. pull) the image for a specific owner and then + // make it available via a mount point. + GetAndMount(ctx context.Context, input GetImageInput) ([]models.Mount, error) +} + +// GetImageInput is the input to getting a image. +type GetImageInput struct { + // ImageName is the name of the image to get. + ImageName string + // OwnerName is the name of the owner of the image. + OwnerName string + // OwnerNamespace is the namespace of the owner of the image. + OwnerNamespace string + // Use is an indoicator of what the image will be used for. + Use models.ImageUse +} diff --git a/go.mod b/go.mod index 28e310f4..e11e51b2 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/containerd/containerd v1.5.5 + github.com/containerd/typeurl v1.0.2 // indirect github.com/firecracker-microvm/firecracker-go-sdk v0.22.0 // indirect github.com/go-openapi/strfmt v0.19.5 // indirect github.com/golang/mock v1.6.0 diff --git a/infrastructure/containerd/config.go b/infrastructure/containerd/config.go new file mode 100644 index 00000000..628221ab --- /dev/null +++ b/infrastructure/containerd/config.go @@ -0,0 +1,46 @@ +package containerd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/weaveworks/reignite/pkg/defaults" +) + +const ( + volSnapshotterFlagName = "containerd-volume-ss" + kernelSnapshotterFlagName = "containerd-kernel-ss" + socketPathFlagName = "containerd-socket" + + supportedSnapshotters = "overlayfs,native,devmapper" +) + +// Config holds the contaierd configuration. +type Config struct { + // SnapshotterKernel is the name of the containerd snapshotter to use for kernel images. + SnapshotterKernel string + // SnapshotterVolume is the name of the containerd snapshotter to use for volume (inc initrd) images. + SnapshotterVolume string + // SocketPath is the path to the containerd socket. + SocketPath string +} + +// AddFlagsToCommand will add the containerd image service specific flags to the supplied cobra command. +func AddFlagsToCommand(cmd *cobra.Command, config *Config) error { + cmd.Flags().StringVar(&config.SocketPath, + socketPathFlagName, + defaults.ContainerdSocket, + "The path to the containerd socket.") + + cmd.Flags().StringVar(&config.SnapshotterKernel, + kernelSnapshotterFlagName, + defaults.ContainerdSnapshotter, + fmt.Sprintf("The name of the snapshotter to use with containerd for kernel images. Options: %s", supportedSnapshotters)) + + cmd.Flags().StringVar(&config.SnapshotterVolume, + volSnapshotterFlagName, + defaults.ContainerdSnapshotter, + fmt.Sprintf("The name of the snapshotter to use with containerd for volume/initrd images. Options: %s", supportedSnapshotters)) + + return nil +} diff --git a/infrastructure/containerd/content.go b/infrastructure/containerd/content.go new file mode 100644 index 00000000..22c3c4bb --- /dev/null +++ b/infrastructure/containerd/content.go @@ -0,0 +1,27 @@ +package containerd + +import ( + "fmt" + + "github.com/weaveworks/reignite/core/models" + "github.com/weaveworks/reignite/pkg/defaults" +) + +var ( + // IDLabel is the name of the containerd content store label used for the microvm identifier. + IDLabel = fmt.Sprintf("%s/vmid", defaults.Domain) + // NamespaceLabel is the name of the containerd content store label used for the microvm namespace. + NamespaceLabel = fmt.Sprintf("%s/ns", defaults.Domain) + // TypeLabel is the name of the containerd content store label used to denote the type of content. + TypeLabel = fmt.Sprintf("%s/type", defaults.Domain) + // VersionLabel is the name of the containerd content store label to hold version of the content. + VersionLabel = fmt.Sprintf("%s/version", defaults.Domain) +) + +func contentRefName(microvm *models.MicroVM) string { + return fmt.Sprintf("%s/%s", microvm.Namespace, microvm.ID) +} + +func labelFilter(name, value string) string { + return fmt.Sprintf("labels.\"%s\"==\"%s\"", name, value) +} diff --git a/infrastructure/containerd/convert.go b/infrastructure/containerd/convert.go new file mode 100644 index 00000000..117bd2b8 --- /dev/null +++ b/infrastructure/containerd/convert.go @@ -0,0 +1,47 @@ +package containerd + +import ( + "fmt" + + "github.com/containerd/containerd/mount" + "github.com/weaveworks/reignite/core/models" +) + +func convertMountToModel(m mount.Mount, snapshotter string) (models.Mount, error) { + switch snapshotter { + case "overlayfs": + return models.Mount{ + Type: models.MountTypeHostPath, + Source: getOverlayMountPath(m), + }, nil + case "native": + return models.Mount{ + Type: models.MountTypeHostPath, + Source: m.Source, + }, nil + case "devmapper": + return models.Mount{ + Type: models.MountTypeDev, + Source: m.Source, + }, nil + default: + return models.Mount{}, errUnsupportedSnapshotter{name: snapshotter} + } +} + +func getOverlayMountPath(m mount.Mount) string { + return "" +} + +func convertMountsToModel(mounts []mount.Mount, snapshotter string) ([]models.Mount, error) { + convertedMounts := []models.Mount{} + for _, m := range mounts { + counvertedMount, err := convertMountToModel(m, snapshotter) + if err != nil { + return nil, fmt.Errorf("converting mount: %w", err) + } + convertedMounts = append(convertedMounts, counvertedMount) + } + + return convertedMounts, nil +} diff --git a/infrastructure/repositories/microvm/containerd/errors.go b/infrastructure/containerd/errors.go similarity index 69% rename from infrastructure/repositories/microvm/containerd/errors.go rename to infrastructure/containerd/errors.go index 537255e3..d09409d0 100644 --- a/infrastructure/repositories/microvm/containerd/errors.go +++ b/infrastructure/containerd/errors.go @@ -24,3 +24,12 @@ func IsSpecNotFound(err error) bool { return errors.Is(err, e) } + +type errUnsupportedSnapshotter struct { + name string +} + +// Error returns the error message. +func (e errUnsupportedSnapshotter) Error() string { + return fmt.Sprintf("snapshotter %s is not supported: snapshotters %s are supported", e.name, supportedSnapshotters) +} diff --git a/infrastructure/containerd/image_service.go b/infrastructure/containerd/image_service.go new file mode 100644 index 00000000..3521d87c --- /dev/null +++ b/infrastructure/containerd/image_service.go @@ -0,0 +1,137 @@ +package containerd + +import ( + "context" + "fmt" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/namespaces" + "github.com/sirupsen/logrus" + + "github.com/weaveworks/reignite/core/models" + "github.com/weaveworks/reignite/core/ports" + "github.com/weaveworks/reignite/pkg/log" +) + +// NewImageService will create a new image service based on containerd with the supplied config. +func NewImageService(cfg *Config) (ports.ImageService, error) { + client, err := containerd.New(cfg.SocketPath) + if err != nil { + return nil, fmt.Errorf("creating containerd client: %w", err) + } + + return NewImageServiceWithClient(cfg, client), nil +} + +// NewImageServiceWithClient will create a new image service based on containerd with the supplied containerd client. +func NewImageServiceWithClient(cfg *Config, client *containerd.Client) ports.ImageService { + return &imageService{ + config: cfg, + client: client, + } +} + +type imageService struct { + client *containerd.Client + config *Config +} + +// Get will get (i.e. pull) the image for a specific owner. +func (im *imageService) Get(ctx context.Context, input ports.GetImageInput) error { + logger := log.GetLogger(ctx).WithField("service", "containerd_image") + actionMessage := fmt.Sprintf("getting image %s for owner %s/%s", input.ImageName, input.OwnerNamespace, input.OwnerName) + logger.Debugf(actionMessage) + + nsCtx := namespaces.WithNamespace(ctx, input.OwnerNamespace) + + _, err := im.getImage(nsCtx, input.ImageName, input.OwnerName, input.OwnerNamespace) + if err != nil { + return fmt.Errorf("%s: %w", actionMessage, err) + } + + return nil +} + +// Get will get (i.e. pull) the image for a specific owner and then +// make it available via a mount point. +func (im *imageService) GetAndMount(ctx context.Context, input ports.GetImageInput) ([]models.Mount, error) { + logger := log.GetLogger(ctx).WithField("service", "containerd_image") + logger.Debugf("getting and mounting image %s for owner %s/%s", input.ImageName, input.OwnerNamespace, input.OwnerName) + + nsCtx := namespaces.WithNamespace(ctx, input.OwnerNamespace) + + image, err := im.getImage(nsCtx, input.ImageName, input.OwnerName, input.OwnerNamespace) + if err != nil { + return nil, fmt.Errorf("getting image %s for owner %s/%s: %w", input.ImageName, input.OwnerNamespace, input.OwnerName, err) + } + + ss := im.config.SnapshotterVolume + if input.Use == models.ImageUseKernel { + ss = im.config.SnapshotterKernel + } + + return im.snapshotAndMount(nsCtx, image, input.OwnerName, ss, logger) +} + +func (im *imageService) getImage(ctx context.Context, imageName string, ownerName, ownerNamespace string) (containerd.Image, error) { + leaseCtx, err := withOwnerLease(ctx, ownerName, ownerNamespace, im.client) + if err != nil { + return nil, fmt.Errorf("getting lease for owner: %w", err) + } + + image, err := im.client.Pull(leaseCtx, imageName, containerd.WithPullUnpack) + if err != nil { + return nil, fmt.Errorf("pulling image using containerd: %w", err) + } + + return image, nil +} + +func (im *imageService) snapshotAndMount(ctx context.Context, image containerd.Image, ownerName, snapshotter string, logger *logrus.Entry) ([]models.Mount, error) { + unpacked, err := image.IsUnpacked(ctx, snapshotter) + if err != nil { + return nil, fmt.Errorf("checking if image %s has been unpacked with snapshotter %s: %w", image.Name(), snapshotter, err) + } + if !unpacked { + logger.Debugf("image %s isn't unpacked, unpacking using %s snapshotter", image.Name(), snapshotter) + if unpackErr := image.Unpack(ctx, snapshotter); unpackErr != nil { + return nil, fmt.Errorf("unpacking %s with snapshotter %s: %w", image.Name(), snapshotter, err) + } + } + + imageContent, err := image.RootFS(ctx) + if err != nil { + return nil, fmt.Errorf("getting rootfs content for %s: %w", image.Name(), err) + } + parent := imageContent[0].String() + + snapshotKey := snapshotKey(ownerName) + logger.Debugf("creating snapshot %s for image %s with snapshotter %s", snapshotKey, image.Name(), snapshotter) + ss := im.client.SnapshotService(snapshotter) + + snapshotExists, err := snapshotExists(ctx, snapshotKey, ss) + if err != nil { + return nil, fmt.Errorf("checking for existence of snapshot %s: %w", snapshotKey, err) + } + + var mounts []mount.Mount + if !snapshotExists { + mounts, err = ss.Prepare(ctx, snapshotKey, parent) + if err != nil { + return nil, fmt.Errorf("preparing snapshot of %s: %w", image.Name(), err) + } + } else { + mounts, err = ss.Mounts(ctx, snapshotKey) + if err != nil { + return nil, fmt.Errorf("getting mounts of %s: %w", image.Name(), err) + } + } + + convertedMounts, err := convertMountsToModel(mounts, snapshotter) + if err != nil { + return nil, fmt.Errorf("converting snapshot mounts: %w", err) + } + + return convertedMounts, nil +} diff --git a/infrastructure/containerd/image_service_test.go b/infrastructure/containerd/image_service_test.go new file mode 100644 index 00000000..2c79e456 --- /dev/null +++ b/infrastructure/containerd/image_service_test.go @@ -0,0 +1,202 @@ +package containerd_test + +import ( + "context" + _ "embed" + "fmt" + "os" + "os/exec" + "syscall" + "testing" + + . "github.com/onsi/gomega" + "github.com/weaveworks/reignite/core/models" + "github.com/weaveworks/reignite/core/ports" + "github.com/weaveworks/reignite/infrastructure/containerd" + + ctr "github.com/containerd/containerd" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/snapshots" +) + +var ( + testImage = "docker.io/library/alpine:3.14.1" + testSnapshotter = "native" + testOwnerNamespace = "int_ns" + testOwnerName = "imageservice-get-test" + + //go:embed testdata/config.toml + containerdConfig string +) + +func TestImageService_Integration(t *testing.T) { + if !runContainerDTests() { + t.Skip("skipping containerd image service integration test") + } + + RegisterTestingT(t) + + client, ctx := testCreateClient(t) + namespaceCtx := namespaces.WithNamespace(ctx, testOwnerNamespace) + + imageSvc := containerd.NewImageServiceWithClient(&containerd.Config{ + SnapshotterKernel: testSnapshotter, + SnapshotterVolume: testSnapshotter, + }, client) + + input := ports.GetImageInput{ + ImageName: testImage, + OwnerName: testOwnerName, + OwnerNamespace: testOwnerNamespace, + Use: models.ImageUseVolume, + } + err := imageSvc.Get(ctx, input) + Expect(err).NotTo(HaveOccurred()) + + mounts, err := imageSvc.GetAndMount(ctx, input) + Expect(err).NotTo(HaveOccurred()) + Expect(mounts).NotTo(BeNil()) + Expect(len(mounts)).To(Equal(1)) + + img, err := client.ImageService().List(namespaceCtx) + Expect(err).NotTo(HaveOccurred()) + Expect(len(img)).To(Equal(1)) + Expect(img[0].Name).To(Equal(testImage)) + + expectedSnapshotName := fmt.Sprintf("reignite/%s", testOwnerName) + snapshotExists := false + err = client.SnapshotService(testSnapshotter).Walk(namespaceCtx, func(walkCtx context.Context, info snapshots.Info) error { + if info.Name == expectedSnapshotName { + snapshotExists = true + } + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + Expect(snapshotExists).To(BeTrue(), "expect snapshot with name %s to exist", expectedSnapshotName) + + expectedLeaseName := fmt.Sprintf("reignite/%s/%s", testOwnerNamespace, testOwnerName) + leases, err := client.LeasesService().List(namespaceCtx) + Expect(err).NotTo(HaveOccurred()) + Expect(len(leases)).To(Equal(1)) + Expect(leases[0].ID).To(Equal(expectedLeaseName), "expect lease with name %s to exists", expectedLeaseName) + + input.Use = models.ImageUseKernel + input.ImageName = "docker.io/linuxkit/kernel:5.4.129" + + err = imageSvc.Get(ctx, input) + Expect(err).NotTo(HaveOccurred()) +} + +func TestMain(m *testing.M) { + if !runContainerDTests() { + os.Exit(m.Run()) + } + + rootDir := os.Getenv("CTR_ROOT_DIR") + if err := os.RemoveAll(rootDir); err != nil { + fmt.Fprintf(os.Stderr, "could not empty test folder %s: %s\n", rootDir, err) + os.Exit(1) + } + fmt.Printf("the containerd root folder is %s\n", rootDir) + + cleanup, err := startContainerd(rootDir) + if err != nil { + fmt.Fprintf(os.Stderr, "could not start containerd: %s\n", err) + os.Exit(1) + } + + status := m.Run() + + cleanup() + os.Exit(status) +} + +func testCreateClient(t *testing.T) (*ctr.Client, context.Context) { + addr := containerDTestSocketPath() + client, err := ctr.New(addr) + Expect(err).NotTo(HaveOccurred()) + + ctx := context.Background() + + serving, err := client.IsServing(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(serving).To(BeTrue()) + + return client, ctx +} + +func runContainerDTests() bool { + testCtr := os.Getenv("CTR_ROOT_DIR") + return testCtr != "" +} + +func containerDTestSocketPath() string { + rootDir := os.Getenv("CTR_ROOT_DIR") + return fmt.Sprintf("%s/containerd.sock", rootDir) +} + +func startContainerd(rootDir string) (func(), error) { + root := fmt.Sprintf("%s/root", rootDir) + os.MkdirAll(root, os.ModePerm) + state := fmt.Sprintf("%s/state", rootDir) + os.MkdirAll(state, os.ModePerm) + addr := containerDTestSocketPath() + cfg := fmt.Sprintf("%s/containerd.config", rootDir) + + stdOutFile, err := os.Create(fmt.Sprintf("%s/stdout.txt", rootDir)) + if err != nil { + return nil, fmt.Errorf("could not open containerd stdout file file %s: %w", stdOutFile.Name(), err) + } + stdErrFile, err := os.Create(fmt.Sprintf("%s/stderr.txt", rootDir)) + if err != nil { + return nil, fmt.Errorf("could not open containerd stderr file file %s: %w", stdErrFile.Name(), err) + } + + if err := writeContainerdConfig(cfg); err != nil { + return nil, fmt.Errorf("writing containerd config file: %w", err) + } + + args := []string{ + "--address", + addr, + "--root", + root, + "--state", + state, + "--log-level", + "debug", + "--config", + cfg, + } + cmd := exec.Command("containerd", args...) + cmd.Stdout = stdOutFile + cmd.Stderr = stdErrFile + if err := cmd.Start(); err != nil { + cmd.Wait() + return nil, fmt.Errorf("failed to start containerd: %w", err) + } + + cleanup := func() { + stdOutFile.Close() + stdErrFile.Close() + cmd.Process.Signal(syscall.SIGTERM) + cmd.Process.Wait() + } + + return cleanup, nil +} + +func writeContainerdConfig(configPath string) error { + cfgFile, err := os.Create(configPath) + defer cfgFile.Close() + + if err != nil { + return fmt.Errorf("could not open containerd config file %s: %w", configPath, err) + } + if _, err := cfgFile.WriteString(containerdConfig); err != nil { + return fmt.Errorf("Failed to write to config file %s: %w", configPath, err) + } + + return nil +} diff --git a/infrastructure/containerd/lease.go b/infrastructure/containerd/lease.go new file mode 100644 index 00000000..3fe32d8c --- /dev/null +++ b/infrastructure/containerd/lease.go @@ -0,0 +1,46 @@ +package containerd + +import ( + "context" + "fmt" + + "github.com/containerd/containerd" + + "github.com/containerd/containerd/leases" +) + +func withOwnerLease(ctx context.Context, ownerName, ownerNamespace string, client *containerd.Client) (context.Context, error) { + leaseName := getLeaseNameForOwner(ownerName, ownerNamespace) + + l, err := getExistingOrCreateLease(ctx, leaseName, client.LeasesService()) + if err != nil { + return nil, fmt.Errorf("getting containerd lease: %w", err) + } + + return leases.WithLease(ctx, l.ID), nil +} + +func getExistingOrCreateLease(ctx context.Context, name string, manager leases.Manager) (*leases.Lease, error) { + filter := fmt.Sprintf("id==%s", name) + existingLeases, err := manager.List(ctx, filter) + if err != nil { + return nil, fmt.Errorf("listing existing containerd leases: %w", err) + } + + for _, lease := range existingLeases { + if lease.ID == name { + return &lease, nil + } + } + + lease, err := manager.Create(ctx, leases.WithID(name)) + if err != nil { + return nil, fmt.Errorf("creating lease with name %s: %w", name, err) + } + + return &lease, nil +} + +func getLeaseNameForOwner(ownerName, ownerNamespace string) string { + return fmt.Sprintf("reignite/%s/%s", ownerNamespace, ownerName) +} diff --git a/infrastructure/containerd/repo.go b/infrastructure/containerd/repo.go new file mode 100644 index 00000000..1eb98f67 --- /dev/null +++ b/infrastructure/containerd/repo.go @@ -0,0 +1,280 @@ +package containerd + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "sync" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/namespaces" + "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/weaveworks/reignite/core/models" + "github.com/weaveworks/reignite/core/ports" + "github.com/weaveworks/reignite/pkg/log" +) + +// NewMicroVMRepo will create a new containerd backed microvm repository with the supplied containerd configuration. +func NewMicroVMRepo(cfg *Config) (ports.MicroVMRepository, error) { + client, err := containerd.New(cfg.SocketPath) + if err != nil { + return nil, fmt.Errorf("creating containerd client: %w", err) + } + + return NewMicroVMRepoWithClient(client), nil +} + +// NewMicroVMRepoWithClient will create a new containerd backed microvm repository with the supplied containerd client. +func NewMicroVMRepoWithClient(client *containerd.Client) ports.MicroVMRepository { + return &containerdRepo{ + client: client, + locks: map[string]*sync.RWMutex{}, + } +} + +type containerdRepo struct { + client *containerd.Client + + locks map[string]*sync.RWMutex + locksMu sync.Mutex +} + +// Save will save the supplied microvm spec to the containred content store. +func (r *containerdRepo) Save(ctx context.Context, microvm *models.MicroVM) (*models.MicroVM, error) { + logger := log.GetLogger(ctx).WithField("repo", "containerd_microvm") + logger.Debugf("saving microvm spec %s/%s", microvm.Namespace, microvm.ID) + + mu := r.getMutex(microvm.ID) + mu.Lock() + defer mu.Unlock() + + namespaceCtx := namespaces.WithNamespace(ctx, microvm.Namespace) + store := r.client.ContentStore() + + microvm.Version++ + + refName := contentRefName(microvm) + writer, err := store.Writer(namespaceCtx, content.WithRef(refName)) + if err != nil { + return nil, fmt.Errorf("getting containerd writer: %w", err) + } + + data, err := json.Marshal(microvm) + if err != nil { + return nil, fmt.Errorf("marshalling microvm to yaml: %w", err) + } + + _, err = writer.Write(data) + if err != nil { + return nil, fmt.Errorf("writing data to contentd store: %w", err) + } + + labels := getVMLabels(microvm) + err = writer.Commit(namespaceCtx, 0, "", content.WithLabels(labels)) + if err != nil { + return nil, fmt.Errorf("committing content to store: %w", err) + } + + return microvm, nil +} + +// Get will get the microvm spec with the given name/namespace from the containerd content store. +func (r *containerdRepo) Get(ctx context.Context, name, namespace string) (*models.MicroVM, error) { + mu := r.getMutex(name) + mu.RLock() + defer mu.RUnlock() + + namespaceCtx := namespaces.WithNamespace(ctx, namespace) + + digest, err := r.findLatestDigestForSpec(namespaceCtx, name) + if err != nil { + return nil, fmt.Errorf("finding content in store: %w", err) + } + if digest == nil { + return nil, errSpecNotFound{name: name, namespace: namespace} + } + + return r.getWithDigest(namespaceCtx, digest) +} + +// GetAll will get a list of microvm details from the containerd content store. +func (r *containerdRepo) GetAll(ctx context.Context, namespace string) ([]*models.MicroVM, error) { + namespaceCtx := namespaces.WithNamespace(ctx, namespace) + store := r.client.ContentStore() + + // NOTE: this seems redundant as we have the namespace based context + nsLabelFilter := labelFilter(NamespaceLabel, namespace) + + versions := map[string]int{} + digests := map[string]*digest.Digest{} + err := store.Walk(namespaceCtx, func(i content.Info) error { + name := i.Labels[IDLabel] + version, err := strconv.Atoi(i.Labels[VersionLabel]) + if err != nil { + return fmt.Errorf("parsing version number: %w", err) + } + + high, ok := versions[name] + if !ok { + high = -1 + } + + if version > high { + versions[name] = version + digests[name] = &i.Digest + } + + return nil + }, nsLabelFilter) + if err != nil { + return nil, fmt.Errorf("walking content store: %w", err) + } + + items := []*models.MicroVM{} + for _, d := range digests { + vm, getErr := r.getWithDigest(namespaceCtx, d) + if getErr != nil { + return nil, fmt.Errorf("getting microvm spec: %w", getErr) + } + + items = append(items, vm) + } + + return items, nil +} + +// Delete will delete the supplied microvm details from the containerd content store. +func (r *containerdRepo) Delete(ctx context.Context, microvm *models.MicroVM) error { + mu := r.getMutex(microvm.ID) + mu.Lock() + defer mu.Unlock() + + namespaceCtx := namespaces.WithNamespace(ctx, microvm.Namespace) + store := r.client.ContentStore() + + digests, err := r.findAllDigestForSpec(namespaceCtx, microvm.ID) + if err != nil { + return fmt.Errorf("finding digests for %s: %w", microvm.ID, err) + } + if len(digests) == 0 { + // Ignore not found + return nil + } + + for _, d := range digests { + if err := store.Delete(namespaceCtx, *d); err != nil { + return fmt.Errorf("deleting content %s from content store: %w", d.String(), err) + } + } + + return nil +} + +// Exists checks to see if the microvm spec exists in the containerd content store. +func (r *containerdRepo) Exists(ctx context.Context, name, namespace string) (bool, error) { + mu := r.getMutex(name) + mu.RLock() + defer mu.RUnlock() + + namespaceCtx := namespaces.WithNamespace(ctx, namespace) + + digest, err := r.findLatestDigestForSpec(namespaceCtx, name) + if err != nil { + return false, fmt.Errorf("finding digest for %s/%s: %w", name, namespace, err) + } + if digest == nil { + return false, nil + } + + return true, nil +} + +func (r *containerdRepo) getWithDigest(ctx context.Context, metadigest *digest.Digest) (*models.MicroVM, error) { + readData, err := content.ReadBlob(ctx, r.client.ContentStore(), v1.Descriptor{ + Digest: *metadigest, + }) + if err != nil { + return nil, fmt.Errorf("reading content %s: %w", metadigest, ErrFailedReadingContent) + } + + microvm := &models.MicroVM{} + err = json.Unmarshal(readData, microvm) + if err != nil { + return nil, fmt.Errorf("unmarshalling json content to microvm: %w", err) + } + + return microvm, nil +} + +func (r *containerdRepo) findLatestDigestForSpec(ctx context.Context, name string) (*digest.Digest, error) { + idLabelFilter := labelFilter(IDLabel, name) + store := r.client.ContentStore() + + var digest *digest.Digest + highestVersion := 0 + + err := store.Walk(ctx, func(i content.Info) error { + version, err := strconv.Atoi(i.Labels[VersionLabel]) + if err != nil { + return fmt.Errorf("parsing version number: %w", err) + } + if version > highestVersion { + digest = &i.Digest + highestVersion = version + } + + return nil + }, idLabelFilter) + if err != nil { + return nil, fmt.Errorf("walking content store for %s: %w", name, err) + } + + return digest, nil +} + +func (r *containerdRepo) findAllDigestForSpec(ctx context.Context, name string) ([]*digest.Digest, error) { + idLabelFilter := labelFilter(IDLabel, name) + store := r.client.ContentStore() + + digests := []*digest.Digest{} + err := store.Walk(ctx, func(i content.Info) error { + digests = append(digests, &i.Digest) + + return nil + }, idLabelFilter) + if err != nil { + return nil, fmt.Errorf("walking content store for %s: %w", name, err) + } + + return digests, nil +} + +func (r *containerdRepo) getMutex(name string) *sync.RWMutex { + r.locksMu.Lock() + defer r.locksMu.Unlock() + + namedMu, ok := r.locks[name] + if ok { + return namedMu + } + + mu := &sync.RWMutex{} + r.locks[name] = mu + + return mu +} + +func getVMLabels(microvm *models.MicroVM) map[string]string { + labels := map[string]string{ + IDLabel: microvm.ID, + NamespaceLabel: microvm.Namespace, + TypeLabel: "microvm", + VersionLabel: strconv.Itoa(microvm.Version), + } + + return labels +} diff --git a/infrastructure/containerd/repo_test.go b/infrastructure/containerd/repo_test.go new file mode 100644 index 00000000..b371e21c --- /dev/null +++ b/infrastructure/containerd/repo_test.go @@ -0,0 +1,73 @@ +package containerd_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + "github.com/weaveworks/reignite/core/models" + "github.com/weaveworks/reignite/infrastructure/containerd" +) + +func TestMicroVMRepo_Integration(t *testing.T) { + if !runContainerDTests() { + t.Skip("skipping containerd microvm repo integration test") + } + + RegisterTestingT(t) + + client, ctx := testCreateClient(t) + + repo := containerd.NewMicroVMRepoWithClient(client) + exists, err := repo.Exists(ctx, testOwnerName, testOwnerNamespace) + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeFalse()) + + testVm := makeSpec(testOwnerName, testOwnerNamespace) + savedVM, err := repo.Save(ctx, testVm) + Expect(err).NotTo(HaveOccurred()) + Expect(savedVM).NotTo(BeNil()) + Expect(savedVM.Version).To(Equal(2)) + + testVm.Spec.VCPU = 2 + savedVM, err = repo.Save(ctx, testVm) + Expect(err).NotTo(HaveOccurred()) + Expect(savedVM).NotTo(BeNil()) + Expect(savedVM.Version).To(Equal(3)) + + exists, err = repo.Exists(ctx, testOwnerName, testOwnerNamespace) + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeTrue()) + + gotVM, err := repo.Get(ctx, testOwnerName, testOwnerNamespace) + Expect(err).NotTo(HaveOccurred()) + Expect(gotVM).NotTo(BeNil()) + Expect(savedVM.Version).To(Equal(3)) + + all, err := repo.GetAll(ctx, testOwnerNamespace) + Expect(err).NotTo(HaveOccurred()) + Expect(len(all)).To(Equal(1)) + + err = repo.Delete(ctx, testVm) + Expect(err).NotTo(HaveOccurred()) + + exists, err = repo.Exists(ctx, testOwnerName, testOwnerNamespace) + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeFalse()) + + exists, err = repo.Exists(ctx, testOwnerName, testOwnerNamespace) + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeFalse()) + + _, err = repo.Get(ctx, testOwnerName, testOwnerNamespace) + Expect(err).To(HaveOccurred()) +} + +func makeSpec(name, namespace string) *models.MicroVM { + return &models.MicroVM{ + ID: name, + Namespace: namespace, + Version: 1, + Spec: models.MicroVMSpec{}, + } +} diff --git a/infrastructure/containerd/snapshot.go b/infrastructure/containerd/snapshot.go new file mode 100644 index 00000000..ef94b45d --- /dev/null +++ b/infrastructure/containerd/snapshot.go @@ -0,0 +1,28 @@ +package containerd + +import ( + "context" + "fmt" + + "github.com/containerd/containerd/snapshots" +) + +func snapshotKey(ownerName string) string { + return fmt.Sprintf("reignite/%s", ownerName) +} + +func snapshotExists(ctx context.Context, key string, ss snapshots.Snapshotter) (bool, error) { + snapshotExists := false + err := ss.Walk(ctx, func(walkCtx context.Context, info snapshots.Info) error { + if info.Name == key { + snapshotExists = true + } + + return nil + }) + if err != nil { + return false, fmt.Errorf("walking snapshots: %w", err) + } + + return snapshotExists, nil +} diff --git a/infrastructure/containerd/testdata/config.toml b/infrastructure/containerd/testdata/config.toml new file mode 100644 index 00000000..d751ed6e --- /dev/null +++ b/infrastructure/containerd/testdata/config.toml @@ -0,0 +1,5 @@ +disabled_plugins = ["cri"] + +[plugins] + [plugins.linux] + shim_debug = true \ No newline at end of file diff --git a/infrastructure/providers/microvm/firecracker/config.go b/infrastructure/firecracker/config.go similarity index 100% rename from infrastructure/providers/microvm/firecracker/config.go rename to infrastructure/firecracker/config.go diff --git a/infrastructure/providers/microvm/firecracker/errors.go b/infrastructure/firecracker/errors.go similarity index 100% rename from infrastructure/providers/microvm/firecracker/errors.go rename to infrastructure/firecracker/errors.go diff --git a/infrastructure/providers/microvm/firecracker/flags.go b/infrastructure/firecracker/flags.go similarity index 100% rename from infrastructure/providers/microvm/firecracker/flags.go rename to infrastructure/firecracker/flags.go diff --git a/infrastructure/providers/microvm/firecracker/provider.go b/infrastructure/firecracker/provider.go similarity index 99% rename from infrastructure/providers/microvm/firecracker/provider.go rename to infrastructure/firecracker/provider.go index acc99342..5b6a78c8 100644 --- a/infrastructure/providers/microvm/firecracker/provider.go +++ b/infrastructure/firecracker/provider.go @@ -12,7 +12,7 @@ import ( "github.com/weaveworks/reignite/pkg/log" ) -// Config represents the configuration options for the Firecracker provider. +// Config represents the configuration options for the Firecracker infrastructure. type Config struct { // FirecrackerBin is the firecracker binary to use. FirecrackerBin string diff --git a/infrastructure/services/microvmgrpc/convert.go b/infrastructure/grpc/convert.go similarity index 99% rename from infrastructure/services/microvmgrpc/convert.go rename to infrastructure/grpc/convert.go index 296f39be..84d4da5f 100644 --- a/infrastructure/services/microvmgrpc/convert.go +++ b/infrastructure/grpc/convert.go @@ -1,4 +1,4 @@ -package microvmgrpc +package grpc import ( "github.com/weaveworks/reignite/api/types" diff --git a/infrastructure/services/microvmgrpc/server.go b/infrastructure/grpc/server.go similarity index 99% rename from infrastructure/services/microvmgrpc/server.go rename to infrastructure/grpc/server.go index 8a737381..2d6944c0 100644 --- a/infrastructure/services/microvmgrpc/server.go +++ b/infrastructure/grpc/server.go @@ -1,4 +1,4 @@ -package microvmgrpc +package grpc import ( "context" diff --git a/infrastructure/mock/gen.go b/infrastructure/mock/gen.go new file mode 100644 index 00000000..d59b5e18 --- /dev/null +++ b/infrastructure/mock/gen.go @@ -0,0 +1,3 @@ +package mock + +//go:generate ../../hack/tools/bin/mockgen -destination mock.go -package mock github.com/weaveworks/reignite/core/ports MicroVMProvider,MicroVMRepository,EventService,IDService,ImageService diff --git a/infrastructure/mock/mock.go b/infrastructure/mock/mock.go new file mode 100644 index 00000000..97de8101 --- /dev/null +++ b/infrastructure/mock/mock.go @@ -0,0 +1,403 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/weaveworks/reignite/core/ports (interfaces: MicroVMProvider,MicroVMRepository,EventService,IDService,ImageService) + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + models "github.com/weaveworks/reignite/core/models" + ports "github.com/weaveworks/reignite/core/ports" +) + +// MockMicroVMProvider is a mock of MicroVMProvider interface. +type MockMicroVMProvider struct { + ctrl *gomock.Controller + recorder *MockMicroVMProviderMockRecorder +} + +// MockMicroVMProviderMockRecorder is the mock recorder for MockMicroVMProvider. +type MockMicroVMProviderMockRecorder struct { + mock *MockMicroVMProvider +} + +// NewMockMicroVMProvider creates a new mock instance. +func NewMockMicroVMProvider(ctrl *gomock.Controller) *MockMicroVMProvider { + mock := &MockMicroVMProvider{ctrl: ctrl} + mock.recorder = &MockMicroVMProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMicroVMProvider) EXPECT() *MockMicroVMProviderMockRecorder { + return m.recorder +} + +// Capabilities mocks base method. +func (m *MockMicroVMProvider) Capabilities() models.Capabilities { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Capabilities") + ret0, _ := ret[0].(models.Capabilities) + return ret0 +} + +// Capabilities indicates an expected call of Capabilities. +func (mr *MockMicroVMProviderMockRecorder) Capabilities() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capabilities", reflect.TypeOf((*MockMicroVMProvider)(nil).Capabilities)) +} + +// CreateVM mocks base method. +func (m *MockMicroVMProvider) CreateVM(arg0 context.Context, arg1 *models.MicroVM) (*models.MicroVM, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateVM", arg0, arg1) + ret0, _ := ret[0].(*models.MicroVM) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateVM indicates an expected call of CreateVM. +func (mr *MockMicroVMProviderMockRecorder) CreateVM(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVM", reflect.TypeOf((*MockMicroVMProvider)(nil).CreateVM), arg0, arg1) +} + +// DeleteVM mocks base method. +func (m *MockMicroVMProvider) DeleteVM(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteVM", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteVM indicates an expected call of DeleteVM. +func (mr *MockMicroVMProviderMockRecorder) DeleteVM(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVM", reflect.TypeOf((*MockMicroVMProvider)(nil).DeleteVM), arg0, arg1) +} + +// ListVMs mocks base method. +func (m *MockMicroVMProvider) ListVMs(arg0 context.Context, arg1 int) ([]*models.MicroVM, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListVMs", arg0, arg1) + ret0, _ := ret[0].([]*models.MicroVM) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListVMs indicates an expected call of ListVMs. +func (mr *MockMicroVMProviderMockRecorder) ListVMs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVMs", reflect.TypeOf((*MockMicroVMProvider)(nil).ListVMs), arg0, arg1) +} + +// PauseVM mocks base method. +func (m *MockMicroVMProvider) PauseVM(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PauseVM", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// PauseVM indicates an expected call of PauseVM. +func (mr *MockMicroVMProviderMockRecorder) PauseVM(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PauseVM", reflect.TypeOf((*MockMicroVMProvider)(nil).PauseVM), arg0, arg1) +} + +// ResumeVM mocks base method. +func (m *MockMicroVMProvider) ResumeVM(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ResumeVM", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ResumeVM indicates an expected call of ResumeVM. +func (mr *MockMicroVMProviderMockRecorder) ResumeVM(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResumeVM", reflect.TypeOf((*MockMicroVMProvider)(nil).ResumeVM), arg0, arg1) +} + +// StartVM mocks base method. +func (m *MockMicroVMProvider) StartVM(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartVM", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// StartVM indicates an expected call of StartVM. +func (mr *MockMicroVMProviderMockRecorder) StartVM(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartVM", reflect.TypeOf((*MockMicroVMProvider)(nil).StartVM), arg0, arg1) +} + +// StopVM mocks base method. +func (m *MockMicroVMProvider) StopVM(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StopVM", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// StopVM indicates an expected call of StopVM. +func (mr *MockMicroVMProviderMockRecorder) StopVM(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopVM", reflect.TypeOf((*MockMicroVMProvider)(nil).StopVM), arg0, arg1) +} + +// MockMicroVMRepository is a mock of MicroVMRepository interface. +type MockMicroVMRepository struct { + ctrl *gomock.Controller + recorder *MockMicroVMRepositoryMockRecorder +} + +// MockMicroVMRepositoryMockRecorder is the mock recorder for MockMicroVMRepository. +type MockMicroVMRepositoryMockRecorder struct { + mock *MockMicroVMRepository +} + +// NewMockMicroVMRepository creates a new mock instance. +func NewMockMicroVMRepository(ctrl *gomock.Controller) *MockMicroVMRepository { + mock := &MockMicroVMRepository{ctrl: ctrl} + mock.recorder = &MockMicroVMRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMicroVMRepository) EXPECT() *MockMicroVMRepositoryMockRecorder { + return m.recorder +} + +// Delete mocks base method. +func (m *MockMicroVMRepository) Delete(arg0 context.Context, arg1 *models.MicroVM) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockMicroVMRepositoryMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMicroVMRepository)(nil).Delete), arg0, arg1) +} + +// Exists mocks base method. +func (m *MockMicroVMRepository) Exists(arg0 context.Context, arg1, arg2 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exists", arg0, arg1, arg2) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Exists indicates an expected call of Exists. +func (mr *MockMicroVMRepositoryMockRecorder) Exists(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockMicroVMRepository)(nil).Exists), arg0, arg1, arg2) +} + +// Get mocks base method. +func (m *MockMicroVMRepository) Get(arg0 context.Context, arg1, arg2 string) (*models.MicroVM, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) + ret0, _ := ret[0].(*models.MicroVM) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockMicroVMRepositoryMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMicroVMRepository)(nil).Get), arg0, arg1, arg2) +} + +// GetAll mocks base method. +func (m *MockMicroVMRepository) GetAll(arg0 context.Context, arg1 string) ([]*models.MicroVM, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAll", arg0, arg1) + ret0, _ := ret[0].([]*models.MicroVM) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAll indicates an expected call of GetAll. +func (mr *MockMicroVMRepositoryMockRecorder) GetAll(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockMicroVMRepository)(nil).GetAll), arg0, arg1) +} + +// Save mocks base method. +func (m *MockMicroVMRepository) Save(arg0 context.Context, arg1 *models.MicroVM) (*models.MicroVM, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Save", arg0, arg1) + ret0, _ := ret[0].(*models.MicroVM) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Save indicates an expected call of Save. +func (mr *MockMicroVMRepositoryMockRecorder) Save(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockMicroVMRepository)(nil).Save), arg0, arg1) +} + +// MockEventService is a mock of EventService interface. +type MockEventService struct { + ctrl *gomock.Controller + recorder *MockEventServiceMockRecorder +} + +// MockEventServiceMockRecorder is the mock recorder for MockEventService. +type MockEventServiceMockRecorder struct { + mock *MockEventService +} + +// NewMockEventService creates a new mock instance. +func NewMockEventService(ctrl *gomock.Controller) *MockEventService { + mock := &MockEventService{ctrl: ctrl} + mock.recorder = &MockEventServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEventService) EXPECT() *MockEventServiceMockRecorder { + return m.recorder +} + +// CreateTopic mocks base method. +func (m *MockEventService) CreateTopic(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateTopic", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateTopic indicates an expected call of CreateTopic. +func (mr *MockEventServiceMockRecorder) CreateTopic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTopic", reflect.TypeOf((*MockEventService)(nil).CreateTopic), arg0, arg1) +} + +// Publish mocks base method. +func (m *MockEventService) Publish(arg0 context.Context, arg1 string, arg2 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Publish", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Publish indicates an expected call of Publish. +func (mr *MockEventServiceMockRecorder) Publish(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockEventService)(nil).Publish), arg0, arg1, arg2) +} + +// Subscribe mocks base method. +func (m *MockEventService) Subscribe(arg0 context.Context, arg1 string, arg2 ports.EventHandlers) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Subscribe", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Subscribe indicates an expected call of Subscribe. +func (mr *MockEventServiceMockRecorder) Subscribe(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockEventService)(nil).Subscribe), arg0, arg1, arg2) +} + +// MockIDService is a mock of IDService interface. +type MockIDService struct { + ctrl *gomock.Controller + recorder *MockIDServiceMockRecorder +} + +// MockIDServiceMockRecorder is the mock recorder for MockIDService. +type MockIDServiceMockRecorder struct { + mock *MockIDService +} + +// NewMockIDService creates a new mock instance. +func NewMockIDService(ctrl *gomock.Controller) *MockIDService { + mock := &MockIDService{ctrl: ctrl} + mock.recorder = &MockIDServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIDService) EXPECT() *MockIDServiceMockRecorder { + return m.recorder +} + +// GenerateRandom mocks base method. +func (m *MockIDService) GenerateRandom() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GenerateRandom") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GenerateRandom indicates an expected call of GenerateRandom. +func (mr *MockIDServiceMockRecorder) GenerateRandom() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateRandom", reflect.TypeOf((*MockIDService)(nil).GenerateRandom)) +} + +// MockImageService is a mock of ImageService interface. +type MockImageService struct { + ctrl *gomock.Controller + recorder *MockImageServiceMockRecorder +} + +// MockImageServiceMockRecorder is the mock recorder for MockImageService. +type MockImageServiceMockRecorder struct { + mock *MockImageService +} + +// NewMockImageService creates a new mock instance. +func NewMockImageService(ctrl *gomock.Controller) *MockImageService { + mock := &MockImageService{ctrl: ctrl} + mock.recorder = &MockImageServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockImageService) EXPECT() *MockImageServiceMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockImageService) Get(arg0 context.Context, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockImageServiceMockRecorder) Get(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockImageService)(nil).Get), arg0, arg1, arg2, arg3) +} + +// GetAndMount mocks base method. +func (m *MockImageService) GetAndMount(arg0 context.Context, arg1, arg2, arg3 string) ([]models.Mount, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAndMount", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]models.Mount) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAndMount indicates an expected call of GetAndMount. +func (mr *MockImageServiceMockRecorder) GetAndMount(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAndMount", reflect.TypeOf((*MockImageService)(nil).GetAndMount), arg0, arg1, arg2, arg3) +} diff --git a/infrastructure/providers/microvm/mock/gen.go b/infrastructure/providers/microvm/mock/gen.go deleted file mode 100644 index 766b9be6..00000000 --- a/infrastructure/providers/microvm/mock/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -package mock - -//go:generate ../../../../hack/tools/bin/mockgen -destination mock.go -package mock github.com/weaveworks/reignite/core/ports MicroVMProvider diff --git a/infrastructure/providers/microvm/mock/mock.go b/infrastructure/providers/microvm/mock/mock.go deleted file mode 100644 index e1df3169..00000000 --- a/infrastructure/providers/microvm/mock/mock.go +++ /dev/null @@ -1,150 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/weaveworks/reignite/core/ports (interfaces: MicroVMProvider) - -// Package mock is a generated GoMock package. -package mock - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - models "github.com/weaveworks/reignite/core/models" -) - -// MockMicroVMProvider is a mock of MicroVMProvider interface. -type MockMicroVMProvider struct { - ctrl *gomock.Controller - recorder *MockMicroVMProviderMockRecorder -} - -// MockMicroVMProviderMockRecorder is the mock recorder for MockMicroVMProvider. -type MockMicroVMProviderMockRecorder struct { - mock *MockMicroVMProvider -} - -// NewMockMicroVMProvider creates a new mock instance. -func NewMockMicroVMProvider(ctrl *gomock.Controller) *MockMicroVMProvider { - mock := &MockMicroVMProvider{ctrl: ctrl} - mock.recorder = &MockMicroVMProviderMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockMicroVMProvider) EXPECT() *MockMicroVMProviderMockRecorder { - return m.recorder -} - -// Capabilities mocks base method. -func (m *MockMicroVMProvider) Capabilities() models.Capabilities { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Capabilities") - ret0, _ := ret[0].(models.Capabilities) - return ret0 -} - -// Capabilities indicates an expected call of Capabilities. -func (mr *MockMicroVMProviderMockRecorder) Capabilities() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capabilities", reflect.TypeOf((*MockMicroVMProvider)(nil).Capabilities)) -} - -// CreateVM mocks base method. -func (m *MockMicroVMProvider) CreateVM(arg0 context.Context, arg1 *models.MicroVM) (*models.MicroVM, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateVM", arg0, arg1) - ret0, _ := ret[0].(*models.MicroVM) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateVM indicates an expected call of CreateVM. -func (mr *MockMicroVMProviderMockRecorder) CreateVM(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVM", reflect.TypeOf((*MockMicroVMProvider)(nil).CreateVM), arg0, arg1) -} - -// DeleteVM mocks base method. -func (m *MockMicroVMProvider) DeleteVM(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteVM", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteVM indicates an expected call of DeleteVM. -func (mr *MockMicroVMProviderMockRecorder) DeleteVM(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVM", reflect.TypeOf((*MockMicroVMProvider)(nil).DeleteVM), arg0, arg1) -} - -// ListVMs mocks base method. -func (m *MockMicroVMProvider) ListVMs(arg0 context.Context, arg1 int) ([]*models.MicroVM, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListVMs", arg0, arg1) - ret0, _ := ret[0].([]*models.MicroVM) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListVMs indicates an expected call of ListVMs. -func (mr *MockMicroVMProviderMockRecorder) ListVMs(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVMs", reflect.TypeOf((*MockMicroVMProvider)(nil).ListVMs), arg0, arg1) -} - -// PauseVM mocks base method. -func (m *MockMicroVMProvider) PauseVM(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PauseVM", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// PauseVM indicates an expected call of PauseVM. -func (mr *MockMicroVMProviderMockRecorder) PauseVM(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PauseVM", reflect.TypeOf((*MockMicroVMProvider)(nil).PauseVM), arg0, arg1) -} - -// ResumeVM mocks base method. -func (m *MockMicroVMProvider) ResumeVM(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResumeVM", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// ResumeVM indicates an expected call of ResumeVM. -func (mr *MockMicroVMProviderMockRecorder) ResumeVM(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResumeVM", reflect.TypeOf((*MockMicroVMProvider)(nil).ResumeVM), arg0, arg1) -} - -// StartVM mocks base method. -func (m *MockMicroVMProvider) StartVM(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StartVM", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// StartVM indicates an expected call of StartVM. -func (mr *MockMicroVMProviderMockRecorder) StartVM(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartVM", reflect.TypeOf((*MockMicroVMProvider)(nil).StartVM), arg0, arg1) -} - -// StopVM mocks base method. -func (m *MockMicroVMProvider) StopVM(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StopVM", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// StopVM indicates an expected call of StopVM. -func (mr *MockMicroVMProviderMockRecorder) StopVM(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopVM", reflect.TypeOf((*MockMicroVMProvider)(nil).StopVM), arg0, arg1) -} diff --git a/infrastructure/repositories/microvm/containerd/microvm.go b/infrastructure/repositories/microvm/containerd/microvm.go deleted file mode 100644 index facf8b62..00000000 --- a/infrastructure/repositories/microvm/containerd/microvm.go +++ /dev/null @@ -1,225 +0,0 @@ -package containerd - -import ( - "context" - "encoding/json" - "fmt" - "sync" - - "github.com/containerd/containerd/content" - "github.com/containerd/containerd/namespaces" - "github.com/opencontainers/go-digest" - v1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/weaveworks/reignite/core/models" - "github.com/weaveworks/reignite/core/ports" - "github.com/weaveworks/reignite/pkg/defaults" -) - -var ( - // IDLabel is the name of the containerd content store label used for the microvm identifier. - IDLabel = fmt.Sprintf("%s/vmid", defaults.Domain) - // NamespaceLabel is the name of the containerd content store label used for the microvm namespace. - NamespaceLabel = fmt.Sprintf("%s/ns", defaults.Domain) - // TypeLabel is the name of the containerd content store label used to denote the type of content. - TypeLabel = fmt.Sprintf("%s/type", defaults.Domain) -) - -// New will create a new containerd backed microvm repository with the supplied store. -func New(store content.Store) ports.MicroVMRepository { - return &containerdRepo{ - store: store, - locks: map[string]*sync.RWMutex{}, - } -} - -type containerdRepo struct { - store content.Store - - locks map[string]*sync.RWMutex - locksMu sync.Mutex -} - -// Save will save the supplied microvm spec to the containred content store. -func (r *containerdRepo) Save(ctx context.Context, microvm *models.MicroVM) (*models.MicroVM, error) { - mu := r.getMutex(microvm.ID) - mu.Lock() - defer mu.Unlock() - - namespaceCtx := namespaces.WithNamespace(ctx, defaults.ContainerdNamespace) - - microvm.Version++ - - refName := fmt.Sprintf("%s/%s", microvm.Namespace, microvm.ID) - writer, err := r.store.Writer(namespaceCtx, content.WithRef(refName)) - if err != nil { - return nil, fmt.Errorf("getting containerd writer: %w", err) - } - - data, err := json.Marshal(microvm) - if err != nil { - return nil, fmt.Errorf("marshalling microvm to yaml: %w", err) - } - - _, err = writer.Write(data) - if err != nil { - return nil, fmt.Errorf("writing data to contentd store: %w", err) - } - - labels := getVMLabels(microvm) - err = writer.Commit(namespaceCtx, 0, "", content.WithLabels(labels)) - if err != nil { - return nil, fmt.Errorf("committing content to store: %w", err) - } - - return microvm, nil -} - -// Get will get the microvm spec with the given name/namespace from the containerd content store. -func (r *containerdRepo) Get(ctx context.Context, name, namespace string) (*models.MicroVM, error) { - mu := r.getMutex(name) - mu.RLock() - defer mu.RUnlock() - - namespaceCtx := namespaces.WithNamespace(ctx, defaults.ContainerdNamespace) - - metadigest, err := r.findDigestForSpec(namespaceCtx, name) - if err != nil { - return nil, fmt.Errorf("walking content store: %w", err) - } - if metadigest == nil { - return nil, errSpecNotFound{name: name, namespace: namespace} - } - - return r.getWithDigest(namespaceCtx, metadigest) -} - -// GetAll will get a list of microvm details from the containerd content store. -func (r *containerdRepo) GetAll(ctx context.Context, namespace string) ([]*models.MicroVM, error) { - namespaceCtx := namespaces.WithNamespace(ctx, defaults.ContainerdNamespace) - - items := []*models.MicroVM{} - - nsLabelFilter := fmt.Sprintf("labels.\"%s\"==\"%s\"", NamespaceLabel, namespace) - - err := r.store.Walk(namespaceCtx, func(i content.Info) error { - vm, getErr := r.getWithDigest(namespaceCtx, &i.Digest) - if getErr != nil { - return fmt.Errorf("getting microvm spec: %w", getErr) - } - - items = append(items, vm) - - return nil - }, nsLabelFilter) - if err != nil { - return nil, fmt.Errorf("walking content store: %w", err) - } - - return items, nil -} - -// Delete will delete the supplied microvm details from the containerd content store. -func (r *containerdRepo) Delete(ctx context.Context, microvm *models.MicroVM) error { - mu := r.getMutex(microvm.ID) - mu.Lock() - defer mu.Unlock() - - namespaceCtx := namespaces.WithNamespace(ctx, defaults.ContainerdNamespace) - - metadigest, err := r.findDigestForSpec(namespaceCtx, microvm.ID) - if err != nil { - return fmt.Errorf("finding digest for %s: %w", microvm.ID, err) - } - if metadigest == nil { - // Ignore not found - return nil - } - - if err := r.store.Delete(namespaceCtx, *metadigest); err != nil { - return fmt.Errorf("deleting content %s from content store: %w", metadigest.String(), err) - } - - return nil -} - -// Exists checks to see if the microvm spec exists in the containerd content store. -func (r *containerdRepo) Exists(ctx context.Context, name, namespace string) (bool, error) { - mu := r.getMutex(name) - mu.RLock() - defer mu.RUnlock() - - namespaceCtx := namespaces.WithNamespace(ctx, defaults.ContainerdNamespace) - - metadigest, err := r.findDigestForSpec(namespaceCtx, name) - if err != nil { - return false, fmt.Errorf("finding digest for %s/%s: %w", name, namespace, err) - } - if metadigest == nil { - return false, nil - } - - return true, nil -} - -func (r *containerdRepo) getWithDigest(ctx context.Context, metadigest *digest.Digest) (*models.MicroVM, error) { - readData, err := content.ReadBlob(ctx, r.store, v1.Descriptor{ - Digest: *metadigest, - }) - if err != nil { - return nil, fmt.Errorf("reading content %s: %w", metadigest, ErrFailedReadingContent) - } - - microvm := &models.MicroVM{} - err = json.Unmarshal(readData, microvm) - if err != nil { - return nil, fmt.Errorf("unmarshalling json content to microvm: %w", err) - } - - return microvm, nil -} - -func (r *containerdRepo) findDigestForSpec(ctx context.Context, name string) (*digest.Digest, error) { - var metaDigest digest.Digest - - idLabelFilter := fmt.Sprintf("labels.\"%s\"==\"%s\"", IDLabel, name) - - err := r.store.Walk(ctx, func(i content.Info) error { - metaDigest = i.Digest - - return nil - }, idLabelFilter) - if err != nil { - return nil, fmt.Errorf("walking content store for %s: %w", name, err) - } - if metaDigest.String() == "" { - return nil, nil - } - - return &metaDigest, nil -} - -func (r *containerdRepo) getMutex(name string) *sync.RWMutex { - r.locksMu.Lock() - defer r.locksMu.Unlock() - - namedMu, ok := r.locks[name] - if ok { - return namedMu - } - - mu := &sync.RWMutex{} - r.locks[name] = mu - - return mu -} - -func getVMLabels(microvm *models.MicroVM) map[string]string { - labels := map[string]string{ - IDLabel: microvm.ID, - NamespaceLabel: microvm.Namespace, - TypeLabel: "microvm", - } - - return labels -} diff --git a/infrastructure/repositories/microvm/containerd/microvm_test.go b/infrastructure/repositories/microvm/containerd/microvm_test.go deleted file mode 100644 index a201df3b..00000000 --- a/infrastructure/repositories/microvm/containerd/microvm_test.go +++ /dev/null @@ -1,290 +0,0 @@ -package containerd_test - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "testing" - - "github.com/containerd/containerd/content" - "github.com/containerd/containerd/content/local" - . "github.com/onsi/gomega" - "github.com/opencontainers/go-digest" - - "github.com/weaveworks/reignite/core/models" - "github.com/weaveworks/reignite/infrastructure/repositories/microvm/containerd" -) - -func TestMicroVMRepo_SaveAndGet(t *testing.T) { - testCases := []struct { - name string - existingSpecs []*models.MicroVM - specToGet string - expectErr bool - }{ - { - name: "empty", - existingSpecs: []*models.MicroVM{}, - specToGet: "test1", - expectErr: true, - }, - { - name: "has existing entry", - existingSpecs: []*models.MicroVM{ - makeSpec("test1", "ns1"), - makeSpec("test2", "ns1"), - }, - specToGet: "test1", - expectErr: false, - }, - { - name: "existing entries but no matching spec name", - existingSpecs: []*models.MicroVM{ - makeSpec("test1", "ns1"), - makeSpec("test2", "ns1"), - }, - specToGet: "test3", - expectErr: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - RegisterTestingT(t) - - ctx := context.Background() - - store := getLocalContentStore(t) - - repo := containerd.New(store) - - for _, specToAdd := range tc.existingSpecs { - _, saveErr := repo.Save(ctx, specToAdd) - Expect(saveErr).NotTo(HaveOccurred()) - } - - mvm, err := repo.Get(ctx, tc.specToGet, "ns1") - - if tc.expectErr { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - Expect(mvm).ToNot(BeNil()) - Expect(mvm.ID).To(Equal(tc.specToGet)) - } - }) - } -} - -func TestMicroVMRepo_Delete(t *testing.T) { - testCases := []struct { - name string - existingSpecs []*models.MicroVM - specToDelete string - expectErr bool - }{ - { - name: "empty", - existingSpecs: []*models.MicroVM{}, - specToDelete: "test1", - expectErr: false, - }, - { - name: "has existing entry", - existingSpecs: []*models.MicroVM{ - makeSpec("test1", "ns1"), - makeSpec("test2", "ns1"), - }, - specToDelete: "test1", - expectErr: false, - }, - { - name: "existing entries but no matching spec", - existingSpecs: []*models.MicroVM{ - makeSpec("test1", "ns1"), - makeSpec("test2", "ns1"), - }, - specToDelete: "test3", - expectErr: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - RegisterTestingT(t) - - ctx := context.Background() - - store := getLocalContentStore(t) - - repo := containerd.New(store) - - for _, specToAdd := range tc.existingSpecs { - _, saveErr := repo.Save(ctx, specToAdd) - Expect(saveErr).NotTo(HaveOccurred()) - } - - err := repo.Delete(ctx, makeSpec(tc.specToDelete, "ns1")) - - if tc.expectErr { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - } - }) - } -} - -func TestMicroVMRepo_GetAll(t *testing.T) { - testCases := []struct { - name string - existingSpecs []*models.MicroVM - nsToGet string - expectErr bool - expectedNumItems int - }{ - { - name: "empty", - existingSpecs: []*models.MicroVM{}, - nsToGet: "ns1", - expectErr: false, - expectedNumItems: 0, - }, - { - name: "has existing entry", - existingSpecs: []*models.MicroVM{ - makeSpec("test1", "ns1"), - makeSpec("test2", "ns1"), - }, - nsToGet: "ns1", - expectErr: false, - expectedNumItems: 2, - }, - { - name: "different ns - has existing entry", - existingSpecs: []*models.MicroVM{ - makeSpec("test1", "ns1"), - makeSpec("test2", "ns2"), - }, - nsToGet: "ns1", - expectErr: false, - expectedNumItems: 1, - }, - { - name: "existing entries but no matching spec", - existingSpecs: []*models.MicroVM{ - makeSpec("test1", "ns1"), - makeSpec("test2", "ns1"), - }, - nsToGet: "ns2", - expectErr: false, - expectedNumItems: 0, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - RegisterTestingT(t) - - ctx := context.Background() - - store := getLocalContentStore(t) - - repo := containerd.New(store) - - for _, specToAdd := range tc.existingSpecs { - _, saveErr := repo.Save(ctx, specToAdd) - Expect(saveErr).NotTo(HaveOccurred()) - } - - items, err := repo.GetAll(ctx, tc.nsToGet) - - if tc.expectErr { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - Expect(items).ToNot(BeNil()) - Expect(len(items)).To(Equal(tc.expectedNumItems)) - } - }) - } -} - -func getLocalContentStore(t *testing.T) content.Store { - contentDir, err := ioutil.TempDir(os.TempDir(), "reignite-store-") - if err != nil { - t.Fatal(err) - } - blobsDir := fmt.Sprintf("%s/blobs", contentDir) - err = os.Mkdir(blobsDir, os.ModePerm) - if err != nil { - t.Fatal(err) - } - - store, err := local.NewLabeledStore(contentDir, newInmemoryLabelStore()) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - _ = os.RemoveAll(contentDir) - }) - - return store -} - -func makeSpec(name, namespace string) *models.MicroVM { - return &models.MicroVM{ - ID: name, - Namespace: namespace, - Version: 1, - Spec: models.MicroVMSpec{}, - } -} - -func newInmemoryLabelStore() *inMemoryLabelStore { - return &inMemoryLabelStore{ - labels: map[string]map[string]string{}, - } -} - -type inMemoryLabelStore struct { - labels map[string]map[string]string -} - -func (ls *inMemoryLabelStore) Get(d digest.Digest) (map[string]string, error) { - labels, ok := ls.labels[d.String()] - if ok { - return labels, nil - } - - return map[string]string{}, nil -} - -func (ls *inMemoryLabelStore) Set(d digest.Digest, labelsToSet map[string]string) error { - ls.labels[d.String()] = labelsToSet - - return nil -} - -func (ls *inMemoryLabelStore) Update(d digest.Digest, labelsToUpdate map[string]string) (map[string]string, error) { - labels, ok := ls.labels[d.String()] - if !ok { - ls.labels[d.String()] = labelsToUpdate - return ls.labels[d.String()], nil - } - - // Add / update any labels - for k, v := range labelsToUpdate { - if v == "" { - delete(labels, k) - } else { - labels[k] = v - } - } - - ls.labels[d.String()] = labels - - return labels, nil -} diff --git a/infrastructure/repositories/microvm/mock/gen.go b/infrastructure/repositories/microvm/mock/gen.go deleted file mode 100644 index ac3906b4..00000000 --- a/infrastructure/repositories/microvm/mock/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -package mock - -//go:generate ../../../../hack/tools/bin/mockgen -destination mock.go -package mock github.com/weaveworks/reignite/core/ports MicroVMRepository diff --git a/infrastructure/repositories/microvm/mock/mock.go b/infrastructure/repositories/microvm/mock/mock.go deleted file mode 100644 index 8474a58b..00000000 --- a/infrastructure/repositories/microvm/mock/mock.go +++ /dev/null @@ -1,110 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/weaveworks/reignite/core/ports (interfaces: MicroVMRepository) - -// Package mock is a generated GoMock package. -package mock - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - models "github.com/weaveworks/reignite/core/models" -) - -// MockMicroVMRepository is a mock of MicroVMRepository interface. -type MockMicroVMRepository struct { - ctrl *gomock.Controller - recorder *MockMicroVMRepositoryMockRecorder -} - -// MockMicroVMRepositoryMockRecorder is the mock recorder for MockMicroVMRepository. -type MockMicroVMRepositoryMockRecorder struct { - mock *MockMicroVMRepository -} - -// NewMockMicroVMRepository creates a new mock instance. -func NewMockMicroVMRepository(ctrl *gomock.Controller) *MockMicroVMRepository { - mock := &MockMicroVMRepository{ctrl: ctrl} - mock.recorder = &MockMicroVMRepositoryMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockMicroVMRepository) EXPECT() *MockMicroVMRepositoryMockRecorder { - return m.recorder -} - -// Delete mocks base method. -func (m *MockMicroVMRepository) Delete(arg0 context.Context, arg1 *models.MicroVM) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delete", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// Delete indicates an expected call of Delete. -func (mr *MockMicroVMRepositoryMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMicroVMRepository)(nil).Delete), arg0, arg1) -} - -// Exists mocks base method. -func (m *MockMicroVMRepository) Exists(arg0 context.Context, arg1, arg2 string) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Exists", arg0, arg1, arg2) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Exists indicates an expected call of Exists. -func (mr *MockMicroVMRepositoryMockRecorder) Exists(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockMicroVMRepository)(nil).Exists), arg0, arg1, arg2) -} - -// Get mocks base method. -func (m *MockMicroVMRepository) Get(arg0 context.Context, arg1, arg2 string) (*models.MicroVM, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) - ret0, _ := ret[0].(*models.MicroVM) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Get indicates an expected call of Get. -func (mr *MockMicroVMRepositoryMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMicroVMRepository)(nil).Get), arg0, arg1, arg2) -} - -// GetAll mocks base method. -func (m *MockMicroVMRepository) GetAll(arg0 context.Context, arg1 string) ([]*models.MicroVM, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAll", arg0, arg1) - ret0, _ := ret[0].([]*models.MicroVM) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetAll indicates an expected call of GetAll. -func (mr *MockMicroVMRepositoryMockRecorder) GetAll(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockMicroVMRepository)(nil).GetAll), arg0, arg1) -} - -// Save mocks base method. -func (m *MockMicroVMRepository) Save(arg0 context.Context, arg1 *models.MicroVM) (*models.MicroVM, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Save", arg0, arg1) - ret0, _ := ret[0].(*models.MicroVM) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Save indicates an expected call of Save. -func (mr *MockMicroVMRepositoryMockRecorder) Save(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockMicroVMRepository)(nil).Save), arg0, arg1) -} diff --git a/infrastructure/services/event/mock/gen.go b/infrastructure/services/event/mock/gen.go deleted file mode 100644 index 50f9ff5d..00000000 --- a/infrastructure/services/event/mock/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -package mock - -//go:generate ../../../../hack/tools/bin/mockgen -destination mock.go -package mock github.com/weaveworks/reignite/core/ports EventService diff --git a/infrastructure/services/event/mock/mock.go b/infrastructure/services/event/mock/mock.go deleted file mode 100644 index aa75ce77..00000000 --- a/infrastructure/services/event/mock/mock.go +++ /dev/null @@ -1,78 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/weaveworks/reignite/core/ports (interfaces: EventService) - -// Package mock is a generated GoMock package. -package mock - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - ports "github.com/weaveworks/reignite/core/ports" -) - -// MockEventService is a mock of EventService interface. -type MockEventService struct { - ctrl *gomock.Controller - recorder *MockEventServiceMockRecorder -} - -// MockEventServiceMockRecorder is the mock recorder for MockEventService. -type MockEventServiceMockRecorder struct { - mock *MockEventService -} - -// NewMockEventService creates a new mock instance. -func NewMockEventService(ctrl *gomock.Controller) *MockEventService { - mock := &MockEventService{ctrl: ctrl} - mock.recorder = &MockEventServiceMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockEventService) EXPECT() *MockEventServiceMockRecorder { - return m.recorder -} - -// CreateTopic mocks base method. -func (m *MockEventService) CreateTopic(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateTopic", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// CreateTopic indicates an expected call of CreateTopic. -func (mr *MockEventServiceMockRecorder) CreateTopic(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTopic", reflect.TypeOf((*MockEventService)(nil).CreateTopic), arg0, arg1) -} - -// Publish mocks base method. -func (m *MockEventService) Publish(arg0 context.Context, arg1 string, arg2 interface{}) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Publish", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// Publish indicates an expected call of Publish. -func (mr *MockEventServiceMockRecorder) Publish(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockEventService)(nil).Publish), arg0, arg1, arg2) -} - -// Subscribe mocks base method. -func (m *MockEventService) Subscribe(arg0 context.Context, arg1 string, arg2 ports.EventHandlers) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Subscribe", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// Subscribe indicates an expected call of Subscribe. -func (mr *MockEventServiceMockRecorder) Subscribe(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockEventService)(nil).Subscribe), arg0, arg1, arg2) -} diff --git a/infrastructure/services/id/mock/gen.go b/infrastructure/services/id/mock/gen.go deleted file mode 100644 index 3c593bae..00000000 --- a/infrastructure/services/id/mock/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -package mock - -//go:generate ../../../../hack/tools/bin/mockgen -destination mock.go -package mock github.com/weaveworks/reignite/core/ports IDService diff --git a/infrastructure/services/id/mock/mock.go b/infrastructure/services/id/mock/mock.go deleted file mode 100644 index 709eddec..00000000 --- a/infrastructure/services/id/mock/mock.go +++ /dev/null @@ -1,49 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/weaveworks/reignite/core/ports (interfaces: IDService) - -// Package mock is a generated GoMock package. -package mock - -import ( - reflect "reflect" - - gomock "github.com/golang/mock/gomock" -) - -// MockIDService is a mock of IDService interface. -type MockIDService struct { - ctrl *gomock.Controller - recorder *MockIDServiceMockRecorder -} - -// MockIDServiceMockRecorder is the mock recorder for MockIDService. -type MockIDServiceMockRecorder struct { - mock *MockIDService -} - -// NewMockIDService creates a new mock instance. -func NewMockIDService(ctrl *gomock.Controller) *MockIDService { - mock := &MockIDService{ctrl: ctrl} - mock.recorder = &MockIDServiceMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockIDService) EXPECT() *MockIDServiceMockRecorder { - return m.recorder -} - -// GenerateRandom mocks base method. -func (m *MockIDService) GenerateRandom() (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GenerateRandom") - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GenerateRandom indicates an expected call of GenerateRandom. -func (mr *MockIDServiceMockRecorder) GenerateRandom() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateRandom", reflect.TypeOf((*MockIDService)(nil).GenerateRandom)) -} diff --git a/infrastructure/services/event/transport/errors.go b/infrastructure/transport/errors.go similarity index 100% rename from infrastructure/services/event/transport/errors.go rename to infrastructure/transport/errors.go diff --git a/infrastructure/services/event/transport/transport.go b/infrastructure/transport/transport.go similarity index 97% rename from infrastructure/services/event/transport/transport.go rename to infrastructure/transport/transport.go index 99aa9d12..b09b1390 100644 --- a/infrastructure/services/event/transport/transport.go +++ b/infrastructure/transport/transport.go @@ -7,9 +7,9 @@ import ( "github.com/vmware/transport-go/bus" "github.com/vmware/transport-go/model" + event "github.com/weaveworks/reignite/core" "github.com/weaveworks/reignite/core/models" "github.com/weaveworks/reignite/core/ports" - "github.com/weaveworks/reignite/infrastructure/services/event" ) // New creates a new event service based on Transport (https://vmware.github.io/transport/). diff --git a/infrastructure/services/event/transport/transport_test.go b/infrastructure/transport/transport_test.go similarity index 98% rename from infrastructure/services/event/transport/transport_test.go rename to infrastructure/transport/transport_test.go index bf0e2407..8412c880 100644 --- a/infrastructure/services/event/transport/transport_test.go +++ b/infrastructure/transport/transport_test.go @@ -9,7 +9,7 @@ import ( "github.com/weaveworks/reignite/core/models" "github.com/weaveworks/reignite/core/ports" - "github.com/weaveworks/reignite/infrastructure/services/event/transport" + "github.com/weaveworks/reignite/infrastructure/transport" ) func TestTransport_SimplePubSub(t *testing.T) { diff --git a/infrastructure/services/id/ulid/ulid.go b/infrastructure/ulid/ulid.go similarity index 100% rename from infrastructure/services/id/ulid/ulid.go rename to infrastructure/ulid/ulid.go diff --git a/infrastructure/services/id/ulid/ulid_test.go b/infrastructure/ulid/ulid_test.go similarity index 100% rename from infrastructure/services/id/ulid/ulid_test.go rename to infrastructure/ulid/ulid_test.go diff --git a/internal/command/run/run.go b/internal/command/run/run.go index d5bec027..7a66edb1 100644 --- a/internal/command/run/run.go +++ b/internal/command/run/run.go @@ -17,11 +17,11 @@ import ( mvmv1 "github.com/weaveworks/reignite/api/services/microvm/v1alpha1" "github.com/weaveworks/reignite/core/application" - "github.com/weaveworks/reignite/infrastructure/providers/microvm/firecracker" - containerd_repo "github.com/weaveworks/reignite/infrastructure/repositories/microvm/containerd" - "github.com/weaveworks/reignite/infrastructure/services/event/transport" - "github.com/weaveworks/reignite/infrastructure/services/id/ulid" - "github.com/weaveworks/reignite/infrastructure/services/microvmgrpc" + containerd_repo "github.com/weaveworks/reignite/infrastructure/containerd" + "github.com/weaveworks/reignite/infrastructure/firecracker" + microvmgrpc "github.com/weaveworks/reignite/infrastructure/grpc" + "github.com/weaveworks/reignite/infrastructure/transport" + "github.com/weaveworks/reignite/infrastructure/ulid" cmdflags "github.com/weaveworks/reignite/internal/command/flags" "github.com/weaveworks/reignite/internal/config" "github.com/weaveworks/reignite/pkg/defaults" @@ -87,12 +87,12 @@ func runServer(ctx context.Context, cfg *config.Config) error { func serveAPI(ctx context.Context, cfg *config.Config) error { logger := log.GetLogger(ctx) - // TODO: Use CI framework to inject these ------- + // TODO: Use DI framework to inject these ------- containerdClient, err := containerd.New(cfg.ContainerdSocketPath) if err != nil { return fmt.Errorf("creating containerd client: %w", err) } - repo := containerd_repo.New(containerdClient.ContentStore()) + repo := containerd_repo.NewMicroVMRepoWithClient(containerdClient) eventSvc := transport.New() if err := eventSvc.CreateTopic(ctx, defaults.TopicMicroVMEvents); err != nil { return fmt.Errorf("creating %s topic: %w", defaults.TopicMicroVMEvents, err) diff --git a/internal/config/config.go b/internal/config/config.go index 21eaddbb..db968f1b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,7 +1,7 @@ package config import ( - "github.com/weaveworks/reignite/infrastructure/providers/microvm/firecracker" + "github.com/weaveworks/reignite/infrastructure/firecracker" "github.com/weaveworks/reignite/pkg/log" ) diff --git a/pkg/defaults/defaults.go b/pkg/defaults/defaults.go index 50b80d96..a882b9b8 100644 --- a/pkg/defaults/defaults.go +++ b/pkg/defaults/defaults.go @@ -10,6 +10,9 @@ const ( // ContainerdSocket is the defaults path for the containerd socket. ContainerdSocket = "/run/containerd/containerd.sock" + // ContainerdSnapshotter is the name of the default snapshotter to use for containerd. + ContainerdSnapshotter = "devmapper" + // FirecrackerBin is the name of the firecracker binary. FirecrackerBin = "firecracker" diff --git a/pkg/planner/actuator_test.go b/pkg/planner/actuator_test.go index 22345682..2ee8aecd 100644 --- a/pkg/planner/actuator_test.go +++ b/pkg/planner/actuator_test.go @@ -7,7 +7,7 @@ import ( . "github.com/onsi/gomega" - "github.com/weaveworks/reignite/infrastructure/services/id/ulid" + "github.com/weaveworks/reignite/infrastructure/ulid" "github.com/weaveworks/reignite/pkg/planner" )