Repository: firecracker-microvm/firecracker-containerd (go)

Issue:
Add proto compile/compare step to CI
Add a check that re-generates the proto files and produces an error if the generated files don't match what's in a given commit.

see: #319 

Pull request description:
Add extra verify steps to buildkite for go.mod/go.sum and proto defs
I noticed our go.mod/go.sum and generated proto defs were a bit out of sync (the proto differences were very minor, just differences in the comments of a .pb.go), so figured it was a good time to add some verification steps to buildkite that check if go.mod/go.sum and the proto defs are all up to date.

Closes #323 

Verified the steps fail as expected w/ a purposely broken commit here: https://buildkite.com/firecracker-microvm/sipsma-firecracker-containerd/builds/861#

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


Patch:
diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml
index 2688669..1907ccf 100644
--- a/.buildkite/pipeline.yml
+++ b/.buildkite/pipeline.yml
@@ -45,6 +45,26 @@ steps:
   - label: 'git log validation'
     command: './.buildkite/logcheck.sh'
 
+  - label: ":protractor: verify proto"
+    agents:
+      queue: "${BUILDKITE_AGENT_META_DATA_QUEUE:-default}"
+    env:
+      DOCKER_IMAGE_TAG: "$BUILDKITE_BUILD_NUMBER"
+    command:
+      - make proto
+      - test -z "$(git status --porcelain)" && exit 0 || git status && echo -e '\nGenerated protobuf code differs from committed, please run "make proto" and commit the updated files.\n' && exit 1
+    timeout_in_minutes: 1
+
+  - label: ":golang: go mod tidy"
+    agents:
+      queue: "${BUILDKITE_AGENT_META_DATA_QUEUE:-default}"
+    env:
+      DOCKER_IMAGE_TAG: "$BUILDKITE_BUILD_NUMBER"
+    command:
+      - make tidy-in-docker
+      - test -z "$(git status --porcelain)" && exit 0 || git status && echo -e '\ngo.mod and/or go.sum differ from committed, please run "go mod tidy" and commit the updated files.\n' && exit 1
+    timeout_in_minutes: 1
+
   - label: ":gear: unit tests"
     agents:
       queue: "${BUILDKITE_AGENT_META_DATA_QUEUE:-default}"
@@ -78,3 +98,4 @@ steps:
     command:
       - make -C examples integ-test TEST_SS=devmapper TEST_POOL=build_${BUILDKITE_BUILD_NUMBER}_example
     timeout_in_minutes: 10
+
diff --git a/Makefile b/Makefile
index 38c0ffe..2defada 100644
--- a/Makefile
+++ b/Makefile
@@ -98,6 +98,9 @@ lint:
 	$(BINPATH)/git-validation -run DCO,short-subject -range HEAD~20..HEAD
 	$(BINPATH)/golangci-lint run
 
+tidy:
+	go mod tidy
+
 deps:
 	curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(BINPATH) v1.21.0
 	$(BINPATH)/golangci-lint --version
@@ -153,7 +156,7 @@ firecracker-containerd-test-image: all-in-docker firecracker runc test-cni-bins
 		--build-arg FIRECRACKER_TARGET=$(FIRECRACKER_TARGET) \
 		--tag $(FIRECRACKER_CONTAINERD_TEST_IMAGE):${DOCKER_IMAGE_TAG} .
 
-.PHONY: all $(SUBDIRS) clean proto deps lint install image test-images firecracker-container-test-image firecracker-containerd-integ-test-image test test-in-docker $(TEST_SUBDIRS) integ-test $(INTEG_TEST_SUBDIRS)
+.PHONY: all $(SUBDIRS) clean proto deps lint install image test-images firecracker-container-test-image firecracker-containerd-integ-test-image test test-in-docker $(TEST_SUBDIRS) integ-test $(INTEG_TEST_SUBDIRS) tidy
 
 ##########################
 # Runtime config
