Repository: firecracker-microvm/firecracker-containerd
Pull request #425 (2020-05-20 23:22:43)

Make CreateVM's timeout configurable
*Issue #, if available:* #423

*Description of changes:*

As like StopVM, CreateVM's timeout should be configurable to accomodate
other root fs images and slower environments.

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/Makefile b/Makefile
index c0df1d7..fd315a4 100644
--- a/Makefile
+++ b/Makefile
@@ -187,6 +187,10 @@ DEFAULT_RUNC_JAILER_CONFIG_INSTALLPATH?=/etc/containerd/firecracker-runc-config.
 $(DEFAULT_RUNC_JAILER_CONFIG_INSTALLPATH): $(ETC_CONTAINERD) runtime/firecracker-runc-config.json.example
 	install -D -o root -g root -m400 runtime/firecracker-runc-config.json.example $@
 
+ROOTFS_SLOW_BOOT_INSTALLPATH=$(FIRECRACKER_CONTAINERD_RUNTIME_DIR)/rootfs-slow-boot.img
+$(ROOTFS_SLOW_BOOT_INSTALLPATH): tools/image-builder/rootfs-slow-boot.img $(FIRECRACKER_CONTAINERD_RUNTIME_DIR)
+	install -D -o root -g root -m400 $< $@
+
 ROOTFS_SLOW_REBOOT_INSTALLPATH=$(FIRECRACKER_CONTAINERD_RUNTIME_DIR)/rootfs-slow-reboot.img
 $(ROOTFS_SLOW_REBOOT_INSTALLPATH): tools/image-builder/rootfs-slow-reboot.img $(FIRECRACKER_CONTAINERD_RUNTIME_DIR)
 	install -D -o root -g root -m400 $< $@
@@ -204,7 +208,7 @@ install-default-rootfs: $(DEFAULT_ROOTFS_INSTALLPATH)
 install-default-runc-jailer-config: $(DEFAULT_RUNC_JAILER_CONFIG_INSTALLPATH)
 
 .PHONY: install-test-rootfs
-install-test-rootfs: $(ROOTFS_SLOW_REBOOT_INSTALLPATH)
+install-test-rootfs: $(ROOTFS_SLOW_BOOT_INSTALLPATH) $(ROOTFS_SLOW_REBOOT_INSTALLPATH)
 
 ##########################
 # CNI Network
diff --git a/firecracker-control/local.go b/firecracker-control/local.go
index 41ba883..fe5d568 100644
--- a/firecracker-control/local.go
+++ b/firecracker-control/local.go
@@ -195,8 +195,7 @@ func (s *local) CreateVM(requestCtx context.Context, req *proto.CreateVMRequest)
 
 	resp, err := client.CreateVM(requestCtx, req)
 	if err != nil {
-		err = errors.Wrap(err, "shim CreateVM returned error")
-		s.logger.WithError(err).Error()
+		s.logger.WithError(err).Error("shim CreateVM returned error")
 		return nil, err
 	}
 
