From eee28b7f45c2d7beceb6559165fa153666d10ac3 Mon Sep 17 00:00:00 2001 From: Marc Sluiter Date: Tue, 8 Oct 2019 22:50:54 +0200 Subject: [PATCH] Prepared handler and launcher for suspend/resume Signed-off-by: Marc Sluiter --- cmd/virt-handler/virt-handler.go | 11 +- go.mod | 1 - go.sum | 9 +- pkg/handler-launcher-com/cmd/v1/cmd.pb.go | 141 +++++++++++++----- pkg/handler-launcher-com/cmd/v1/cmd.proto | 2 + pkg/virt-controller/watch/vm.go | 83 +++++++---- pkg/virt-handler/cmd-client/client.go | 10 ++ .../cmd-client/generated_mock_client.go | 20 +++ pkg/virt-handler/rest/BUILD.bazel | 7 +- pkg/virt-handler/rest/common.go | 43 ++++++ pkg/virt-handler/rest/console.go | 17 +-- pkg/virt-handler/rest/lifecycle.go | 91 +++++++++++ pkg/virt-handler/vm.go | 20 +++ pkg/virt-launcher/virtwrap/BUILD.bazel | 1 + .../virtwrap/cli/generated_mock_libvirt.go | 10 ++ pkg/virt-launcher/virtwrap/cli/libvirt.go | 1 + .../virtwrap/cmd-server/server.go | 34 +++++ .../virtwrap/generated_mock_manager.go | 22 +++ pkg/virt-launcher/virtwrap/manager.go | 133 ++++++++++++++++- .../src/kubevirt.io/client-go/api/v1/types.go | 7 + .../golang.org/x/sys/cpu/cpu_other_arm64.go | 11 ++ vendor/modules.txt | 2 +- 22 files changed, 578 insertions(+), 98 deletions(-) create mode 100644 pkg/virt-handler/rest/common.go create mode 100644 pkg/virt-handler/rest/lifecycle.go diff --git a/cmd/virt-handler/virt-handler.go b/cmd/virt-handler/virt-handler.go index 8ccfb98ef81e..d5c48d72ae42 100644 --- a/cmd/virt-handler/virt-handler.go +++ b/cmd/virt-handler/virt-handler.go @@ -312,6 +312,11 @@ func (app *virtHandlerApp) Run() { vmiInformer, ) + lifecycleHandler := rest.NewLifecycleHandler( + vmiInformer, + app.VirtShareDir, + ) + certsDirectory, err := ioutil.TempDir("", "certsdir") if err != nil { panic(err) @@ -335,7 +340,7 @@ func (app *virtHandlerApp) Run() { errCh := make(chan error) go app.runPrometheusServer(errCh, certStore) - go app.runConsoleServer(errCh, consoleHandler) + go app.runServer(errCh, consoleHandler, lifecycleHandler) // wait for one of the servers to exit <-errCh @@ -347,10 +352,12 @@ func (app *virtHandlerApp) runPrometheusServer(errCh chan error, certStore certi errCh <- http.ListenAndServeTLS(app.ServiceListen.Address(), certStore.CurrentPath(), certStore.CurrentPath(), nil) } -func (app *virtHandlerApp) runConsoleServer(errCh chan error, consoleHandler *rest.ConsoleHandler) { +func (app *virtHandlerApp) runServer(errCh chan error, consoleHandler *rest.ConsoleHandler, lifecycleHandler *rest.LifecycleHandler) { ws := new(restful.WebService) ws.Route(ws.GET("/v1/namespaces/{namespace}/virtualmachineinstances/{name}/console").To(consoleHandler.SerialHandler)) ws.Route(ws.GET("/v1/namespaces/{namespace}/virtualmachineinstances/{name}/vnc").To(consoleHandler.VNCHandler)) + ws.Route(ws.PUT("/v1/namespaces/{namespace}/virtualmachineinstances/{name}/suspend").To(lifecycleHandler.SuspendHandler)) + ws.Route(ws.PUT("/v1/namespaces/{namespace}/virtualmachineinstances/{name}/resume").To(lifecycleHandler.ResumeHandler)) restful.DefaultContainer.Add(ws) server := &http.Server{ Addr: fmt.Sprintf("%s:%d", app.ServiceListen.BindAddress, app.consoleServerPort), diff --git a/go.mod b/go.mod index dbc6132da89f..00c8dce8fc5a 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,6 @@ require ( github.com/onsi/gomega v1.5.1-0.20190515112211-6a48b4839f85 github.com/openshift/api v3.9.1-0.20190401220125-3a6077f1f910+incompatible github.com/openshift/client-go v0.0.0-20190401163519-84c2b942258a - github.com/openshift/custom-resource-status v0.0.0-20190822192428-e62f2f3b79f3 // indirect github.com/operator-framework/go-appr v0.0.0-20180917210448-f2aef88446f2 // indirect github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190128024246-5eb7ae5bdb7a github.com/operator-framework/operator-marketplace v0.0.0-20190508022032-93d436f211c1 diff --git a/go.sum b/go.sum index 8aa728acd4a8..6892eacf2f91 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.9+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.1 h1:TyEMaK2xD/EcB0385QcvX/OvI2XI7s4SJEI2EhZFfEU= -github.com/coreos/go-iptables v0.4.1/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.4.3 h1:jJg1aFuhCqWbgBl1VTqgTHG5faPM60A5JDMjQ2HYv+A= +github.com/coreos/go-iptables v0.4.3/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -137,8 +137,6 @@ github.com/go-openapi/strfmt v0.18.0 h1:FqqmmVCKn3di+ilU/+1m957T1CnMz3IteVUcV3aG github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8= -github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= @@ -482,15 +480,12 @@ k8s.io/kube-aggregator v0.0.0-20181204002017-122bac39d429/go.mod h1:8sbzT4QQKDEm k8s.io/kube-aggregator v0.0.0-20190228175259-3e0149950b0e h1:EDJ6HxLCxUAKNRG6zampFM0/uxaeJS4c4PmwHLXO6p0= k8s.io/kube-aggregator v0.0.0-20190228175259-3e0149950b0e/go.mod h1:8sbzT4QQKDEmSCIbfqjV0sd97GpUT7A4W626sBiYJmU= k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI= -k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058 h1:di3XCwddOR9cWBNpfgXaskhh6cgJuwcK54rvtwUaC10= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kubernetes v1.11.7-beta.0.0.20181219023948-b875d52ea96d/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/kubernetes v1.11.8-beta.0.0.20190124204751-3a10094374f2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw= k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -kubevirt.io/containerized-data-importer v1.8.1-0.20190516083534-83c12eaae2ed/go.mod h1:qF594BtRRkruyrqLwt3zbLCWdPIQNs1qWh4LR1cOzy0= kubevirt.io/containerized-data-importer v1.10.6 h1:xkqLb48pkbdoY8gB2VDP2o+KXpO18tgQuLjcXNn0qAI= kubevirt.io/containerized-data-importer v1.10.6/go.mod h1:qF594BtRRkruyrqLwt3zbLCWdPIQNs1qWh4LR1cOzy0= kubevirt.io/qe-tools v0.1.3-0.20190512140058-934db0579e0c h1:9FawJ0jS2NvLLE3oyBvnEgv+vVl0aIVKrgXuhLoWfjw= diff --git a/pkg/handler-launcher-com/cmd/v1/cmd.pb.go b/pkg/handler-launcher-com/cmd/v1/cmd.pb.go index 828cd5195104..5f6a33aaa2c0 100644 --- a/pkg/handler-launcher-com/cmd/v1/cmd.pb.go +++ b/pkg/handler-launcher-com/cmd/v1/cmd.pb.go @@ -272,6 +272,8 @@ const _ = grpc.SupportPackageIsVersion4 type CmdClient interface { SyncVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) + SuspendVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) + ResumeVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) ShutdownVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) KillVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) DeleteVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) @@ -300,6 +302,24 @@ func (c *cmdClient) SyncVirtualMachine(ctx context.Context, in *VMIRequest, opts return out, nil } +func (c *cmdClient) SuspendVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := grpc.Invoke(ctx, "/kubevirt.cmd.v1.Cmd/SuspendVirtualMachine", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cmdClient) ResumeVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := grpc.Invoke(ctx, "/kubevirt.cmd.v1.Cmd/ResumeVirtualMachine", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *cmdClient) ShutdownVirtualMachine(ctx context.Context, in *VMIRequest, opts ...grpc.CallOption) (*Response, error) { out := new(Response) err := grpc.Invoke(ctx, "/kubevirt.cmd.v1.Cmd/ShutdownVirtualMachine", in, out, c.cc, opts...) @@ -385,6 +405,8 @@ func (c *cmdClient) Ping(ctx context.Context, in *EmptyRequest, opts ...grpc.Cal type CmdServer interface { SyncVirtualMachine(context.Context, *VMIRequest) (*Response, error) + SuspendVirtualMachine(context.Context, *VMIRequest) (*Response, error) + ResumeVirtualMachine(context.Context, *VMIRequest) (*Response, error) ShutdownVirtualMachine(context.Context, *VMIRequest) (*Response, error) KillVirtualMachine(context.Context, *VMIRequest) (*Response, error) DeleteVirtualMachine(context.Context, *VMIRequest) (*Response, error) @@ -418,6 +440,42 @@ func _Cmd_SyncVirtualMachine_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _Cmd_SuspendVirtualMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VMIRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CmdServer).SuspendVirtualMachine(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/kubevirt.cmd.v1.Cmd/SuspendVirtualMachine", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CmdServer).SuspendVirtualMachine(ctx, req.(*VMIRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Cmd_ResumeVirtualMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VMIRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CmdServer).ResumeVirtualMachine(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/kubevirt.cmd.v1.Cmd/ResumeVirtualMachine", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CmdServer).ResumeVirtualMachine(ctx, req.(*VMIRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Cmd_ShutdownVirtualMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(VMIRequest) if err := dec(in); err != nil { @@ -588,6 +646,14 @@ var _Cmd_serviceDesc = grpc.ServiceDesc{ MethodName: "SyncVirtualMachine", Handler: _Cmd_SyncVirtualMachine_Handler, }, + { + MethodName: "SuspendVirtualMachine", + Handler: _Cmd_SuspendVirtualMachine_Handler, + }, + { + MethodName: "ResumeVirtualMachine", + Handler: _Cmd_ResumeVirtualMachine_Handler, + }, { MethodName: "ShutdownVirtualMachine", Handler: _Cmd_ShutdownVirtualMachine_Handler, @@ -632,41 +698,42 @@ var _Cmd_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("pkg/handler-launcher-com/cmd/v1/cmd.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 562 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x95, 0xdd, 0x4e, 0xdb, 0x30, - 0x14, 0xc7, 0x29, 0xed, 0x4a, 0x39, 0xad, 0x3a, 0x64, 0x0a, 0xcb, 0x98, 0x10, 0x2c, 0x9a, 0xd0, - 0x76, 0x41, 0xab, 0x76, 0xda, 0xed, 0x34, 0x15, 0xa6, 0x89, 0xa1, 0x00, 0x4a, 0x51, 0xa7, 0xed, - 0x66, 0x32, 0x89, 0x69, 0xad, 0x26, 0x76, 0x66, 0x3b, 0x99, 0xfa, 0x0a, 0x7b, 0xa6, 0x3d, 0xdc, - 0x14, 0x27, 0x29, 0xa4, 0x29, 0xa0, 0xa9, 0xbd, 0x6a, 0xce, 0xd7, 0xef, 0xfc, 0x7d, 0x7c, 0xac, - 0xc2, 0xbb, 0x60, 0x32, 0xea, 0x8c, 0x31, 0x73, 0x3d, 0x22, 0x8e, 0x3d, 0x1c, 0x32, 0x67, 0x4c, - 0xc4, 0xb1, 0xc3, 0xfd, 0x8e, 0xe3, 0xbb, 0x9d, 0xa8, 0x1b, 0xff, 0xb4, 0x03, 0xc1, 0x15, 0x47, - 0xcf, 0x27, 0xe1, 0x0d, 0x89, 0xa8, 0x50, 0xed, 0xd8, 0x17, 0x75, 0xcd, 0x03, 0x28, 0x0f, 0xad, - 0x33, 0x64, 0xc0, 0x46, 0xe4, 0xd3, 0xaf, 0x92, 0x33, 0xa3, 0x74, 0x58, 0x7a, 0xdb, 0xb0, 0x33, - 0xd3, 0xfc, 0x53, 0x82, 0xea, 0xc0, 0xea, 0x53, 0x2e, 0x91, 0x09, 0x0d, 0x1f, 0xb3, 0xf0, 0x16, - 0x3b, 0x2a, 0x14, 0x44, 0xe8, 0xcc, 0x4d, 0x3b, 0xe7, 0x8b, 0x41, 0x81, 0xe0, 0x6e, 0xe8, 0x28, - 0x63, 0x5d, 0x87, 0x33, 0x53, 0xb7, 0x20, 0x42, 0x52, 0xce, 0x8c, 0x72, 0x12, 0x49, 0x4d, 0xb4, - 0x05, 0x65, 0x39, 0x09, 0x8d, 0x8a, 0xf6, 0xc6, 0x9f, 0x68, 0x17, 0xaa, 0xb7, 0xd8, 0xa7, 0xde, - 0xd4, 0x78, 0xa6, 0x9d, 0xa9, 0x65, 0xba, 0xb0, 0x33, 0xa4, 0x42, 0x85, 0xd8, 0xb3, 0xb0, 0x33, - 0xa6, 0x8c, 0x5c, 0x06, 0x8a, 0x72, 0x26, 0xd1, 0x39, 0xb4, 0xf2, 0x81, 0x44, 0xb2, 0x96, 0x58, - 0xef, 0xbd, 0x68, 0xcf, 0x1d, 0xbb, 0x9d, 0x84, 0xed, 0x85, 0x45, 0x66, 0x04, 0x30, 0xb4, 0xce, - 0x6c, 0xf2, 0x2b, 0x24, 0x52, 0xa1, 0x23, 0x28, 0x47, 0x3e, 0x4d, 0x49, 0xad, 0x02, 0x29, 0xce, - 0x8c, 0x13, 0xd0, 0x27, 0xd8, 0xe0, 0x89, 0x1a, 0x7d, 0xf2, 0x7a, 0xef, 0xa8, 0x98, 0xbb, 0x48, - 0xbb, 0x9d, 0x95, 0x99, 0xd7, 0xb0, 0x65, 0xd1, 0x91, 0xc0, 0xb1, 0xf5, 0xbf, 0xdd, 0x8d, 0x7c, - 0xf7, 0xc6, 0x1d, 0xb5, 0x09, 0x8d, 0xcf, 0x7e, 0xa0, 0xa6, 0x29, 0xd1, 0xfc, 0x08, 0x35, 0x9b, - 0xc8, 0x80, 0x33, 0x49, 0xe2, 0x2a, 0x19, 0x3a, 0x0e, 0x91, 0xc9, 0xa4, 0x6a, 0x76, 0x66, 0xc6, - 0x11, 0x9f, 0x48, 0x89, 0x47, 0x24, 0xbb, 0xc7, 0xd4, 0x34, 0x7f, 0x42, 0xf3, 0x94, 0xfb, 0x98, - 0xb2, 0x19, 0xe5, 0x03, 0xd4, 0x44, 0xfa, 0x9d, 0x0a, 0x7d, 0x59, 0x10, 0x9a, 0x25, 0xdb, 0xb3, - 0xd4, 0xf8, 0x92, 0x5d, 0x0d, 0x4a, 0x3b, 0xa4, 0x96, 0xc9, 0x60, 0x3b, 0x69, 0x30, 0x50, 0x58, - 0xc9, 0x65, 0xbb, 0x1c, 0x42, 0xdd, 0xbd, 0xa3, 0xa5, 0xad, 0xee, 0xbb, 0x7a, 0x7f, 0xab, 0x50, - 0x3e, 0xf1, 0x5d, 0x74, 0x01, 0x68, 0x30, 0x65, 0x4e, 0xfe, 0x92, 0xd0, 0xab, 0x85, 0x33, 0x4f, - 0x66, 0xb9, 0xf7, 0xb0, 0x02, 0x73, 0x0d, 0xd9, 0xb0, 0x3b, 0x18, 0x87, 0xca, 0xe5, 0xbf, 0xd9, - 0xca, 0x98, 0x17, 0x80, 0xce, 0xa9, 0xe7, 0xad, 0x8c, 0x77, 0x05, 0xad, 0x53, 0xe2, 0x11, 0x45, - 0x56, 0x46, 0xfc, 0x06, 0x3b, 0xc9, 0x12, 0xcf, 0x23, 0x5f, 0x17, 0xaa, 0xe6, 0x97, 0xfd, 0x71, - 0xf0, 0x25, 0x6c, 0xc7, 0xd7, 0x33, 0x2b, 0xba, 0xc6, 0x62, 0x44, 0xd4, 0x12, 0x4a, 0xbf, 0xc3, - 0xfe, 0x09, 0x66, 0x0e, 0x99, 0x9b, 0xe6, 0xac, 0xc1, 0x12, 0x68, 0x0b, 0x36, 0xbf, 0x10, 0x95, - 0x6c, 0x31, 0xda, 0x2f, 0x64, 0xde, 0x7f, 0x8f, 0x7b, 0x07, 0x85, 0x70, 0xfe, 0x79, 0xe9, 0x99, - 0x36, 0x67, 0x38, 0xbd, 0xb3, 0x4f, 0x31, 0xdf, 0x3c, 0xc0, 0xcc, 0xbd, 0x28, 0x73, 0x0d, 0xf5, - 0xa1, 0x72, 0x45, 0xd9, 0xe8, 0x29, 0xdc, 0x63, 0x67, 0xed, 0x57, 0x7e, 0xac, 0x47, 0xdd, 0x9b, - 0xaa, 0xfe, 0x7f, 0x79, 0xff, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x6f, 0xca, 0x9c, 0xe5, 0x8c, 0x06, - 0x00, 0x00, + // 579 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x95, 0x5b, 0x4f, 0x1a, 0x4f, + 0x14, 0xc0, 0x55, 0xfc, 0x03, 0x1e, 0x08, 0x7f, 0x33, 0x82, 0xdd, 0xda, 0x18, 0xed, 0xa6, 0x31, + 0xed, 0x83, 0x10, 0x68, 0xfa, 0xda, 0x34, 0x68, 0xd3, 0x58, 0xb3, 0x6a, 0x17, 0x43, 0xd3, 0xbe, + 0x34, 0xe3, 0xee, 0x08, 0x13, 0x76, 0x67, 0xb6, 0x73, 0xd9, 0x86, 0xaf, 0xd0, 0x6f, 0xd9, 0x6f, + 0xd2, 0xec, 0x0d, 0x5d, 0x16, 0x35, 0x0d, 0x3c, 0x31, 0xe7, 0xf6, 0x3b, 0x97, 0xe1, 0xcc, 0xc2, + 0x9b, 0x60, 0x32, 0xea, 0x8c, 0x31, 0x73, 0x3d, 0x22, 0x8e, 0x3d, 0xac, 0x99, 0x33, 0x26, 0xe2, + 0xd8, 0xe1, 0x7e, 0xc7, 0xf1, 0xdd, 0x4e, 0xd8, 0x8d, 0x7e, 0xda, 0x81, 0xe0, 0x8a, 0xa3, 0xff, + 0x27, 0xfa, 0x86, 0x84, 0x54, 0xa8, 0x76, 0xa4, 0x0b, 0xbb, 0xe6, 0x01, 0x94, 0x86, 0xd6, 0x19, + 0x32, 0xa0, 0x12, 0xfa, 0xf4, 0xb3, 0xe4, 0xcc, 0x58, 0x3f, 0x5c, 0x7f, 0x5d, 0xb7, 0x33, 0xd1, + 0xfc, 0xbd, 0x0e, 0xe5, 0x81, 0xd5, 0xa7, 0x5c, 0x22, 0x13, 0xea, 0x3e, 0x66, 0xfa, 0x16, 0x3b, + 0x4a, 0x0b, 0x22, 0x62, 0xcf, 0x2d, 0x3b, 0xa7, 0x8b, 0x40, 0x81, 0xe0, 0xae, 0x76, 0x94, 0xb1, + 0x11, 0x9b, 0x33, 0x31, 0x4e, 0x41, 0x84, 0xa4, 0x9c, 0x19, 0xa5, 0xc4, 0x92, 0x8a, 0x68, 0x1b, + 0x4a, 0x72, 0xa2, 0x8d, 0xcd, 0x58, 0x1b, 0x1d, 0xd1, 0x2e, 0x94, 0x6f, 0xb1, 0x4f, 0xbd, 0xa9, + 0xf1, 0x5f, 0xac, 0x4c, 0x25, 0xd3, 0x85, 0xd6, 0x90, 0x0a, 0xa5, 0xb1, 0x67, 0x61, 0x67, 0x4c, + 0x19, 0xb9, 0x0c, 0x14, 0xe5, 0x4c, 0xa2, 0x73, 0x68, 0xe6, 0x0d, 0x49, 0xc9, 0x71, 0x89, 0xb5, + 0xde, 0xb3, 0xf6, 0x5c, 0xdb, 0xed, 0xc4, 0x6c, 0x2f, 0x0c, 0x32, 0x43, 0x80, 0xa1, 0x75, 0x66, + 0x93, 0x9f, 0x9a, 0x48, 0x85, 0x8e, 0xa0, 0x14, 0xfa, 0x34, 0x25, 0x35, 0x0b, 0xa4, 0xc8, 0x33, + 0x72, 0x40, 0x1f, 0xa0, 0xc2, 0x93, 0x6a, 0xe2, 0xce, 0x6b, 0xbd, 0xa3, 0xa2, 0xef, 0xa2, 0xda, + 0xed, 0x2c, 0xcc, 0xbc, 0x86, 0x6d, 0x8b, 0x8e, 0x04, 0x8e, 0xa4, 0x7f, 0xcd, 0x6e, 0xe4, 0xb3, + 0xd7, 0xef, 0xa8, 0x0d, 0xa8, 0x7f, 0xf4, 0x03, 0x35, 0x4d, 0x89, 0xe6, 0x7b, 0xa8, 0xda, 0x44, + 0x06, 0x9c, 0x49, 0x12, 0x45, 0x49, 0xed, 0x38, 0x44, 0x26, 0x93, 0xaa, 0xda, 0x99, 0x18, 0x59, + 0x7c, 0x22, 0x25, 0x1e, 0x91, 0xec, 0x1e, 0x53, 0xd1, 0xfc, 0x01, 0x8d, 0x53, 0xee, 0x63, 0xca, + 0x66, 0x94, 0x77, 0x50, 0x15, 0xe9, 0x39, 0x2d, 0xf4, 0x79, 0xa1, 0xd0, 0xcc, 0xd9, 0x9e, 0xb9, + 0x46, 0x97, 0xec, 0xc6, 0xa0, 0x34, 0x43, 0x2a, 0x99, 0x0c, 0x76, 0x92, 0x04, 0x03, 0x85, 0x95, + 0x5c, 0x36, 0xcb, 0x21, 0xd4, 0xdc, 0x3b, 0x5a, 0x9a, 0xea, 0xbe, 0xaa, 0xf7, 0xa7, 0x02, 0xa5, + 0x13, 0xdf, 0x45, 0x17, 0x80, 0x06, 0x53, 0xe6, 0xe4, 0x2f, 0x09, 0xbd, 0x58, 0x38, 0xf3, 0x64, + 0x96, 0x7b, 0x0f, 0x57, 0x60, 0xae, 0xa1, 0x2f, 0xd0, 0x1a, 0x68, 0x19, 0x10, 0xe6, 0xae, 0x0c, + 0x79, 0x05, 0x4d, 0x9b, 0x48, 0xed, 0x93, 0x95, 0x11, 0x6d, 0xd8, 0x1d, 0x8c, 0xb5, 0x72, 0xf9, + 0x2f, 0xb6, 0x32, 0xe6, 0x05, 0xa0, 0x73, 0xea, 0x79, 0xab, 0xec, 0xfa, 0x94, 0x78, 0x44, 0xad, + 0xae, 0xeb, 0xaf, 0xd0, 0x4a, 0x36, 0x6d, 0x1e, 0xf9, 0xb2, 0x10, 0x35, 0xbf, 0x91, 0x8f, 0x83, + 0x2f, 0x61, 0x27, 0xfa, 0x0f, 0xcd, 0x82, 0xae, 0xb1, 0x18, 0x11, 0xb5, 0x44, 0xa5, 0xdf, 0x60, + 0xff, 0x04, 0x33, 0x87, 0xcc, 0x4d, 0x73, 0x96, 0x60, 0x09, 0xb4, 0x05, 0x5b, 0x9f, 0x88, 0x4a, + 0x56, 0x0d, 0xed, 0x17, 0x3c, 0xef, 0x3f, 0x1a, 0x7b, 0x07, 0x05, 0x73, 0xfe, 0x0d, 0x88, 0x67, + 0xda, 0x98, 0xe1, 0xe2, 0xc5, 0x7a, 0x8a, 0xf9, 0xea, 0x01, 0x66, 0x6e, 0xed, 0xcd, 0x35, 0xd4, + 0x87, 0xcd, 0x2b, 0xca, 0x46, 0x4f, 0xe1, 0x1e, 0xeb, 0xb5, 0xbf, 0xf9, 0x7d, 0x23, 0xec, 0xde, + 0x94, 0xe3, 0x8f, 0xe0, 0xdb, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x73, 0x70, 0x16, 0x29, 0x31, + 0x07, 0x00, 0x00, } diff --git a/pkg/handler-launcher-com/cmd/v1/cmd.proto b/pkg/handler-launcher-com/cmd/v1/cmd.proto index 5122435ab203..e7f83f126740 100644 --- a/pkg/handler-launcher-com/cmd/v1/cmd.proto +++ b/pkg/handler-launcher-com/cmd/v1/cmd.proto @@ -5,6 +5,8 @@ option go_package = "v1"; service Cmd { rpc SyncVirtualMachine(VMIRequest) returns (Response) {} + rpc SuspendVirtualMachine(VMIRequest) returns (Response) {} + rpc ResumeVirtualMachine(VMIRequest) returns (Response) {} rpc ShutdownVirtualMachine(VMIRequest) returns (Response) {} rpc KillVirtualMachine(VMIRequest) returns (Response) {} rpc DeleteVirtualMachine(VMIRequest) returns (Response) {} diff --git a/pkg/virt-controller/watch/vm.go b/pkg/virt-controller/watch/vm.go index 7089dba395ac..ceee7ae6980a 100644 --- a/pkg/virt-controller/watch/vm.go +++ b/pkg/virt-controller/watch/vm.go @@ -160,30 +160,30 @@ func (c *VMController) execute(key string) error { c.expectations.DeleteExpectations(key) return nil } - VM := obj.(*virtv1.VirtualMachine) + vm := obj.(*virtv1.VirtualMachine) - logger := log.Log.Object(VM) + logger := log.Log.Object(vm) - logger.V(4).Info("Started processing VM") + logger.V(4).Info("Started processing vm") // this must be first step in execution. Writing the object // when api version changes ensures our api stored version is updated. - if !controller.ObservedLatestApiVersionAnnotation(VM) { - vm := VM.DeepCopy() + if !controller.ObservedLatestApiVersionAnnotation(vm) { + vm := vm.DeepCopy() controller.SetLatestApiVersionAnnotation(vm) _, err = c.clientset.VirtualMachine(vm.ObjectMeta.Namespace).Update(vm) return err } //TODO default vm if necessary, the aggregated apiserver will do that in the future - if VM.Spec.Template == nil { + if vm.Spec.Template == nil { logger.Error("Invalid controller spec, will not re-enqueue.") return nil } needsSync := c.expectations.SatisfiedExpectations(key) && c.dataVolumeExpectations.SatisfiedExpectations(key) - vmKey, err := controller.KeyFunc(VM) + vmKey, err := controller.KeyFunc(vm) if err != nil { return err } @@ -191,19 +191,19 @@ func (c *VMController) execute(key string) error { // If any adoptions are attempted, we should first recheck for deletion with // an uncached quorum read sometime after listing VirtualMachines (see kubernetes/kubernetes#42639). canAdoptFunc := controller.RecheckDeletionTimestamp(func() (v1.Object, error) { - fresh, err := c.clientset.VirtualMachine(VM.ObjectMeta.Namespace).Get(VM.ObjectMeta.Name, &v1.GetOptions{}) + fresh, err := c.clientset.VirtualMachine(vm.ObjectMeta.Namespace).Get(vm.ObjectMeta.Name, &v1.GetOptions{}) if err != nil { return nil, err } - if fresh.ObjectMeta.UID != VM.ObjectMeta.UID { - return nil, fmt.Errorf("original VirtualMachine %v/%v is gone: got uid %v, wanted %v", VM.Namespace, VM.Name, fresh.UID, VM.UID) + if fresh.ObjectMeta.UID != vm.ObjectMeta.UID { + return nil, fmt.Errorf("original VirtualMachine %v/%v is gone: got uid %v, wanted %v", vm.Namespace, vm.Name, fresh.UID, vm.UID) } return fresh, nil }) cm := controller.NewVirtualMachineControllerRefManager( controller.RealVirtualMachineControl{ Clientset: c.clientset, - }, VM, nil, virtv1.VirtualMachineGroupVersionKind, canAdoptFunc) + }, vm, nil, virtv1.VirtualMachineGroupVersionKind, canAdoptFunc) var vmi *virtv1.VirtualMachineInstance vmiObj, exist, err := c.vmiInformer.GetStore().GetByKey(vmKey) @@ -223,7 +223,7 @@ func (c *VMController) execute(key string) error { } } - dataVolumes, err := c.listDataVolumesForVM(VM) + dataVolumes, err := c.listDataVolumesForVM(vm) if err != nil { logger.Reason(err).Error("Failed to fetch dataVolumes for namespace from cache.") return err @@ -239,21 +239,21 @@ func (c *VMController) execute(key string) error { var createErr error // Scale up or down, if all expected creates and deletes were report by the listener - if needsSync && VM.ObjectMeta.DeletionTimestamp == nil { + if needsSync && vm.ObjectMeta.DeletionTimestamp == nil { - dataVolumesReady, err := c.handleDataVolumes(VM, dataVolumes) + dataVolumesReady, err := c.handleDataVolumes(vm, dataVolumes) if err != nil { createErr = err } else if dataVolumesReady == true { - createErr = c.startStop(VM, vmi) + createErr = c.startStop(vm, vmi) } else { - log.Log.Object(VM).V(3).Infof("Waiting on DataVolumes to be ready. %d datavolumes found", len(dataVolumes)) + log.Log.Object(vm).V(3).Infof("Waiting on DataVolumes to be ready. %d datavolumes found", len(dataVolumes)) } } // If the controller is going to be deleted and the orphan finalizer is the next one, release the VMIs. Don't update the status // TODO: Workaround for https://github.com/kubernetes/kubernetes/issues/56348, remove it once it is fixed - if VM.ObjectMeta.DeletionTimestamp != nil && controller.HasFinalizer(VM, v1.FinalizerOrphanDependents) { + if vm.ObjectMeta.DeletionTimestamp != nil && controller.HasFinalizer(vm, v1.FinalizerOrphanDependents) { err = c.orphan(cm, vmi) if err != nil { return err @@ -265,7 +265,7 @@ func (c *VMController) execute(key string) error { logger.Reason(err).Error("Creating the VirtualMachine failed.") } - err = c.updateStatus(VM.DeepCopy(), vmi, createErr) + err = c.updateStatus(vm, vmi, createErr) if err != nil { logger.Reason(err).Error("Updating the VirtualMachine status failed.") return err @@ -1091,18 +1091,18 @@ func (c *VMController) removeCondition(vm *virtv1.VirtualMachine, cond virtv1.Vi vm.Status.Conditions = conds } -func (c *VMController) updateStatus(vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance, createErr error) error { - // Check if it is worth updating - errMatch := (createErr != nil) == c.hasCondition(vm, virtv1.VirtualMachineFailure) +func (c *VMController) updateStatus(vmOrig *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance, createErr error) error { + + vm := vmOrig.DeepCopy() + created := vmi != nil - createdMatch := created == vm.Status.Created + vm.Status.Created = created ready := false - if created { ready = controller.NewVirtualMachineInstanceConditionManager().HasConditionWithStatus(vmi, virtv1.VirtualMachineInstanceConditionType(k8score.PodReady), k8score.ConditionTrue) } - readyMatch := ready == vm.Status.Ready + vm.Status.Ready = ready runStrategy, err := vm.RunStrategy() if err != nil { @@ -1153,24 +1153,41 @@ func (c *VMController) updateStatus(vm *virtv1.VirtualMachine, vmi *virtv1.Virtu } } - if errMatch && createdMatch && readyMatch && !clearChangeRequest { - return nil - } - - // Set created and ready flags - vm.Status.Created = created - vm.Status.Ready = ready - if clearChangeRequest { vm.Status.StateChangeRequests = vm.Status.StateChangeRequests[1:] } // Add/Remove Failure condition if necessary + errMatch := (createErr != nil) == c.hasCondition(vm, virtv1.VirtualMachineFailure) if !(errMatch) { c.processFailure(vm, vmi, createErr) } - _, err = c.clientset.VirtualMachine(vm.ObjectMeta.Namespace).Update(vm) + // Add/Remove Paused condition (VMI suspended by user) + vmiCondManager := controller.NewVirtualMachineInstanceConditionManager() + if vmiCondManager.HasCondition(vmi, virtv1.VirtualMachineInstancePaused) { + if !c.hasCondition(vm, virtv1.VirtualMachinePaused) { + log.Log.Object(vm).V(3).Info("Adding paused condition") + now := v1.NewTime(time.Now()) + vm.Status.Conditions = append(vm.Status.Conditions, virtv1.VirtualMachineCondition{ + Type: virtv1.VirtualMachinePaused, + Status: k8score.ConditionTrue, + LastProbeTime: now, + LastTransitionTime: now, + Reason: "SuspendedByUser", + Message: "VMI was suspended by user", + }) + } + } else if c.hasCondition(vm, virtv1.VirtualMachinePaused) { + log.Log.Object(vm).V(3).Info("Removing paused condition") + c.removeCondition(vm, virtv1.VirtualMachinePaused) + } + + // only update if necessary + err = nil + if !reflect.DeepEqual(vm.Status, vmOrig.Status) { + _, err = c.clientset.VirtualMachine(vm.ObjectMeta.Namespace).Update(vm) + } return err } diff --git a/pkg/virt-handler/cmd-client/client.go b/pkg/virt-handler/cmd-client/client.go index 2e9bea5c814a..c23e0bfa6a61 100644 --- a/pkg/virt-handler/cmd-client/client.go +++ b/pkg/virt-handler/cmd-client/client.go @@ -71,6 +71,8 @@ type MigrationOptions struct { type LauncherClient interface { SyncVirtualMachine(vmi *v1.VirtualMachineInstance, options *cmdv1.VirtualMachineOptions) error + SuspendVirtualMachine(vmi *v1.VirtualMachineInstance) error + ResumeVirtualMachine(vmi *v1.VirtualMachineInstance) error SyncMigrationTarget(vmi *v1.VirtualMachineInstance) error ShutdownVirtualMachine(vmi *v1.VirtualMachineInstance) error KillVirtualMachine(vmi *v1.VirtualMachineInstance) error @@ -243,6 +245,14 @@ func (c *VirtLauncherClient) SyncVirtualMachine(vmi *v1.VirtualMachineInstance, return c.genericSendVMICmd("SyncVMI", c.v1client.SyncVirtualMachine, vmi, options) } +func (c *VirtLauncherClient) SuspendVirtualMachine(vmi *v1.VirtualMachineInstance) error { + return c.genericSendVMICmd("Suspend", c.v1client.SuspendVirtualMachine, vmi, &cmdv1.VirtualMachineOptions{}) +} + +func (c *VirtLauncherClient) ResumeVirtualMachine(vmi *v1.VirtualMachineInstance) error { + return c.genericSendVMICmd("Resume", c.v1client.ResumeVirtualMachine, vmi, &cmdv1.VirtualMachineOptions{}) +} + func (c *VirtLauncherClient) ShutdownVirtualMachine(vmi *v1.VirtualMachineInstance) error { return c.genericSendVMICmd("Shutdown", c.v1client.ShutdownVirtualMachine, vmi, &cmdv1.VirtualMachineOptions{}) } diff --git a/pkg/virt-handler/cmd-client/generated_mock_client.go b/pkg/virt-handler/cmd-client/generated_mock_client.go index 019fa1626e68..1a5e549ac90c 100644 --- a/pkg/virt-handler/cmd-client/generated_mock_client.go +++ b/pkg/virt-handler/cmd-client/generated_mock_client.go @@ -43,6 +43,26 @@ func (_mr *_MockLauncherClientRecorder) SyncVirtualMachine(arg0, arg1 interface{ return _mr.mock.ctrl.RecordCall(_mr.mock, "SyncVirtualMachine", arg0, arg1) } +func (_m *MockLauncherClient) SuspendVirtualMachine(vmi *v1.VirtualMachineInstance) error { + ret := _m.ctrl.Call(_m, "SuspendVirtualMachine", vmi) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockLauncherClientRecorder) SuspendVirtualMachine(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "SuspendVirtualMachine", arg0) +} + +func (_m *MockLauncherClient) ResumeVirtualMachine(vmi *v1.VirtualMachineInstance) error { + ret := _m.ctrl.Call(_m, "ResumeVirtualMachine", vmi) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockLauncherClientRecorder) ResumeVirtualMachine(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "ResumeVirtualMachine", arg0) +} + func (_m *MockLauncherClient) SyncMigrationTarget(vmi *v1.VirtualMachineInstance) error { ret := _m.ctrl.Call(_m, "SyncMigrationTarget", vmi) ret0, _ := ret[0].(error) diff --git a/pkg/virt-handler/rest/BUILD.bazel b/pkg/virt-handler/rest/BUILD.bazel index 76dbdd9fb0d5..2cae16fa0200 100644 --- a/pkg/virt-handler/rest/BUILD.bazel +++ b/pkg/virt-handler/rest/BUILD.bazel @@ -2,10 +2,15 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["console.go"], + srcs = [ + "common.go", + "console.go", + "lifecycle.go", + ], importpath = "kubevirt.io/kubevirt/pkg/virt-handler/rest", visibility = ["//visibility:public"], deps = [ + "//pkg/virt-handler/cmd-client:go_default_library", "//pkg/virt-handler/isolation:go_default_library", "//staging/src/kubevirt.io/client-go/api/v1:go_default_library", "//staging/src/kubevirt.io/client-go/kubecli:go_default_library", diff --git a/pkg/virt-handler/rest/common.go b/pkg/virt-handler/rest/common.go new file mode 100644 index 000000000000..e2bfb6b46704 --- /dev/null +++ b/pkg/virt-handler/rest/common.go @@ -0,0 +1,43 @@ +/* + * This file is part of the KubeVirt project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2019 Red Hat, Inc. + * + */ + +package rest + +import ( + "fmt" + "net/http" + + "github.com/emicklei/go-restful" + + "k8s.io/client-go/tools/cache" + + v1 "kubevirt.io/client-go/api/v1" +) + +func getVMI(request *restful.Request, vmiInformer cache.SharedIndexInformer) (*v1.VirtualMachineInstance, int, error) { + key := fmt.Sprintf("%s/%s", request.PathParameter("namespace"), request.PathParameter("name")) + vmiObj, vmiExists, err := vmiInformer.GetStore().GetByKey(key) + if err != nil { + return nil, http.StatusInternalServerError, err + } + if !vmiExists { + return nil, http.StatusNotFound, fmt.Errorf("VMI %s does not exist", key) + } + return vmiObj.(*v1.VirtualMachineInstance), 0, nil +} diff --git a/pkg/virt-handler/rest/console.go b/pkg/virt-handler/rest/console.go index 84f8e7bef296..ec24883831b2 100644 --- a/pkg/virt-handler/rest/console.go +++ b/pkg/virt-handler/rest/console.go @@ -20,7 +20,6 @@ package rest import ( - "fmt" "io" "net" "net/http" @@ -60,7 +59,7 @@ func NewConsoleHandler(podIsolationDetector isolation.PodIsolationDetector, vmiI } func (t *ConsoleHandler) VNCHandler(request *restful.Request, response *restful.Response) { - vmi, code, err := t.getVMI(request) + vmi, code, err := getVMI(request, t.vmiInformer) if err != nil { log.Log.Object(vmi).Reason(err).Error("Failed to retrieve VMI") response.WriteError(code, err) @@ -81,7 +80,7 @@ func (t *ConsoleHandler) VNCHandler(request *restful.Request, response *restful. } func (t *ConsoleHandler) SerialHandler(request *restful.Request, response *restful.Response) { - vmi, code, err := t.getVMI(request) + vmi, code, err := getVMI(request, t.vmiInformer) if err != nil { log.Log.Object(vmi).Reason(err).Error("Failed to retrieve VMI") response.WriteError(code, err) @@ -101,18 +100,6 @@ func (t *ConsoleHandler) SerialHandler(request *restful.Request, response *restf t.stream(vmi, request, response, unixSocketPath, stopCh, cleanup) } -func (t *ConsoleHandler) getVMI(request *restful.Request) (*v1.VirtualMachineInstance, int, error) { - key := fmt.Sprintf("%s/%s", request.PathParameter("namespace"), request.PathParameter("name")) - vmiObj, vmiExists, err := t.vmiInformer.GetStore().GetByKey(key) - if err != nil { - return nil, http.StatusInternalServerError, err - } - if !vmiExists { - return nil, http.StatusNotFound, fmt.Errorf("VMI %s does not exist", key) - } - return vmiObj.(*v1.VirtualMachineInstance), 0, nil -} - func newStopChan(uid types.UID, lock *sync.Mutex, stopChans map[types.UID](chan struct{})) chan struct{} { lock.Lock() defer lock.Unlock() diff --git a/pkg/virt-handler/rest/lifecycle.go b/pkg/virt-handler/rest/lifecycle.go new file mode 100644 index 000000000000..d0d41a215322 --- /dev/null +++ b/pkg/virt-handler/rest/lifecycle.go @@ -0,0 +1,91 @@ +/* + * This file is part of the KubeVirt project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2019 Red Hat, Inc. + * + */ +package rest + +import ( + "net/http" + + "github.com/emicklei/go-restful" + + cmdclient "kubevirt.io/kubevirt/pkg/virt-handler/cmd-client" + + "k8s.io/client-go/tools/cache" + + "kubevirt.io/client-go/log" +) + +type LifecycleHandler struct { + vmiInformer cache.SharedIndexInformer + virtShareDir string +} + +func NewLifecycleHandler(vmiInformer cache.SharedIndexInformer, virtShareDir string) *LifecycleHandler { + return &LifecycleHandler{ + vmiInformer: vmiInformer, + virtShareDir: virtShareDir, + } +} + +func (lh *LifecycleHandler) SuspendHandler(request *restful.Request, response *restful.Response) { + vmi, code, err := getVMI(request, lh.vmiInformer) + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Failed to retrieve VMI") + response.WriteError(code, err) + return + } + + // TODO do some checks if we can supend? + + sockFile := cmdclient.SocketFromUID(lh.virtShareDir, string(vmi.GetUID())) + client, err := cmdclient.NewClient(sockFile) + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Failed to connect cmd client") + response.WriteError(http.StatusInternalServerError, err) + } + + err = client.SuspendVirtualMachine(vmi) + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Failed to suspend VMI") + response.WriteError(http.StatusInternalServerError, err) + } +} + +func (lh *LifecycleHandler) ResumeHandler(request *restful.Request, response *restful.Response) { + vmi, code, err := getVMI(request, lh.vmiInformer) + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Failed to retrieve VMI") + response.WriteError(code, err) + return + } + + // TODO do some checks if we can resume? + + sockFile := cmdclient.SocketFromUID(lh.virtShareDir, string(vmi.GetUID())) + client, err := cmdclient.NewClient(sockFile) + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Failed to connect cmd client") + response.WriteError(http.StatusInternalServerError, err) + } + + err = client.ResumeVirtualMachine(vmi) + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Failed to suspend VMI") + response.WriteError(http.StatusInternalServerError, err) + } +} diff --git a/pkg/virt-handler/vm.go b/pkg/virt-handler/vm.go index 5f67a3a92c8a..4eb4425ff33a 100644 --- a/pkg/virt-handler/vm.go +++ b/pkg/virt-handler/vm.go @@ -33,6 +33,7 @@ import ( k8sv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v12 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" @@ -490,6 +491,25 @@ func (d *VirtualMachineController) updateVMIStatus(vmi *v1.VirtualMachineInstanc condManager.RemoveCondition(vmi, v1.VirtualMachineInstanceAgentConnected) } + // Update paused condition in case VMI was suspended / resumed + if domain != nil && domain.Status.Status == api.Paused && domain.Status.Reason == api.ReasonPausedUser { + if !condManager.HasCondition(vmi, v1.VirtualMachineInstancePaused) { + log.Log.Object(vmi).V(3).Info("Adding paused condition") + now := metav1.NewTime(time.Now()) + vmi.Status.Conditions = append(vmi.Status.Conditions, v1.VirtualMachineInstanceCondition{ + Type: v1.VirtualMachineInstancePaused, + Status: k8sv1.ConditionTrue, + LastProbeTime: now, + LastTransitionTime: now, + Reason: "SuspendedByUser", + Message: "VMI was suspended by user", + }) + } + } else if condManager.HasCondition(vmi, v1.VirtualMachineInstancePaused) { + log.Log.Object(vmi).V(3).Info("Removing paused condition") + condManager.RemoveCondition(vmi, v1.VirtualMachineInstancePaused) + } + condManager.CheckFailure(vmi, syncError, "Synchronizing with the Domain failed.") if !reflect.DeepEqual(oldStatus, vmi.Status) { diff --git a/pkg/virt-launcher/virtwrap/BUILD.bazel b/pkg/virt-launcher/virtwrap/BUILD.bazel index 85b23a557955..ca7ef6a04ca3 100644 --- a/pkg/virt-launcher/virtwrap/BUILD.bazel +++ b/pkg/virt-launcher/virtwrap/BUILD.bazel @@ -34,6 +34,7 @@ go_library( "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", ], ) diff --git a/pkg/virt-launcher/virtwrap/cli/generated_mock_libvirt.go b/pkg/virt-launcher/virtwrap/cli/generated_mock_libvirt.go index 23d3551d8e31..512e23eb16db 100644 --- a/pkg/virt-launcher/virtwrap/cli/generated_mock_libvirt.go +++ b/pkg/virt-launcher/virtwrap/cli/generated_mock_libvirt.go @@ -253,6 +253,16 @@ func (_mr *_MockVirDomainRecorder) Create() *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Create") } +func (_m *MockVirDomain) Suspend() error { + ret := _m.ctrl.Call(_m, "Suspend") + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockVirDomainRecorder) Suspend() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Suspend") +} + func (_m *MockVirDomain) Resume() error { ret := _m.ctrl.Call(_m, "Resume") ret0, _ := ret[0].(error) diff --git a/pkg/virt-launcher/virtwrap/cli/libvirt.go b/pkg/virt-launcher/virtwrap/cli/libvirt.go index 0e427f0b3e2f..8ec291b15e3a 100644 --- a/pkg/virt-launcher/virtwrap/cli/libvirt.go +++ b/pkg/virt-launcher/virtwrap/cli/libvirt.go @@ -332,6 +332,7 @@ func (l *LibvirtConnection) checkConnectionLost(err error) { type VirDomain interface { GetState() (libvirt.DomainState, int, error) Create() error + Suspend() error Resume() error DestroyFlags(flags libvirt.DomainDestroyFlags) error ShutdownFlags(flags libvirt.DomainShutdownFlags) error diff --git a/pkg/virt-launcher/virtwrap/cmd-server/server.go b/pkg/virt-launcher/virtwrap/cmd-server/server.go index 2dc445d3bef0..0c12f143073c 100644 --- a/pkg/virt-launcher/virtwrap/cmd-server/server.go +++ b/pkg/virt-launcher/virtwrap/cmd-server/server.go @@ -168,6 +168,40 @@ func (l *Launcher) SyncVirtualMachine(ctx context.Context, request *cmdv1.VMIReq return response, nil } +func (l *Launcher) SuspendVirtualMachine(ctx context.Context, request *cmdv1.VMIRequest) (*cmdv1.Response, error) { + vmi, response := getVMIFromRequest(request.Vmi) + if !response.Success { + return response, nil + } + + if _, err := l.domainManager.SuspendVMI(vmi); err != nil { + log.Log.Object(vmi).Reason(err).Errorf("Failed to suspend vmi") + response.Success = false + response.Message = getErrorMessage(err) + return response, nil + } + + log.Log.Object(vmi).Info("Suspended vmi") + return response, nil +} + +func (l *Launcher) ResumeVirtualMachine(ctx context.Context, request *cmdv1.VMIRequest) (*cmdv1.Response, error) { + vmi, response := getVMIFromRequest(request.Vmi) + if !response.Success { + return response, nil + } + + if _, err := l.domainManager.ResumeVMI(vmi); err != nil { + log.Log.Object(vmi).Reason(err).Errorf("Failed to resume vmi") + response.Success = false + response.Message = getErrorMessage(err) + return response, nil + } + + log.Log.Object(vmi).Info("Resumed vmi") + return response, nil +} + func (l *Launcher) KillVirtualMachine(ctx context.Context, request *cmdv1.VMIRequest) (*cmdv1.Response, error) { vmi, response := getVMIFromRequest(request.Vmi) diff --git a/pkg/virt-launcher/virtwrap/generated_mock_manager.go b/pkg/virt-launcher/virtwrap/generated_mock_manager.go index 3b6df83d783e..b052ff070d9c 100644 --- a/pkg/virt-launcher/virtwrap/generated_mock_manager.go +++ b/pkg/virt-launcher/virtwrap/generated_mock_manager.go @@ -45,6 +45,28 @@ func (_mr *_MockDomainManagerRecorder) SyncVMI(arg0, arg1, arg2 interface{}) *go return _mr.mock.ctrl.RecordCall(_mr.mock, "SyncVMI", arg0, arg1, arg2) } +func (_m *MockDomainManager) SuspendVMI(_param0 *v1.VirtualMachineInstance) (*api.DomainSpec, error) { + ret := _m.ctrl.Call(_m, "SuspendVMI", _param0) + ret0, _ := ret[0].(*api.DomainSpec) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockDomainManagerRecorder) SuspendVMI(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "SuspendVMI", arg0) +} + +func (_m *MockDomainManager) ResumeVMI(_param0 *v1.VirtualMachineInstance) (*api.DomainSpec, error) { + ret := _m.ctrl.Call(_m, "ResumeVMI", _param0) + ret0, _ := ret[0].(*api.DomainSpec) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockDomainManagerRecorder) ResumeVMI(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "ResumeVMI", arg0) +} + func (_m *MockDomainManager) KillVMI(_param0 *v1.VirtualMachineInstance) error { ret := _m.ctrl.Call(_m, "KillVMI", _param0) ret0, _ := ret[0].(error) diff --git a/pkg/virt-launcher/virtwrap/manager.go b/pkg/virt-launcher/virtwrap/manager.go index f213b2bd32d8..657344f4b821 100644 --- a/pkg/virt-launcher/virtwrap/manager.go +++ b/pkg/virt-launcher/virtwrap/manager.go @@ -43,6 +43,7 @@ import ( k8sv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" utilwait "k8s.io/apimachinery/pkg/util/wait" v1 "kubevirt.io/client-go/api/v1" @@ -71,6 +72,8 @@ const vgpuEnvPrefix = "VGPU_PASSTHROUGH_DEVICES" type DomainManager interface { SyncVMI(*v1.VirtualMachineInstance, bool, *cmdv1.VirtualMachineOptions) (*api.DomainSpec, error) + SuspendVMI(*v1.VirtualMachineInstance) (*api.DomainSpec, error) + ResumeVMI(*v1.VirtualMachineInstance) (*api.DomainSpec, error) KillVMI(*v1.VirtualMachineInstance) error DeleteVMI(*v1.VirtualMachineInstance) error SignalShutdownVMI(*v1.VirtualMachineInstance) error @@ -90,6 +93,7 @@ type LibvirtDomainManager struct { virtShareDir string notifier *eventsclient.Notifier lessPVCSpaceToleration int + suspended suspendedVMIs } type migrationDisks struct { @@ -97,12 +101,38 @@ type migrationDisks struct { generated map[string]bool } +type suspendedVMIs struct { + suspended map[types.UID]bool +} + +func (s suspendedVMIs) add(uid types.UID) { + // implicitly locked by domainModifyLock + if _, ok := s.suspended[uid]; !ok { + s.suspended[uid] = true + } +} + +func (s suspendedVMIs) remove(uid types.UID) { + // implicitly locked by domainModifyLock + if _, ok := s.suspended[uid]; ok { + delete(s.suspended, uid) + } +} + +func (s suspendedVMIs) contains(uid types.UID) bool { + _, ok := s.suspended[uid] + return ok +} + func NewLibvirtDomainManager(connection cli.Connection, virtShareDir string, notifier *eventsclient.Notifier, lessPVCSpaceToleration int) (DomainManager, error) { manager := LibvirtDomainManager{ virConn: connection, virtShareDir: virtShareDir, notifier: notifier, lessPVCSpaceToleration: lessPVCSpaceToleration, + suspended: suspendedVMIs{ + suspended: make(map[types.UID]bool, 0), + }, } return &manager, nil @@ -1022,7 +1052,7 @@ func (l *LibvirtDomainManager) SyncVMI(vmi *v1.VirtualMachineInstance, useEmulat return nil, err } logger.Info("Domain started.") - } else if cli.IsPaused(domState) { + } else if cli.IsPaused(domState) && !l.suspended.contains(vmi.UID) { // TODO: if state change reason indicates a system error, we could try something smarter err := dom.Resume() if err != nil { @@ -1085,6 +1115,107 @@ func (l *LibvirtDomainManager) getDomainSpec(dom cli.VirDomain) (*api.DomainSpec return util.GetDomainSpec(state, dom) } +func (l *LibvirtDomainManager) SuspendVMI(vmi *v1.VirtualMachineInstance) (*api.DomainSpec, error) { + l.domainModifyLock.Lock() + defer l.domainModifyLock.Unlock() + + logger := log.Log.Object(vmi) + + domName := util.VMINamespaceKeyFunc(vmi) + dom, err := l.virConn.LookupDomainByName(domName) + if err != nil { + // If the VirtualMachineInstance does not exist, we are done + if domainerrors.IsNotFound(err) { + return nil, fmt.Errorf("Domain not found.") + } else { + log.Log.Object(vmi).Reason(err).Error("Getting the domain failed during suspend.") + return nil, err + } + } + defer dom.Free() + + domState, _, err := dom.GetState() + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Getting the domain state failed.") + return nil, err + } + + if domState == libvirt.DOMAIN_RUNNING { + err = dom.Suspend() + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Signalling suspension failed.") + return nil, err + } + log.Log.Object(vmi).Infof("Signaled suspend for %s", vmi.GetObjectMeta().GetName()) + l.suspended.add(vmi.UID) + } else { + log.Log.Object(vmi).Infof("Domain is not running for %s", vmi.GetObjectMeta().GetName()) + } + + xmlstr, err := dom.GetXMLDesc(0) + if err != nil { + return nil, err + } + var newSpec api.DomainSpec + err = xml.Unmarshal([]byte(xmlstr), &newSpec) + if err != nil { + logger.Reason(err).Error("Parsing domain XML failed.") + return nil, err + } + return &newSpec, nil +} + +func (l *LibvirtDomainManager) ResumeVMI(vmi *v1.VirtualMachineInstance) (*api.DomainSpec, error) { + l.domainModifyLock.Lock() + defer l.domainModifyLock.Unlock() + + logger := log.Log.Object(vmi) + + domName := util.VMINamespaceKeyFunc(vmi) + dom, err := l.virConn.LookupDomainByName(domName) + if err != nil { + // If the VirtualMachineInstance does not exist, we are done + if domainerrors.IsNotFound(err) { + return nil, fmt.Errorf("Domain not found.") + } else { + log.Log.Object(vmi).Reason(err).Error("Getting the domain failed during resume.") + return nil, err + } + } + defer dom.Free() + + domState, _, err := dom.GetState() + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Getting the domain state failed.") + return nil, err + } + + if domState == libvirt.DOMAIN_PAUSED { + err = dom.Resume() + if err != nil { + log.Log.Object(vmi).Reason(err).Error("Signalling resume failed.") + return nil, err + } + log.Log.Object(vmi).Infof("Signaled resume for %s", vmi.GetObjectMeta().GetName()) + l.suspended.remove(vmi.UID) + + } else { + log.Log.Object(vmi).Infof("Domain is not paused for %s", vmi.GetObjectMeta().GetName()) + } + + xmlstr, err := dom.GetXMLDesc(0) + if err != nil { + return nil, err + } + var newSpec api.DomainSpec + err = xml.Unmarshal([]byte(xmlstr), &newSpec) + if err != nil { + logger.Reason(err).Error("Parsing domain XML failed.") + return nil, err + } + return &newSpec, nil +} + func (l *LibvirtDomainManager) SignalShutdownVMI(vmi *v1.VirtualMachineInstance) error { l.domainModifyLock.Lock() defer l.domainModifyLock.Unlock() diff --git a/staging/src/kubevirt.io/client-go/api/v1/types.go b/staging/src/kubevirt.io/client-go/api/v1/types.go index de61153c0b57..c2dfb965d331 100644 --- a/staging/src/kubevirt.io/client-go/api/v1/types.go +++ b/staging/src/kubevirt.io/client-go/api/v1/types.go @@ -333,6 +333,9 @@ const ( // this is reported as false. VirtualMachineInstanceSynchronized VirtualMachineInstanceConditionType = "Synchronized" + // If the VMI was suspended by the user, this is reported as true. + VirtualMachineInstancePaused VirtualMachineInstanceConditionType = "Paused" + // Reflects whether the QEMU guest agent is connected through the channel VirtualMachineInstanceAgentConnected VirtualMachineInstanceConditionType = "AgentConnected" @@ -1088,6 +1091,10 @@ const ( // fails to be created due to insufficient quota, limit ranges, pod security policy, node selectors, // etc. or deleted due to kubelet being down or finalizers are failing. VirtualMachineFailure VirtualMachineConditionType = "Failure" + + // VirtualMachinePaused is added in a virtual machine when its vmi + // signals with its own condition that it is paused. + VirtualMachinePaused VirtualMachineConditionType = "Paused" ) // --- diff --git a/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go index e69de29bb2d1..dd1e76dc921a 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go @@ -0,0 +1,11 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !linux,arm64 + +package cpu + +const cacheLineSize = 64 + +func doinit() {} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6995c37c6ef1..25520a9a3800 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -181,9 +181,9 @@ github.com/modern-go/concurrent # github.com/modern-go/reflect2 v1.0.1 github.com/modern-go/reflect2 # github.com/onsi/ginkgo v1.8.0 -github.com/onsi/ginkgo/types github.com/onsi/ginkgo github.com/onsi/ginkgo/config +github.com/onsi/ginkgo/types github.com/onsi/ginkgo/extensions/table github.com/onsi/ginkgo/internal/codelocation github.com/onsi/ginkgo/internal/failer