diff --git a/agent/drive_handler.go b/agent/drive_handler.go
index c83d383..aeec3ac 100644
--- a/agent/drive_handler.go
+++ b/agent/drive_handler.go
@@ -177,11 +177,22 @@ func (dh driveHandler) MountDrive(ctx context.Context, req *drivemount.MountDriv
 	logger = logger.WithField("drive_path", drive.Path())
 
 	// Do a basic check that we won't be mounting over any important system directories
-	if err := isSystemDir(req.DestinationPath); err != nil {
-		return nil, err
+	resolvedDest, err := evalAnySymlinks(req.DestinationPath)
+	if err != nil {
+		return nil, errors.Wrapf(err,
+			"failed to evaluate any symlinks in drive mount destination %q", req.DestinationPath)
+	}
+
+	for _, systemDir := range bannedSystemDirs {
+		if isOrUnderDir(resolvedDest, systemDir) {
+			return nil, errors.Errorf(
+				"drive mount destination %q resolves to path %q under banned system directory %q",
+				req.DestinationPath, resolvedDest, systemDir,
+			)
+		}
 	}
 
-	err := os.MkdirAll(req.DestinationPath, 0700)
+	err = os.MkdirAll(req.DestinationPath, 0700)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to create drive mount destination %q", req.DestinationPath)
 	}
@@ -218,40 +229,6 @@ func (dh driveHandler) MountDrive(ctx context.Context, req *drivemount.MountDriv
 		drive.Path(), req.DestinationPath)
 }
 
-func (dh driveHandler) UnmountDrive(ctx context.Context, req *drivemount.UnmountDriveRequest) (*empty.Empty, error) {
-	drive, ok := dh.GetDrive(req.DriveID)
-	if !ok {
-		return nil, fmt.Errorf("drive %q could not be found", req.DriveID)
-	}
-
-	err := mount.Unmount(drive.Path(), 0)
-	if err == nil {
-		return &empty.Empty{}, nil
-	}
-
-	return nil, errors.Errorf("failed to unmount the drive %q",
-		drive.Path())
-}
-
-func isSystemDir(path string) error {
-	resolvedDest, err := evalAnySymlinks(path)
-	if err != nil {
-		return errors.Wrapf(err,
-			"failed to evaluate any symlinks in drive ummount destination %q", path)
-	}
-
-	for _, systemDir := range bannedSystemDirs {
-		if isOrUnderDir(resolvedDest, systemDir) {
-			return errors.Errorf(
-				"drive mount destination %q resolves to path %q under banned system directory %q",
-				path, resolvedDest, systemDir,
-			)
-		}
-	}
-
-	return nil
-}
-
 // evalAnySymlinks is similar to filepath.EvalSymlinks, except it will not return an error if part of the
 // provided path does not exist. It will evaluate symlinks present in the path up to a component that doesn't
 // exist, at which point it will just append the rest of the provided path to what has been resolved so far.
diff --git a/go.mod b/go.mod
index af09a50..f16334b 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/containerd/cgroups v0.0.0-20181105182409-82cb49fc1779 // indirect
 	github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50 // indirect
 	github.com/containerd/containerd v1.3.1
-	github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac
+	github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac // indirect
 	github.com/containerd/fifo v0.0.0-20190816180239-bda0ff6ed73c
 	github.com/containerd/go-runc v0.0.0-20190226155025-7d11b49dc076 // indirect
 	github.com/containerd/ttrpc v0.0.0-20190613183316-1fb3814edf44