diff --git a/internal/vm/vsock.go b/internal/vm/vsock.go
index a85136e..7f4edfb 100644
--- a/internal/vm/vsock.go
+++ b/internal/vm/vsock.go
@@ -37,10 +37,7 @@ const (
 // VSockDial attempts to connect to the Firecracker host-side vsock at the provided unix
 // path and port. It will retry connect attempts if a temporary error is encountered (up
 // to a fixed timeout) or the provided request is canceled.
-func VSockDial(reqCtx context.Context, logger *logrus.Entry, udsPath string, port uint32) (net.Conn, error) {
-	ctx, cancel := context.WithTimeout(reqCtx, vsockRetryTimeout)
-	defer cancel()
-
+func VSockDial(ctx context.Context, logger *logrus.Entry, udsPath string, port uint32) (net.Conn, error) {
 	tickerCh := time.NewTicker(vsockRetryInterval).C
 	var attemptCount int
 	for {
diff --git a/proto/firecracker.pb.go b/proto/firecracker.pb.go
index ef7db13..37ee60b 100644
--- a/proto/firecracker.pb.go
+++ b/proto/firecracker.pb.go
@@ -41,6 +41,7 @@ type CreateVMRequest struct {
 	// Whether the VM should exit after all tasks running in it have been deleted.
 	ExitAfterAllTasksDeleted bool          `protobuf:"varint,9,opt,name=ExitAfterAllTasksDeleted,json=exitAfterAllTasksDeleted,proto3" json:"ExitAfterAllTasksDeleted,omitempty"`
 	JailerConfig             *JailerConfig `protobuf:"bytes,10,opt,name=JailerConfig,json=jailerConfig,proto3" json:"JailerConfig,omitempty"`
+	TimeoutSeconds           uint32        `protobuf:"varint,11,opt,name=TimeoutSeconds,json=timeoutSeconds,proto3" json:"TimeoutSeconds,omitempty"`
 	XXX_NoUnkeyedLiteral     struct{}      `json:"-"`
 	XXX_unrecognized         []byte        `json:"-"`
 	XXX_sizecache            int32         `json:"-"`
@@ -140,6 +141,13 @@ func (m *CreateVMRequest) GetJailerConfig() *JailerConfig {
 	return nil
 }
 
+func (m *CreateVMRequest) GetTimeoutSeconds() uint32 {
+	if m != nil {
+		return m.TimeoutSeconds
+	}
+	return 0
+}
+
 type CreateVMResponse struct {
 	VMID                 string   `protobuf:"bytes,1,opt,name=VMID,json=vMID,proto3" json:"VMID,omitempty"`
 	SocketPath           string   `protobuf:"bytes,2,opt,name=SocketPath,json=socketPath,proto3" json:"SocketPath,omitempty"`
@@ -648,43 +656,44 @@ func init() {
 func init() { proto.RegisterFile("firecracker.proto", fileDescriptor_a73317e9fb8da571) }
 
 var fileDescriptor_a73317e9fb8da571 = []byte{
-	// 603 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x54, 0xc1, 0x6e, 0xd3, 0x40,
-	0x10, 0x95, 0x9b, 0xa4, 0x4d, 0xc6, 0x4d, 0xd2, 0xac, 0x5a, 0x58, 0x55, 0xa8, 0xb2, 0x7c, 0xa8,
-	0x22, 0x0e, 0x95, 0x68, 0x2f, 0x88, 0x13, 0x25, 0xa6, 0x95, 0x5b, 0x5c, 0x55, 0x9b, 0xa6, 0x07,
-	0x6e, 0x8b, 0x33, 0x76, 0x4d, 0xe2, 0xdd, 0xb0, 0xbb, 0x29, 0x70, 0xe6, 0x07, 0xf8, 0x16, 0xf8,
-	0x41, 0xe4, 0x4d, 0x9a, 0x38, 0x11, 0x54, 0x48, 0x9c, 0x38, 0x65, 0xe7, 0xcd, 0x9b, 0x99, 0xa7,
-	0x79, 0x13, 0x43, 0x27, 0xc9, 0x14, 0xc6, 0x8a, 0xc7, 0x23, 0x54, 0x47, 0x13, 0x25, 0x8d, 0xdc,
-	0x77, 0xcd, 0xd7, 0x09, 0xea, 0x59, 0xe0, 0x7f, 0xab, 0x42, 0xbb, 0xa7, 0x90, 0x1b, 0xbc, 0x8d,
-	0x18, 0x7e, 0x9a, 0xa2, 0x36, 0x84, 0x40, 0xf5, 0x36, 0x0a, 0x03, 0xea, 0x78, 0x4e, 0xb7, 0xc1,
-	0xaa, 0xf7, 0x51, 0x18, 0x90, 0xd7, 0x00, 0x11, 0x8f, 0xef, 0x32, 0x81, 0xbd, 0x24, 0xa5, 0x1b,
-	0x9e, 0xd3, 0x75, 0x8f, 0xbd, 0xa3, 0xb3, 0x65, 0xf3, 0x87, 0xac, 0x14, 0x49, 0x96, 0x4e, 0x15,
-	0x37, 0x99, 0x14, 0x0c, 0xf2, 0x45, 0x0d, 0xe9, 0x42, 0xfb, 0x12, 0x95, 0xc0, 0x71, 0x98, 0xf3,
-	0x14, 0xaf, 0xb9, 0xb9, 0xa3, 0x15, 0x3b, 0xa0, 0x3d, 0x5a, 0x85, 0xc9, 0x01, 0xc0, 0x8c, 0x79,
-	0xaa, 0x52, 0x4d, 0xab, 0x96, 0x04, 0xa3, 0x05, 0x42, 0x4e, 0xa0, 0xc1, 0xa4, 0x34, 0x81, 0xca,
-	0xee, 0x91, 0xd6, 0xac, 0x94, 0xbd, 0xb2, 0x94, 0x45, 0x92, 0x35, 0xd4, 0xc3, 0x93, 0xbc, 0x04,
-	0xd7, 0x3e, 0x22, 0x39, 0x15, 0x46, 0xd3, 0x4d, 0xaf, 0xd2, 0x75, 0x8f, 0x9f, 0x94, 0xcb, 0x96,
-	0x69, 0xe6, 0x0e, 0x97, 0x54, 0x72, 0x01, 0x9d, 0x2b, 0x34, 0x9f, 0xa5, 0x1a, 0x85, 0xc2, 0xa0,
-	0x4a, 0x78, 0x8c, 0x9a, 0x6e, 0xd9, 0xfa, 0x67, 0xe5, 0xfa, 0x75, 0x12, 0xeb, 0x88, 0xf5, 0x32,
-	0x72, 0x08, 0xad, 0x9e, 0x14, 0x86, 0x67, 0x02, 0x55, 0xaf, 0x68, 0x4f, 0xeb, 0x9e, 0xd3, 0xad,
-	0xb1, 0x56, 0xbc, 0x82, 0x92, 0x57, 0x40, 0xdf, 0x7e, 0xc9, 0xcc, 0x69, 0x62, 0x50, 0x9d, 0x8e,
-	0xc7, 0x37, 0x5c, 0x8f, 0x74, 0x80, 0x63, 0x34, 0x38, 0xa4, 0x0d, 0xcf, 0xe9, 0xd6, 0x19, 0xc5,
-	0x3f, 0xe4, 0xc9, 0x0b, 0xd8, 0xbe, 0xe0, 0xd9, 0xb8, 0x68, 0x55, 0x78, 0x41, 0xc1, 0x6e, 0xa8,
-	0x79, 0x54, 0x06, 0xd9, 0xf6, 0xc7, 0x52, 0xe4, 0xff, 0x70, 0x60, 0x67, 0x79, 0x05, 0x7a, 0x22,
-	0x85, 0xc6, 0xdf, 0x9e, 0xc1, 0x01, 0x40, 0x5f, 0xc6, 0x23, 0x34, 0xd6, 0xbf, 0x8d, 0x99, 0x35,
-	0x7a, 0x81, 0x10, 0x0f, 0xdc, 0x77, 0x32, 0x3d, 0xcb, 0x12, 0x59, 0x32, 0xd8, 0x1d, 0x2f, 0xa1,
-	0xe2, 0x0c, 0x22, 0x34, 0x2a, 0x8b, 0xf5, 0x82, 0x35, 0x73, 0xb8, 0x9d, 0xaf, 0xc2, 0xc5, 0xac,
-	0x5e, 0xaa, 0xe4, 0x74, 0x62, 0x49, 0xb5, 0xd9, 0xac, 0x78, 0x81, 0xf8, 0x97, 0xd0, 0xec, 0x1b,
-	0x39, 0x79, 0xfc, 0x6e, 0x0f, 0xa1, 0x75, 0x93, 0xe5, 0x28, 0xa7, 0xa6, 0x8f, 0xb1, 0x14, 0x43,
-	0x6d, 0x45, 0x37, 0x59, 0xcb, 0xac, 0xa0, 0xfe, 0x21, 0xec, 0x9c, 0xa3, 0xb9, 0x8d, 0x42, 0x91,
-	0xc8, 0x47, 0xfa, 0xf9, 0x3f, 0x1d, 0xe8, 0x94, 0x88, 0xff, 0xc9, 0xaa, 0xce, 0x60, 0xb7, 0x5f,
-	0x88, 0x8e, 0xd0, 0xf0, 0x21, 0x37, 0xfc, 0xb1, 0x8d, 0xed, 0x43, 0xfd, 0x81, 0x36, 0x57, 0x5d,
-	0xcf, 0xe7, 0xb1, 0x1f, 0xc2, 0xd3, 0xc1, 0x64, 0x68, 0xcf, 0xe4, 0x5f, 0x5b, 0x3d, 0x87, 0xdd,
-	0xf3, 0xbf, 0x94, 0xe4, 0x9f, 0xc0, 0xde, 0x1a, 0x77, 0xbe, 0xf7, 0xf2, 0x00, 0x67, 0x6d, 0xc0,
-	0x77, 0x67, 0xf5, 0x7f, 0x40, 0x76, 0xa1, 0x76, 0x85, 0xe6, 0xaa, 0x3f, 0x67, 0xd6, 0x44, 0x11,
-	0x14, 0xf3, 0x7a, 0xd7, 0x03, 0x3d, 0xd7, 0x57, 0x8d, 0xaf, 0x07, 0xba, 0xc0, 0x22, 0xcc, 0xf5,
-	0xdc, 0x93, 0x6a, 0x8e, 0xb9, 0x26, 0x3b, 0x50, 0x19, 0x84, 0x81, 0x35, 0xa0, 0xc9, 0x2a, 0xd3,
-	0x30, 0x28, 0x90, 0xf3, 0x30, 0xb0, 0xdb, 0x6e, 0xb2, 0x4a, 0x3a, 0xb3, 0xbc, 0x64, 0xc3, 0xe6,
-	0xba, 0x0d, 0x6f, 0xb6, 0xde, 0xd7, 0xec, 0x57, 0xf7, 0xc3, 0xa6, 0xfd, 0x39, 0xf9, 0x15, 0x00,
-	0x00, 0xff, 0xff, 0x8e, 0xa5, 0x2f, 0xe8, 0x9e, 0x05, 0x00, 0x00,
+	// 611 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x54, 0x4d, 0x6f, 0xd3, 0x40,
+	0x10, 0x95, 0x9b, 0xa4, 0x4d, 0xc6, 0x4d, 0xd3, 0xac, 0x5a, 0x58, 0x55, 0xa8, 0xb2, 0x72, 0xa8,
+	0x2c, 0x0e, 0x95, 0x68, 0x2f, 0x88, 0x13, 0x25, 0xa6, 0x95, 0x5b, 0x5c, 0x55, 0x9b, 0xa6, 0x07,
+	0x6e, 0x8b, 0x33, 0x76, 0x4d, 0xe2, 0xdd, 0xb0, 0xbb, 0x29, 0xf0, 0x2f, 0xf8, 0x1f, 0xdc, 0xe0,
+	0x0f, 0x22, 0x6f, 0xd2, 0xc4, 0x09, 0xa5, 0x42, 0xe2, 0xc4, 0x29, 0x3b, 0x6f, 0xde, 0x7c, 0x68,
+	0xde, 0x8b, 0xa1, 0x9d, 0x64, 0x0a, 0x63, 0xc5, 0xe3, 0x21, 0xaa, 0xc3, 0xb1, 0x92, 0x46, 0xee,
+	0xb9, 0xe6, 0xeb, 0x18, 0xf5, 0x34, 0xe8, 0x7c, 0xaf, 0x42, 0xab, 0xab, 0x90, 0x1b, 0xbc, 0x89,
+	0x18, 0x7e, 0x9a, 0xa0, 0x36, 0x84, 0x40, 0xf5, 0x26, 0x0a, 0x03, 0xea, 0x78, 0x8e, 0xdf, 0x60,
+	0xd5, 0xbb, 0x28, 0x0c, 0xc8, 0x6b, 0x80, 0x88, 0xc7, 0xb7, 0x99, 0xc0, 0x6e, 0x92, 0xd2, 0x35,
+	0xcf, 0xf1, 0xdd, 0x23, 0xef, 0xf0, 0x74, 0xd1, 0xfc, 0x3e, 0x2b, 0x45, 0x92, 0xa5, 0x13, 0xc5,
+	0x4d, 0x26, 0x05, 0x83, 0x7c, 0x5e, 0x43, 0x7c, 0x68, 0x5d, 0xa0, 0x12, 0x38, 0x0a, 0x73, 0x9e,
+	0xe2, 0x15, 0x37, 0xb7, 0xb4, 0x62, 0x07, 0xb4, 0x86, 0xcb, 0x30, 0xd9, 0x07, 0x98, 0x32, 0x4f,
+	0x54, 0xaa, 0x69, 0xd5, 0x92, 0x60, 0x38, 0x47, 0xc8, 0x31, 0x34, 0x98, 0x94, 0x26, 0x50, 0xd9,
+	0x1d, 0xd2, 0x9a, 0x5d, 0x65, 0xb7, 0xbc, 0xca, 0x3c, 0xc9, 0x1a, 0xea, 0xfe, 0x49, 0x5e, 0x82,
+	0x6b, 0x1f, 0x91, 0x9c, 0x08, 0xa3, 0xe9, 0xba, 0x57, 0xf1, 0xdd, 0xa3, 0x27, 0xe5, 0xb2, 0x45,
+	0x9a, 0xb9, 0x83, 0x05, 0x95, 0x9c, 0x43, 0xfb, 0x12, 0xcd, 0x67, 0xa9, 0x86, 0xa1, 0x30, 0xa8,
+	0x12, 0x1e, 0xa3, 0xa6, 0x1b, 0xb6, 0xfe, 0x59, 0xb9, 0x7e, 0x95, 0xc4, 0xda, 0x62, 0xb5, 0x8c,
+	0x1c, 0xc0, 0x56, 0x57, 0x0a, 0xc3, 0x33, 0x81, 0xaa, 0x5b, 0xb4, 0xa7, 0x75, 0xcf, 0xf1, 0x6b,
+	0x6c, 0x2b, 0x5e, 0x42, 0xc9, 0x2b, 0xa0, 0x6f, 0xbf, 0x64, 0xe6, 0x24, 0x31, 0xa8, 0x4e, 0x46,
+	0xa3, 0x6b, 0xae, 0x87, 0x3a, 0xc0, 0x11, 0x1a, 0x1c, 0xd0, 0x86, 0xe7, 0xf8, 0x75, 0x46, 0xf1,
+	0x0f, 0x79, 0xf2, 0x02, 0x36, 0xcf, 0x79, 0x36, 0x2a, 0x5a, 0x15, 0x5a, 0x50, 0xb0, 0x17, 0x6a,
+	0x1e, 0x96, 0x41, 0xb6, 0xf9, 0xb1, 0x14, 0x15, 0x6b, 0x5d, 0x67, 0x39, 0xca, 0x89, 0xe9, 0x61,
+	0x2c, 0xc5, 0x40, 0x53, 0xd7, 0x73, 0xfc, 0x26, 0xdb, 0x32, 0x4b, 0x68, 0xe7, 0x87, 0x03, 0xdb,
+	0x0b, 0xb7, 0xe8, 0xb1, 0x14, 0x1a, 0x1f, 0xb4, 0xcb, 0x3e, 0x40, 0x4f, 0xc6, 0x43, 0x34, 0x56,
+	0xe7, 0xb5, 0xa9, 0x84, 0x7a, 0x8e, 0x10, 0x0f, 0xdc, 0x77, 0x32, 0x3d, 0xcd, 0x12, 0x59, 0x32,
+	0x82, 0x3b, 0x5a, 0x40, 0x85, 0x5d, 0x22, 0x34, 0x2a, 0x8b, 0xf5, 0x9c, 0x35, 0x75, 0x42, 0x2b,
+	0x5f, 0x86, 0x8b, 0x59, 0xdd, 0x54, 0xc9, 0xc9, 0xd8, 0x92, 0x6a, 0xd3, 0x59, 0xf1, 0x1c, 0xe9,
+	0x5c, 0x40, 0xb3, 0x67, 0xe4, 0xf8, 0x71, 0x7f, 0xff, 0x7e, 0x81, 0xb5, 0x07, 0x2f, 0x70, 0x00,
+	0xdb, 0x67, 0x68, 0x6e, 0xa2, 0x50, 0x24, 0xf2, 0x91, 0x7e, 0x9d, 0x9f, 0x0e, 0xb4, 0x4b, 0xc4,
+	0xff, 0xe4, 0x54, 0xa7, 0xb0, 0xd3, 0x2b, 0x96, 0x8e, 0xd0, 0xf0, 0x01, 0x37, 0xfc, 0xb1, 0x8b,
+	0xed, 0x41, 0xfd, 0x9e, 0x36, 0xdb, 0xba, 0x9e, 0xcf, 0xe2, 0x4e, 0x08, 0x4f, 0xfb, 0xe3, 0x81,
+	0xb5, 0xc9, 0xbf, 0xb6, 0x7a, 0x0e, 0x3b, 0x67, 0x7f, 0xb9, 0x52, 0xe7, 0x18, 0x76, 0x57, 0xb8,
+	0xb3, 0xbb, 0x97, 0x07, 0x38, 0x2b, 0x03, 0xbe, 0x39, 0xcb, 0xff, 0x17, 0xb2, 0x03, 0xb5, 0x4b,
+	0x34, 0x97, 0xbd, 0x19, 0xb3, 0x26, 0x8a, 0xa0, 0x98, 0xd7, 0xbd, 0xea, 0xeb, 0xd9, 0x7e, 0xd5,
+	0xf8, 0xaa, 0xaf, 0x0b, 0x2c, 0xc2, 0x5c, 0xcf, 0x34, 0xa9, 0xe6, 0x98, 0x6b, 0xb2, 0x0d, 0x95,
+	0x7e, 0x18, 0x58, 0x01, 0x9a, 0xac, 0x32, 0x09, 0x83, 0x02, 0x39, 0x0b, 0x03, 0x7b, 0xed, 0x26,
+	0xab, 0xa4, 0x53, 0xc9, 0x4b, 0x32, 0xac, 0xaf, 0xca, 0xf0, 0x66, 0xe3, 0x7d, 0xcd, 0x7e, 0x9d,
+	0x3f, 0xac, 0xdb, 0x9f, 0xe3, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x26, 0x4b, 0x24, 0x7c, 0xc6,
+	0x05, 0x00, 0x00,
 }
diff --git a/proto/firecracker.proto b/proto/firecracker.proto
index 3f7512a..0abd1f8 100644
--- a/proto/firecracker.proto
+++ b/proto/firecracker.proto
@@ -34,6 +34,8 @@ message CreateVMRequest {
     bool ExitAfterAllTasksDeleted = 9;
 
     JailerConfig JailerConfig = 10;
+
+    uint32 TimeoutSeconds = 11;
 }
 
 message CreateVMResponse {
diff --git a/runtime/service.go b/runtime/service.go
index a288e4e..c2761bc 100644
--- a/runtime/service.go
+++ b/runtime/service.go
@@ -68,12 +68,18 @@ func init() {
 }
 
 const (
-	defaultVsockPort        = 10789
-	minVsockIOPort          = uint32(11000)
-	firecrackerStartTimeout = 5 * time.Second
-	defaultStopVMTimeout    = 5 * time.Second
-	defaultShutdownTimeout  = 5 * time.Second
-	jailerStopTimeout       = 3 * time.Second
+	defaultVsockPort = 10789
+	minVsockIOPort   = uint32(11000)
+
+	// vmReadyTimeout is used to control the time all requests wait a Go channel (vmReady) before calling
+	// Firecracker's API server. The channel is closed once the VM starts.
+	vmReadyTimeout = 5 * time.Second
+
+	defaultCreateVMTimeout = 20 * time.Second
+	defaultStopVMTimeout   = 5 * time.Second
+	defaultShutdownTimeout = 5 * time.Second
+
+	jailerStopTimeout = 3 * time.Second
 
 	// StartEventName is the topic published to when a VM starts
 	StartEventName = "/firecracker-vm/start"
@@ -425,7 +431,7 @@ func (s *service) waitVMReady() error {
 	select {
 	case <-s.vmReady:
 		return nil
-	case <-time.After(firecrackerStartTimeout):
+	case <-time.After(vmReadyTimeout):
 		return status.Error(codes.DeadlineExceeded, "timed out waiting for VM start")
 	}
 }
@@ -435,6 +441,13 @@ func (s *service) waitVMReady() error {
 func (s *service) CreateVM(requestCtx context.Context, request *proto.CreateVMRequest) (*proto.CreateVMResponse, error) {
 	defer logPanicAndDie(s.logger)
 
+	timeout := defaultCreateVMTimeout
+	if request.TimeoutSeconds > 0 {
+		timeout = time.Duration(request.TimeoutSeconds) * time.Second
+	}
+	ctxWithTimeout, cancel := context.WithTimeout(requestCtx, timeout)
+	defer cancel()
+
 	var (
 		err       error
 		createRan bool
@@ -442,7 +455,7 @@ func (s *service) CreateVM(requestCtx context.Context, request *proto.CreateVMRe
 	)
 
 	s.vmStartOnce.Do(func() {
-		err = s.createVM(requestCtx, request)
+		err = s.createVM(ctxWithTimeout, request)
 		createRan = true
 	})
 
@@ -453,9 +466,13 @@ func (s *service) CreateVM(requestCtx context.Context, request *proto.CreateVMRe
 	// If we failed to create the VM, we have no point in existing anymore, so shutdown
 	if err != nil {
 		s.shimCancel()
-		err = errors.Wrap(err, "failed to create VM")
-		s.logger.WithError(err).Error()
-		return nil, err
+
+		s.logger.WithError(err).Error("failed to create VM")
+
+		if errors.Cause(err) == context.DeadlineExceeded {
+			return nil, status.Errorf(codes.DeadlineExceeded, "VM %q didn't start within %s: %s", request.VMID, timeout, err)
+		}
+		return nil, errors.Wrap(err, "failed to create VM")
 	}
 
 	// creating the VM succeeded, setup monitors and publish events to celebrate
diff --git a/tools/image-builder/Makefile b/tools/image-builder/Makefile
index 6e09ae4..ca56d32 100644
--- a/tools/image-builder/Makefile
+++ b/tools/image-builder/Makefile
@@ -37,7 +37,7 @@ fi
 touch --reference=debootstrap_stamp --no-create "$(WORKDIR)"
 endef
 
-all: rootfs.img rootfs-slow-reboot.img
+all: rootfs.img rootfs-slow-boot.img rootfs-slow-reboot.img
 
 $(WORKDIR):
 	mkdir $(WORKDIR)
@@ -67,6 +67,13 @@ endif
 rootfs.img: files_common_stamp files_debootstrap_stamp files_ephemeral_stamp
 	mksquashfs "$(WORKDIR)" rootfs.img -noappend
 
+# Intentionally break the rootfs to simulate the case where CreateVM is taking longer than the minimal timeout
+rootfs-slow-boot.img: files_common_stamp files_debootstrap_stamp files_ephemeral_stamp
+	rm -fr tmp/$@
+	cp -a "$(WORKDIR)" tmp/$@
+	echo 'ExecStartPre=/bin/sleep 30' >> tmp/$@/etc/systemd/system/firecracker-agent.service
+	mksquashfs tmp/$@ $@ -noappend
+
 # Intentionally break the rootfs to simulate the case where StopVM is taking longer than the minimal timeout
 rootfs-slow-reboot.img: files_common_stamp files_debootstrap_stamp files_ephemeral_stamp
 	rm -fr tmp/$@
@@ -98,7 +105,7 @@ builder_stamp:
 clean:
 	-rm -f *stamp
 	if [ $(UID) -eq 0 ]; then \
-	  rm -f rootfs.img rootfs-slow-reboot.img ;\
+	  rm -f rootfs.img rootfs-slow-boot.img rootfs-slow-reboot.img ;\
 	else \
 	  $(MAKE) clean-in-docker ;\
 	fi


Test patch:
diff --git a/runtime/service_integ_test.go b/runtime/service_integ_test.go
index 693bd5b..a9b7fc2 100644
--- a/runtime/service_integ_test.go
+++ b/runtime/service_integ_test.go
@@ -1047,6 +1047,7 @@ func TestDriveMount_Isolated(t *testing.T) {
 			UID: 300000,
 			GID: 300000,
 		},
+		TimeoutSeconds: 60,
 	})
 	require.NoError(t, err, "failed to create vm")
 
@@ -1787,3 +1788,77 @@ func findProcWithName(name string) func(context.Context, *process.Process) (bool
 		return filepath.Base(processExecutable) == name, nil
 	}
 }
+
+func TestCreateVM_Isolated(t *testing.T) {
+	prepareIntegTest(t)
+
+	client, err := containerd.New(containerdSockPath, containerd.WithDefaultRuntime(firecrackerRuntime))
+	require.NoError(t, err, "unable to create client to containerd service at %s, is containerd running?", containerdSockPath)
+	defer client.Close()
+
+	ctx := namespaces.WithNamespace(context.Background(), "default")
+
+	pluginClient, err := ttrpcutil.NewClient(containerdSockPath + ".ttrpc")
+	require.NoError(t, err, "failed to create ttrpc client")
+
+	fcClient := fccontrol.NewFirecrackerClient(pluginClient.Client())
+
+	subtests := []struct {
+		name     string
+		request  proto.CreateVMRequest
+		validate func(*testing.T, error)
+	}{
+		{
+			name:    "Happy Case",
+			request: proto.CreateVMRequest{},
+			validate: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+		},
+		{
+			name: "Slow Root FS",
+			request: proto.CreateVMRequest{
+				RootDrive: &proto.FirecrackerRootDrive{
+					HostPath: "/var/lib/firecracker-containerd/runtime/rootfs-slow-boot.img",
+				},
+			},
+			validate: func(t *testing.T, err error) {
+				require.Error(t, err)
+				assert.Equal(t, codes.DeadlineExceeded, status.Code(err))
+				assert.Contains(t, err.Error(), "didn't start within 20s")
+			},
+		},
+		{
+			name: "Slow Root FS and Longer Timeout",
+			request: proto.CreateVMRequest{
+				RootDrive: &proto.FirecrackerRootDrive{
+					HostPath: "/var/lib/firecracker-containerd/runtime/rootfs-slow-boot.img",
+				},
+				TimeoutSeconds: 60,
+			},
+			validate: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+		},
+	}
+
+	for _, subtest := range subtests {
+		request := subtest.request
+		validate := subtest.validate
+
+		t.Run(subtest.name, func(t *testing.T) {
+			request.VMID = testNameToVMID(t.Name())
+
+			_, err = fcClient.CreateVM(ctx, &request)
+			validate(t, err)
+
+			if err != nil {
+				// No VM to stop.
+				return
+			}
+
+			_, err = fcClient.StopVM(ctx, &proto.StopVMRequest{VMID: request.VMID})
+			require.Equal(t, status.Code(err), codes.OK)
+		})
+	}
+}