@@ -17,7 +17,6 @@ require (
 	github.com/docker/distribution v2.7.1+incompatible // indirect
 	github.com/docker/go-events v0.0.0-20170721190031-9461782956ad // indirect
 	github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 // indirect
-	github.com/docker/go-units v0.3.3
 	github.com/firecracker-microvm/firecracker-go-sdk v0.19.1-0.20191114205152-9e2ff62839b2
 	github.com/go-ole/go-ole v1.2.4 // indirect
 	github.com/godbus/dbus v0.0.0-20181025153459-66d97aec3384 // indirect
@@ -43,7 +42,7 @@ require (
 	github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect
 	github.com/urfave/cli v1.20.0 // indirect
 	github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf
-	go.etcd.io/bbolt v1.3.1-etcd.8
+	go.etcd.io/bbolt v1.3.1-etcd.8 // indirect
 	golang.org/x/sync v0.0.0-20181108010431-42b317875d0f
 	golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
 	google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b // indirect
diff --git a/go.sum b/go.sum
index e353a35..b2bb1f1 100644
--- a/go.sum
+++ b/go.sum
@@ -18,7 +18,6 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
-github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/containerd/cgroups v0.0.0-20181105182409-82cb49fc1779 h1:j1IsLW6/hNZPIyBH1v0hhEeB1WXE2ffhHaqSuXhgknY=
 github.com/containerd/cgroups v0.0.0-20181105182409-82cb49fc1779/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=
@@ -45,7 +44,6 @@ github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmeka
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142 h1:3jFq2xL4ZajGK4aZY8jz+DAF0FHjI51BXjjSwCzS1Dk=
 github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
 github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
 github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
 github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
@@ -149,7 +147,6 @@ github.com/miekg/dns v1.1.16 h1:iMEQ/IVHxPTtx2Q07JP/k4CKRvSjiAZjZ0hnhgYEDmE=
 github.com/miekg/dns v1.1.16/go.mod h1:YNV562EiewvSmpCB6/W4c6yqjK7Z+M/aIS1JHsIVeg8=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
 github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
 github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a h1:KfNOeFvoAssuZLT7IntKZElKwi/5LRuxY71k+t6rfaM=
@@ -162,7 +159,6 @@ github.com/opencontainers/runc v1.0.0-rc9 h1:/k06BMULKF5hidyoZymkoDCzdJzltZpz/UU
 github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runtime-spec v0.1.2-0.20181106065543-31e0d16c1cb7 h1:vg5OQBKq2D0TX7q7loKQBLZ54IUAbUvq1rlwDRdn1gY=
 github.com/opencontainers/runtime-spec v0.1.2-0.20181106065543-31e0d16c1cb7/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
 github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
diff --git a/proto/Makefile b/proto/Makefile
index 32cee12..0ac66eb 100644
--- a/proto/Makefile
+++ b/proto/Makefile
@@ -37,4 +37,4 @@ clean:
 	- $(MAKE) -C service/fccontrol clean
 	- $(MAKE) -C service/drivemount clean
 
-.PHONY: clean proto
+.PHONY: clean proto proto-docker
diff --git a/proto/firecracker.pb.go b/proto/firecracker.pb.go
index 723e77c..203046a 100644
--- a/proto/firecracker.pb.go
+++ b/proto/firecracker.pb.go
@@ -534,11 +534,24 @@ func (m *GetVMMetadataResponse) GetMetadata() string {
 
 type JailerConfig struct {
 	NetNS string `protobuf:"bytes,1,opt,name=NetNS,json=netNS,proto3" json:"NetNS,omitempty"`
-	// The list of CPUs to run the jailed application on. This field can only
-	// be used on numa architectures
+	// List of the physical numbers of the CPUs on which processes in that
+	// cpuset are allowed to execute.  See List Format below for a description
+	// of the format of cpus.
+	//
+	// The CPUs allowed to a cpuset may be changed by writing a new list to its
+	// cpus file.
+	// Taken from http://man7.org/linux/man-pages/man7/cpuset.7.html
+	//
+	// This is formatted as specified in the cpuset man page under "List Format"
+	// http://man7.org/linux/man-pages/man7/cpuset.7.html
 	CPUs string `protobuf:"bytes,2,opt,name=CPUs,json=cPUs,proto3" json:"CPUs,omitempty"`
-	// The list of memory nodes to run with the jailed application on. This
-	// field can only be used on numa architectures
+	// List of memory nodes on which processes in this cpuset are allowed to
+	// allocate memory.  See List Format below for a description of the format
+	// of mems.
+	// Taken from http://man7.org/linux/man-pages/man7/cpuset.7.html
+	//
+	// This is formatted as specified in the cpuset man page under "List Format"
+	// http://man7.org/linux/man-pages/man7/cpuset.7.html
 	Mems                 string   `protobuf:"bytes,3,opt,name=Mems,json=mems,proto3" json:"Mems,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
diff --git a/proto/service/drivemount/drivemount.proto b/proto/service/drivemount/drivemount.proto
index 4451b21..5ebe020 100644
--- a/proto/service/drivemount/drivemount.proto
+++ b/proto/service/drivemount/drivemount.proto
@@ -6,16 +6,11 @@ option go_package = "drivemount";
 
 service DriveMounter {
     rpc MountDrive(MountDriveRequest) returns (google.protobuf.Empty);
-    rpc UnmountDrive(UnmountDriveRequest) returns (google.protobuf.Empty);
 }
 
 message MountDriveRequest {
-     string DriveID = 1;
-     string DestinationPath = 2;
-     string FilesytemType = 3;
-     repeated string Options = 4;
-}
-
-message UnmountDriveRequest {
     string DriveID = 1;
+    string DestinationPath = 2;
+    string FilesytemType = 3;
+    repeated string Options = 4;
 }
\ No newline at end of file
diff --git a/proto/service/drivemount/ttrpc/drivemount.pb.go b/proto/service/drivemount/ttrpc/drivemount.pb.go
index 3107dee..2beb99f 100644
--- a/proto/service/drivemount/ttrpc/drivemount.pb.go
+++ b/proto/service/drivemount/ttrpc/drivemount.pb.go
@@ -69,54 +69,14 @@ func (m *MountDriveRequest) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_MountDriveRequest proto.InternalMessageInfo
 
-type UnmountDriveRequest struct {
-	DriveID              string   `protobuf:"bytes,1,opt,name=DriveID,json=driveID,proto3" json:"DriveID,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *UnmountDriveRequest) Reset()      { *m = UnmountDriveRequest{} }
-func (*UnmountDriveRequest) ProtoMessage() {}
-func (*UnmountDriveRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_48455e9fea9a7da5, []int{1}
-}
-func (m *UnmountDriveRequest) XXX_Unmarshal(b []byte) error {
-	return m.Unmarshal(b)
-}
-func (m *UnmountDriveRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	if deterministic {
-		return xxx_messageInfo_UnmountDriveRequest.Marshal(b, m, deterministic)
-	} else {
-		b = b[:cap(b)]
-		n, err := m.MarshalToSizedBuffer(b)
-		if err != nil {
-			return nil, err
-		}
-		return b[:n], nil
-	}
-}
-func (m *UnmountDriveRequest) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_UnmountDriveRequest.Merge(m, src)
-}
-func (m *UnmountDriveRequest) XXX_Size() int {
-	return m.Size()
-}
-func (m *UnmountDriveRequest) XXX_DiscardUnknown() {
-	xxx_messageInfo_UnmountDriveRequest.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_UnmountDriveRequest proto.InternalMessageInfo
-
 func init() {
 	proto.RegisterType((*MountDriveRequest)(nil), "MountDriveRequest")
-	proto.RegisterType((*UnmountDriveRequest)(nil), "UnmountDriveRequest")
 }
 
 func init() { proto.RegisterFile("drivemount.proto", fileDescriptor_48455e9fea9a7da5) }
 
 var fileDescriptor_48455e9fea9a7da5 = []byte{
-	// 265 bytes of a gzipped FileDescriptorProto
+	// 240 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x29, 0xca, 0x2c,
 	0x4b, 0xcd, 0xcd, 0x2f, 0xcd, 0x2b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x97, 0x92, 0x4e, 0xcf,
 	0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0xf3, 0x92, 0x4a, 0xd3, 0xf4, 0x53, 0x73, 0x0b, 0x4a, 0x2a,
@@ -126,14 +86,12 @@ var fileDescriptor_48455e9fea9a7da5 = []byte{
 	0x2f, 0xb1, 0x24, 0x33, 0x3f, 0x2f, 0x20, 0xb1, 0x24, 0x43, 0x82, 0x09, 0xac, 0x82, 0x3f, 0x05,
 	0x55, 0x58, 0x48, 0x85, 0x8b, 0xd7, 0x2d, 0x33, 0x27, 0xb5, 0xb8, 0xb2, 0x24, 0x35, 0x37, 0xa4,
 	0xb2, 0x20, 0x55, 0x82, 0x19, 0xac, 0x8e, 0x37, 0x0d, 0x59, 0x10, 0x64, 0x93, 0x7f, 0x01, 0x48,
-	0x4f, 0xb1, 0x04, 0x8b, 0x02, 0x33, 0xc8, 0xa6, 0x7c, 0x08, 0x57, 0x49, 0x9f, 0x4b, 0x38, 0x34,
-	0x2f, 0x97, 0x78, 0xa7, 0x19, 0xb5, 0x31, 0x72, 0xf1, 0x80, 0xa5, 0xc0, 0xfe, 0x49, 0x2d, 0x12,
-	0xb2, 0xe0, 0xe2, 0x42, 0x78, 0x4d, 0x48, 0x48, 0x0f, 0xc3, 0x9f, 0x52, 0x62, 0x7a, 0x90, 0xb0,
-	0xd1, 0x83, 0x85, 0x8d, 0x9e, 0x2b, 0x28, 0x6c, 0x84, 0x6c, 0xb8, 0x78, 0x90, 0xed, 0x16, 0x12,
-	0xd1, 0xc3, 0xe2, 0x14, 0x5c, 0xba, 0x9d, 0x54, 0x4e, 0x3c, 0x94, 0x63, 0xb8, 0xf1, 0x50, 0x8e,
-	0xa1, 0xe1, 0x91, 0x1c, 0xe3, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24,
-	0xc7, 0x18, 0xc5, 0x85, 0x88, 0x9c, 0x24, 0x36, 0xb0, 0x2e, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff,
-	0xff, 0x1e, 0xd7, 0x06, 0xe8, 0xb1, 0x01, 0x00, 0x00,
+	0x4f, 0xb1, 0x04, 0x8b, 0x02, 0x33, 0xc8, 0xa6, 0x7c, 0x08, 0xd7, 0xc8, 0x83, 0x8b, 0x07, 0xec,
+	0x06, 0xb0, 0xeb, 0x52, 0x8b, 0x84, 0x2c, 0xb8, 0xb8, 0x10, 0x0e, 0x15, 0x12, 0xd2, 0xc3, 0x70,
+	0xb5, 0x94, 0x98, 0x1e, 0xc4, 0xa7, 0x7a, 0x30, 0x9f, 0xea, 0xb9, 0x82, 0x7c, 0xea, 0xa4, 0x72,
+	0xe2, 0xa1, 0x1c, 0xc3, 0x8d, 0x87, 0x72, 0x0c, 0x0d, 0x8f, 0xe4, 0x18, 0x4f, 0x3c, 0x92, 0x63,
+	0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x28, 0x2e, 0x44, 0x60, 0x25, 0xb1, 0x81,
+	0x75, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xc1, 0xad, 0x30, 0xb3, 0x41, 0x01, 0x00, 0x00,
 }
 
 func (m *MountDriveRequest) Marshal() (dAtA []byte, err error) {
@@ -193,40 +151,6 @@ func (m *MountDriveRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
-func (m *UnmountDriveRequest) Marshal() (dAtA []byte, err error) {
-	size := m.Size()
-	dAtA = make([]byte, size)
-	n, err := m.MarshalToSizedBuffer(dAtA[:size])
-	if err != nil {
-		return nil, err
-	}
-	return dAtA[:n], nil
-}
-
-func (m *UnmountDriveRequest) MarshalTo(dAtA []byte) (int, error) {
-	size := m.Size()
-	return m.MarshalToSizedBuffer(dAtA[:size])
-}
-
-func (m *UnmountDriveRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
-	i := len(dAtA)
-	_ = i
-	var l int
-	_ = l
-	if m.XXX_unrecognized != nil {
-		i -= len(m.XXX_unrecognized)
-		copy(dAtA[i:], m.XXX_unrecognized)
-	}
-	if len(m.DriveID) > 0 {
-		i -= len(m.DriveID)
-		copy(dAtA[i:], m.DriveID)
-		i = encodeVarintDrivemount(dAtA, i, uint64(len(m.DriveID)))
-		i--
-		dAtA[i] = 0xa
-	}
-	return len(dAtA) - i, nil
-}
-
 func encodeVarintDrivemount(dAtA []byte, offset int, v uint64) int {
 	offset -= sovDrivemount(v)
 	base := offset
@@ -268,22 +192,6 @@ func (m *MountDriveRequest) Size() (n int) {
 	return n
 }
 
-func (m *UnmountDriveRequest) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	l = len(m.DriveID)
-	if l > 0 {
-		n += 1 + l + sovDrivemount(uint64(l))
-	}
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
-	}
-	return n
-}
-
 func sovDrivemount(x uint64) (n int) {
 	return (math_bits.Len64(x|1) + 6) / 7
 }
@@ -304,17 +212,6 @@ func (this *MountDriveRequest) String() string {
 	}, "")
 	return s
 }
-func (this *UnmountDriveRequest) String() string {
-	if this == nil {
-		return "nil"
-	}
-	s := strings.Join([]string{`&UnmountDriveRequest{`,
-		`DriveID:` + fmt.Sprintf("%v", this.DriveID) + `,`,
-		`XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`,
-		`}`,
-	}, "")
-	return s
-}
 func valueToStringDrivemount(v interface{}) string {
 	rv := reflect.ValueOf(v)
 	if rv.IsNil() {
@@ -326,7 +223,6 @@ func valueToStringDrivemount(v interface{}) string {
 
 type DriveMounterService interface {
 	MountDrive(ctx context.Context, req *MountDriveRequest) (*empty.Empty, error)
-	UnmountDrive(ctx context.Context, req *UnmountDriveRequest) (*empty.Empty, error)
 }
 
 func RegisterDriveMounterService(srv *github_com_containerd_ttrpc.Server, svc DriveMounterService) {
@@ -338,13 +234,6 @@ func RegisterDriveMounterService(srv *github_com_containerd_ttrpc.Server, svc Dr
 			}
 			return svc.MountDrive(ctx, &req)
 		},
-		"UnmountDrive": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {
-			var req UnmountDriveRequest
-			if err := unmarshal(&req); err != nil {
-				return nil, err
-			}
-			return svc.UnmountDrive(ctx, &req)
-		},
 	})
 }
 
@@ -365,14 +254,6 @@ func (c *driveMounterClient) MountDrive(ctx context.Context, req *MountDriveRequ
 	}
 	return &resp, nil
 }
-
-func (c *driveMounterClient) UnmountDrive(ctx context.Context, req *UnmountDriveRequest) (*empty.Empty, error) {
-	var resp empty.Empty
-	if err := c.client.Call(ctx, "DriveMounter", "UnmountDrive", req, &resp); err != nil {
-		return nil, err
-	}
-	return &resp, nil
-}
 func (m *MountDriveRequest) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -555,92 +436,6 @@ func (m *MountDriveRequest) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
-func (m *UnmountDriveRequest) Unmarshal(dAtA []byte) error {
-	l := len(dAtA)
-	iNdEx := 0
-	for iNdEx < l {
-		preIndex := iNdEx
-		var wire uint64
-		for shift := uint(0); ; shift += 7 {
-			if shift >= 64 {
-				return ErrIntOverflowDrivemount
-			}
-			if iNdEx >= l {
-				return io.ErrUnexpectedEOF
-			}
-			b := dAtA[iNdEx]
-			iNdEx++
-			wire |= uint64(b&0x7F) << shift
-			if b < 0x80 {
-				break
-			}
-		}
-		fieldNum := int32(wire >> 3)
-		wireType := int(wire & 0x7)
-		if wireType == 4 {
-			return fmt.Errorf("proto: UnmountDriveRequest: wiretype end group for non-group")
-		}
-		if fieldNum <= 0 {
-			return fmt.Errorf("proto: UnmountDriveRequest: illegal tag %d (wire type %d)", fieldNum, wire)
-		}
-		switch fieldNum {
-		case 1:
-			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field DriveID", wireType)
-			}
-			var stringLen uint64
-			for shift := uint(0); ; shift += 7 {
-				if shift >= 64 {
-					return ErrIntOverflowDrivemount
-				}
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := dAtA[iNdEx]
-				iNdEx++
-				stringLen |= uint64(b&0x7F) << shift
-				if b < 0x80 {
-					break
-				}
-			}
-			intStringLen := int(stringLen)
-			if intStringLen < 0 {
-				return ErrInvalidLengthDrivemount
-			}
-			postIndex := iNdEx + intStringLen
-			if postIndex < 0 {
-				return ErrInvalidLengthDrivemount
-			}
-			if postIndex > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.DriveID = string(dAtA[iNdEx:postIndex])
-			iNdEx = postIndex
-		default:
-			iNdEx = preIndex
-			skippy, err := skipDrivemount(dAtA[iNdEx:])
-			if err != nil {
-				return err
-			}
-			if skippy < 0 {
-				return ErrInvalidLengthDrivemount
-			}
-			if (iNdEx + skippy) < 0 {
-				return ErrInvalidLengthDrivemount
-			}
-			if (iNdEx + skippy) > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
-			iNdEx += skippy
-		}
-	}
-
-	if iNdEx > l {
-		return io.ErrUnexpectedEOF
-	}
-	return nil
-}
 func skipDrivemount(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/runtime/drive_handler.go b/runtime/drive_handler.go
index a10f401..6ac09bf 100644
--- a/runtime/drive_handler.go
+++ b/runtime/drive_handler.go
@@ -96,80 +96,32 @@ type StubDriveHandler struct {
 // Reserve pops a unused stub drive and returns a MountableStubDrive that can be
 // mounted with the provided options as the patched drive information.
 func (h *StubDriveHandler) Reserve(
-	requestCtx context.Context,
 	id string,
 	hostPath string,
 	vmPath string,
 	filesystemType string,
 	options []string,
-	driveMounter drivemount.DriveMounterService,
-	machine firecracker.MachineIface,
-) error {
+) (MountableStubDrive, error) {
 	h.mu.Lock()
 	defer h.mu.Unlock()
 	if len(h.freeDrives) == 0 {
-		return ErrDrivesExhausted
-	}
-	if _, ok := h.usedDrives[id]; ok {
-		// This case means that drive wasn't released or removed properly
-		return fmt.Errorf("drive with ID %s already in use, a previous attempt to remove it may have failed", id)
+		return nil, ErrDrivesExhausted
 	}
 
 	freeDrive := h.freeDrives[0]
 	options, err := setReadWriteOptions(options, freeDrive.driveMount.IsWritable)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
-	stubDrive := freeDrive.withMountConfig(
+	h.freeDrives = h.freeDrives[1:]
+	h.usedDrives[id] = freeDrive
+	return freeDrive.withMountConfig(
 		hostPath,
 		vmPath,
 		filesystemType,
 		options,
-	)
-	freeDrive = &stubDrive
-
-	err = stubDrive.PatchAndMount(requestCtx, machine, driveMounter)
-	if err != nil {
-		err = errors.Wrapf(err, "failed to mount drive inside vm")
-		return err
-	}
-
-	h.freeDrives = h.freeDrives[1:]
-	h.usedDrives[id] = freeDrive
-	return nil
-}
-
-// Release unmounts stub drive of just deleted container
-// and pushes just released drive to freeDrives
-func (h *StubDriveHandler) Release(
-	requestCtx context.Context,
-	id string,
-	driveMounter drivemount.DriveMounterService,
-	machine firecracker.MachineIface,
-) error {
-	h.mu.Lock()
-	defer h.mu.Unlock()
-	drive, ok := h.usedDrives[id]
-	if !ok {
-		return errors.Errorf("container %s drive wasn't found", id)
-	}
-
-	_, err := driveMounter.UnmountDrive(requestCtx, &drivemount.UnmountDriveRequest{
-		DriveID: drive.driveID,
-	})
-	if err != nil {
-		return errors.Wrap(err, "failed to unmount drive")
-	}
-
-	err = machine.UpdateGuestDrive(requestCtx, drive.driveID, filepath.Base(drive.stubPath))
-	if err != nil {
-		return errors.Wrap(err, "failed to patch drive")
-	}
-
-	delete(h.usedDrives, id)
-	h.freeDrives = append(h.freeDrives, drive)
-	return nil
+	), nil
 }
 
 // CreateDriveMountStubs creates a set of MountableStubDrives from the provided DriveMount configs.
diff --git a/runtime/service.go b/runtime/service.go
index 2b207ca..c713b25 100644
--- a/runtime/service.go
+++ b/runtime/service.go
@@ -46,7 +46,6 @@ import (
 	"github.com/gofrs/uuid"
 	ptypes "github.com/gogo/protobuf/types"
 	"github.com/golang/protobuf/ptypes/empty"
-	"github.com/hashicorp/go-multierror"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/sys/unix"
@@ -811,14 +810,21 @@ func (s *service) Create(requestCtx context.Context, request *taskAPI.CreateTask
 	}
 	rootfsMnt := request.Rootfs[0]
 
-	err = s.containerStubHandler.Reserve(requestCtx, request.ID,
-		rootfsMnt.Source, vmBundleDir.RootfsPath(), "ext4", nil, s.driveMountClient, s.machine)
+	stubDrive, err := s.containerStubHandler.Reserve(request.ID,
+		rootfsMnt.Source, vmBundleDir.RootfsPath(), "ext4", nil)
 	if err != nil {
 		err = errors.Wrapf(err, "failed to get stub drive for task %q", request.ID)
 		logger.WithError(err).Error()
 		return nil, err
 	}
 
+	err = stubDrive.PatchAndMount(requestCtx, s.machine, s.driveMountClient)
+	if err != nil {
+		err = errors.Wrapf(err, "failed to mount drive inside vm")
+		logger.WithError(err).Error()
+		return nil, err
+	}
+
 	ociConfigBytes, err := hostBundleDir.OCIConfig().Bytes()
 	if err != nil {
 		return nil, err
@@ -922,29 +928,23 @@ func (s *service) Delete(requestCtx context.Context, req *taskAPI.DeleteRequest)
 	if req.ExecID != "" {
 		return resp, nil
 	}
-	var result *multierror.Error
-	// Trying to release stub drive for further reuse
-	err = s.containerStubHandler.Release(requestCtx, req.ID, s.driveMountClient, s.machine)
-	if err != nil {
-		result = multierror.Append(result, errors.Wrapf(err, "failed to release stub drive for container: %s", req.ID))
-	}
 
 	// Otherwise, delete the container
 	dir, err := s.shimDir.BundleLink(req.ID)
 	if err != nil {
-		result = multierror.Append(result, errors.Wrapf(err, "failed to find the bundle directory of the container: %s", req.ID))
+		return nil, errors.Wrapf(err, "failed to find the bundle directory of the container: %s", req.ID)
 	}
 
 	_, err = os.Stat(dir.RootPath())
 	if os.IsNotExist(err) {
-		result = multierror.Append(result, errors.Wrapf(err, "failed to find the bundle directory of the container: %s", dir.RootPath()))
+		return nil, errors.Wrapf(err, "failed to find the bundle directory of the container: %s", dir.RootPath())
 	}
 
 	if err = os.Remove(dir.RootPath()); err != nil {
-		result = multierror.Append(result, errors.Wrapf(err, "failed to remove the bundle directory of the container: %s", dir.RootPath()))
+		return nil, errors.Wrapf(err, "failed to remove the bundle directory of the container: %s", dir.RootPath())
 	}
 
-	return resp, result.ErrorOrNil()
+	return resp, nil
 }
 
 // Exec an additional process inside the container


Test patch:
diff --git a/runtime/drive_handler_test.go b/runtime/drive_handler_test.go
index 5009402..6a64217 100644
--- a/runtime/drive_handler_test.go
+++ b/runtime/drive_handler_test.go
@@ -92,6 +92,18 @@ func TestContainerStubs(t *testing.T) {
 		fsType := "foo4"
 		fsOptions := []string{"blah", "bleg"}
 
+		mountableStubDrive, err := stubDriveHandler.Reserve(
+			id, hostPath, vmPath, fsType, fsOptions)
+		assert.NoError(t, err, "failed to reserve stub drive")
+
+		stub := mountableStubDrive.(stubDrive)
+		assert.Equal(t, hostPath, stub.driveMount.HostPath)
+		assert.Equal(t, vmPath, stub.driveMount.VMPath)
+		assert.Equal(t, fsType, stub.driveMount.FilesystemType)
+		assert.Equal(t, append(fsOptions, "rw"), stub.driveMount.Options)
+		assert.True(t, stub.driveMount.IsWritable)
+		assert.Nil(t, stub.driveMount.RateLimiter)
+
 		mockMachine, err := firecracker.NewMachine(ctx, firecracker.Config{}, firecracker.WithClient(
 			firecracker.NewClient("/path/to/socket", nil, false, firecracker.WithOpsClient(&fctesting.MockClient{
 				PatchGuestDriveByIDFn: func(params *ops.PatchGuestDriveByIDParams) (*ops.PatchGuestDriveByIDNoContent, error) {
@@ -108,18 +120,8 @@ func TestContainerStubs(t *testing.T) {
 			expectedOptions:         append(fsOptions, "rw"),
 		}
 
-		err = stubDriveHandler.Reserve(
-			ctx, id, hostPath, vmPath, fsType, fsOptions, mockDriveMounter, mockMachine)
-		assert.NoError(t, err, "failed to reserve stub drive")
-
-		stub, ok := stubDriveHandler.usedDrives[id]
-		assert.True(t, ok)
-		assert.Equal(t, hostPath, stub.driveMount.HostPath)
-		assert.Equal(t, vmPath, stub.driveMount.VMPath)
-		assert.Equal(t, fsType, stub.driveMount.FilesystemType)
-		assert.Equal(t, append(fsOptions, "rw"), stub.driveMount.Options)
-		assert.True(t, stub.driveMount.IsWritable)
-		assert.Nil(t, stub.driveMount.RateLimiter)
+		err = mountableStubDrive.PatchAndMount(ctx, mockMachine, mockDriveMounter)
+		assert.NoError(t, err, "failed to patch and mount stub drive")
 	}
 }
 
diff --git a/runtime/service_integ_test.go b/runtime/service_integ_test.go
index 74a4d24..a83352e 100644
--- a/runtime/service_integ_test.go
+++ b/runtime/service_integ_test.go
@@ -782,7 +782,7 @@ func TestCreateContainerWithSameName_Isolated(t *testing.T) {
 	testCreateContainerWithSameName(t, vmID)
 }
 
-func TestStubDriveReserveAndReleaseByContainers_Isolated(t *testing.T) {
+func TestCreateTooManyContainers_Isolated(t *testing.T) {
 	prepareIntegTest(t)
 
 	assert := assert.New(t)
@@ -825,9 +825,13 @@ func TestStubDriveReserveAndReleaseByContainers_Isolated(t *testing.T) {
 		require.NoError(t, err, "failed to delete a container")
 	}()
 
-	// With the new behaviour, on previous task deletion, stub drive will be released
-	// and now can be reused by new container and task.
-	assert.Equal("hello", startAndWaitTask(ctx, t, c2))
+	var stdout bytes.Buffer
+	var stderr bytes.Buffer
+
+	// When we reuse a VM explicitly, we cannot start multiple containers unless we pre-allocate stub drives.
+	_, err = c2.NewTask(ctx, cio.NewCreator(cio.WithStreams(nil, &stdout, &stderr)))
+	assert.Contains(err.Error(), "There are no remaining drives to be used")
+	require.Error(t, err)
 }
 
 func TestDriveMount_Isolated(t *testing.T) {