From 1cad3aa6dde7ad9bf199422566bf1635e1f6247e Mon Sep 17 00:00:00 2001 From: Daniel Milchev Date: Fri, 12 Jan 2024 15:08:29 +0200 Subject: [PATCH] CLI Remove command improvements (#224) [#217] CLI Remove command improvements - Timeout was added to the remove command - Timeout was changed in the stop command - Changed default container stop timeout to duration string Signed-off-by: Daniel Milchev fixed-term.daniel.milchev@bosch.io --- .../api/services/containers/containers.pb.go | 395 +++++++++--------- .../api/services/containers/containers.proto | 1 + containerm/cli/cli_command_ctrs_list.go | 2 +- containerm/cli/cli_command_ctrs_remove.go | 26 +- .../cli/cli_command_ctrs_remove_test.go | 77 ++-- containerm/cli/cli_command_ctrs_stop.go | 21 +- containerm/cli/cli_command_ctrs_stop_test.go | 25 +- containerm/client/client.go | 4 +- containerm/client/client_api.go | 2 +- containerm/client/client_test.go | 29 +- containerm/ctr/ctrd_client_test.go | 4 +- containerm/daemon/daemon_command.go | 2 +- containerm/daemon/daemon_config.go | 29 +- containerm/daemon/daemon_config_default.go | 4 +- containerm/daemon/daemon_config_util.go | 8 +- containerm/daemon/daemon_test.go | 26 +- containerm/deployment/deployment_internal.go | 2 +- containerm/deployment/deployment_test.go | 10 +- containerm/mgr/mgr.go | 14 +- containerm/mgr/mgr_api.go | 2 +- containerm/mgr/mgr_internal.go | 5 +- containerm/mgr/mgr_internal_init.go | 4 +- containerm/mgr/mgr_opts.go | 19 +- containerm/mgr/mgr_opts_test.go | 18 +- containerm/mgr/mgr_test.go | 8 +- .../pkg/testutil/config/daemon-config.json | 2 +- ...-mgr-default-ctrs-stop-timeout-config.json | 5 + .../testutil/mocks/client/mock_client_api.go | 8 +- .../pkg/testutil/mocks/mgr/mock_mgr_api.go | 8 +- .../services/grpc_containers_service.go | 2 +- containerm/services/grpc_services_test.go | 25 +- containerm/things/features_container.go | 2 +- containerm/things/features_container_test.go | 6 +- ...es_rollouts_software_updatable_internal.go | 2 +- ...llouts_software_updatable_internal_test.go | 10 +- containerm/updateagent/update_operation.go | 2 +- .../updateagent/update_operation_test.go | 16 +- containerm/util/protobuf/convert_test.go | 29 +- .../util/protobuf/convert_to_internal_util.go | 2 +- .../util/protobuf/convert_to_proto_util.go | 6 + integration/testdata/list-test.yaml | 4 +- 41 files changed, 534 insertions(+), 332 deletions(-) create mode 100644 containerm/pkg/testutil/config/daemon-mgr-default-ctrs-stop-timeout-config.json diff --git a/containerm/api/services/containers/containers.pb.go b/containerm/api/services/containers/containers.pb.go index 3c11d11..60cd59b 100644 --- a/containerm/api/services/containers/containers.pb.go +++ b/containerm/api/services/containers/containers.pb.go @@ -12,7 +12,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.29.0 +// protoc-gen-go v1.32.0 // protoc v4.22.0 // source: api/services/containers/containers.proto @@ -920,7 +920,8 @@ type RemoveContainerRequest struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Whether the container should be removed disregarding its current state. - Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` + Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` + StopOptions *containers.StopOptions `protobuf:"bytes,3,opt,name=stopOptions,proto3" json:"stopOptions,omitempty"` } func (x *RemoveContainerRequest) Reset() { @@ -969,6 +970,13 @@ func (x *RemoveContainerRequest) GetForce() bool { return false } +func (x *RemoveContainerRequest) GetStopOptions() *containers.StopOptions { + if x != nil { + return x.StopOptions + } + return nil +} + type GetLogsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1200,177 +1208,185 @@ var file_api_services_containers_containers_proto_rawDesc = []byte{ 0x0a, 0x16, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3e, 0x0a, 0x16, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x34, 0x0a, 0x0e, - 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x61, - 0x69, 0x6c, 0x22, 0x23, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x32, 0x99, 0x13, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0xdd, 0x01, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x12, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, - 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x69, 0x2e, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, - 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xd4, 0x01, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xbc, 0x01, 0x0a, + 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x7c, 0x0a, + 0x0b, 0x73, 0x74, 0x6f, 0x70, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, + 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0b, + 0x73, 0x74, 0x6f, 0x70, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x34, 0x0a, 0x0e, 0x47, + 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x61, 0x69, + 0x6c, 0x22, 0x23, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x32, 0x99, 0x13, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0xdd, 0x01, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x12, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, + 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x69, 0x2e, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, + 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xd4, 0x01, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x65, 0x2e, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, + 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, + 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x66, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xd9, 0x01, 0x0a, + 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x67, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x73, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x66, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xd9, 0x01, - 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x67, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, - 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, - 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xdf, 0x01, 0x0a, 0x0a, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x67, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xdf, 0x01, 0x0a, 0x0a, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x67, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, + 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x66, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, + 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x88, 0x01, 0x0a, 0x05, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x12, 0x67, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0xe1, 0x01, 0x0a, 0x06, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x12, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, + 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x73, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x69, 0x2e, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, + 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x41, 0x74, + 0x74, 0x61, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x86, 0x01, 0x0a, 0x04, 0x53, 0x74, + 0x6f, 0x70, 0x12, 0x66, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, + 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x8a, 0x01, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x68, 0x2e, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, + 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x8c, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x69, 0x2e, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, + 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x52, + 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x88, + 0x01, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, 0x65, 0x12, 0x67, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x66, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, - 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x88, 0x01, 0x0a, 0x05, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x67, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x50, 0x61, 0x75, 0x73, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x8c, 0x01, 0x0a, 0x07, 0x55, 0x6e, + 0x70, 0x61, 0x75, 0x73, 0x65, 0x12, 0x69, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0xe1, 0x01, 0x0a, 0x06, 0x41, 0x74, 0x74, 0x61, 0x63, - 0x68, 0x12, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, - 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x69, 0x2e, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, - 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x41, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x86, 0x01, 0x0a, 0x04, 0x53, - 0x74, 0x6f, 0x70, 0x12, 0x66, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, + 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x8a, 0x01, 0x0a, 0x06, 0x52, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x8a, 0x01, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x68, - 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, - 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x73, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x12, 0x8c, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x69, 0x2e, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, - 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, - 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, - 0x88, 0x01, 0x0a, 0x05, 0x50, 0x61, 0x75, 0x73, 0x65, 0x12, 0x67, 0x2e, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, - 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x50, 0x61, 0x75, - 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x8c, 0x01, 0x0a, 0x07, 0x55, - 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x12, 0x69, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, - 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, - 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x8a, 0x01, 0x0a, 0x06, 0x52, 0x65, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x8a, 0x01, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x12, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, - 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0xcd, 0x01, 0x0a, 0x04, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x60, 0x2e, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, - 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, - 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x61, - 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, - 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x73, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x30, 0x01, 0x42, 0x5d, 0x5a, 0x5b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x2d, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x8a, 0x01, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x12, 0x68, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, + 0x6c, 0x69, 0x70, 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x73, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0xcd, 0x01, 0x0a, 0x04, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x60, 0x2e, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, + 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x47, + 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x61, 0x2e, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x63, 0x6c, 0x69, 0x70, + 0x73, 0x65, 0x5f, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, + 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x30, 0x01, 0x42, 0x5d, 0x5a, 0x5b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x65, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x65, 0x2d, 0x6b, 0x61, 0x6e, 0x74, 0x6f, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x6d, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1419,39 +1435,40 @@ var file_api_services_containers_containers_proto_depIdxs = []int32{ 19, // 4: github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainerMessage.container:type_name -> github.com.eclipse_kanto.container_management.containerm.api.types.containers.Container 20, // 5: github.com.eclipse_kanto.container_management.containerm.api.services.containers.StopContainerRequest.stopOptions:type_name -> github.com.eclipse_kanto.container_management.containerm.api.types.containers.StopOptions 21, // 6: github.com.eclipse_kanto.container_management.containerm.api.services.containers.UpdateContainerRequest.updateOptions:type_name -> github.com.eclipse_kanto.container_management.containerm.api.types.containers.UpdateOptions - 1, // 7: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Create:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.CreateContainerRequest - 3, // 8: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Get:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.GetContainerRequest - 0, // 9: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.List:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainersRequest - 0, // 10: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.ListStream:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainersRequest - 7, // 11: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Start:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.StartContainerRequest - 8, // 12: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Attach:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.AttachContainerRequest - 10, // 13: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Stop:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.StopContainerRequest - 11, // 14: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Update:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.UpdateContainerRequest - 12, // 15: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Restart:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.RestartContainerRequest - 13, // 16: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Pause:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.PauseContainerRequest - 14, // 17: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Unpause:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.UnpauseContainerRequest - 15, // 18: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Rename:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.RenameContainerRequest - 16, // 19: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Remove:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.RemoveContainerRequest - 17, // 20: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Logs:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.GetLogsRequest - 2, // 21: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Create:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.CreateContainerResponse - 4, // 22: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Get:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.GetContainerResponse - 5, // 23: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.List:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainersResponse - 6, // 24: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.ListStream:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainerMessage - 22, // 25: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Start:output_type -> google.protobuf.Empty - 9, // 26: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Attach:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.AttachContainerResponse - 22, // 27: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Stop:output_type -> google.protobuf.Empty - 22, // 28: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Update:output_type -> google.protobuf.Empty - 22, // 29: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Restart:output_type -> google.protobuf.Empty - 22, // 30: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Pause:output_type -> google.protobuf.Empty - 22, // 31: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Unpause:output_type -> google.protobuf.Empty - 22, // 32: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Rename:output_type -> google.protobuf.Empty - 22, // 33: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Remove:output_type -> google.protobuf.Empty - 18, // 34: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Logs:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.GetLogsResponse - 21, // [21:35] is the sub-list for method output_type - 7, // [7:21] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 20, // 7: github.com.eclipse_kanto.container_management.containerm.api.services.containers.RemoveContainerRequest.stopOptions:type_name -> github.com.eclipse_kanto.container_management.containerm.api.types.containers.StopOptions + 1, // 8: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Create:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.CreateContainerRequest + 3, // 9: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Get:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.GetContainerRequest + 0, // 10: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.List:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainersRequest + 0, // 11: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.ListStream:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainersRequest + 7, // 12: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Start:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.StartContainerRequest + 8, // 13: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Attach:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.AttachContainerRequest + 10, // 14: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Stop:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.StopContainerRequest + 11, // 15: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Update:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.UpdateContainerRequest + 12, // 16: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Restart:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.RestartContainerRequest + 13, // 17: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Pause:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.PauseContainerRequest + 14, // 18: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Unpause:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.UnpauseContainerRequest + 15, // 19: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Rename:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.RenameContainerRequest + 16, // 20: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Remove:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.RemoveContainerRequest + 17, // 21: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Logs:input_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.GetLogsRequest + 2, // 22: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Create:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.CreateContainerResponse + 4, // 23: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Get:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.GetContainerResponse + 5, // 24: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.List:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainersResponse + 6, // 25: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.ListStream:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.ListContainerMessage + 22, // 26: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Start:output_type -> google.protobuf.Empty + 9, // 27: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Attach:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.AttachContainerResponse + 22, // 28: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Stop:output_type -> google.protobuf.Empty + 22, // 29: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Update:output_type -> google.protobuf.Empty + 22, // 30: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Restart:output_type -> google.protobuf.Empty + 22, // 31: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Pause:output_type -> google.protobuf.Empty + 22, // 32: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Unpause:output_type -> google.protobuf.Empty + 22, // 33: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Rename:output_type -> google.protobuf.Empty + 22, // 34: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Remove:output_type -> google.protobuf.Empty + 18, // 35: github.com.eclipse_kanto.container_management.containerm.api.services.containers.Containers.Logs:output_type -> github.com.eclipse_kanto.container_management.containerm.api.services.containers.GetLogsResponse + 22, // [22:36] is the sub-list for method output_type + 8, // [8:22] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_api_services_containers_containers_proto_init() } diff --git a/containerm/api/services/containers/containers.proto b/containerm/api/services/containers/containers.proto index 8703756..fbd8854 100644 --- a/containerm/api/services/containers/containers.proto +++ b/containerm/api/services/containers/containers.proto @@ -169,6 +169,7 @@ message RemoveContainerRequest { // Whether the container should be removed disregarding its current state. bool force = 2; + github.com.eclipse_kanto.container_management.containerm.api.types.containers.StopOptions stopOptions = 3; } message GetLogsRequest { diff --git a/containerm/cli/cli_command_ctrs_list.go b/containerm/cli/cli_command_ctrs_list.go index e25e203..fea1705 100644 --- a/containerm/cli/cli_command_ctrs_list.go +++ b/containerm/cli/cli_command_ctrs_list.go @@ -78,7 +78,7 @@ func (cc *listCmd) run(args []string) error { return nil } if len(ctrs) == 0 { - fmt.Println("No found containers.") + fmt.Println("No containers found.") } else { prettyPrint(ctrs) } diff --git a/containerm/cli/cli_command_ctrs_remove.go b/containerm/cli/cli_command_ctrs_remove.go index c1cfee5..87b80c6 100644 --- a/containerm/cli/cli_command_ctrs_remove.go +++ b/containerm/cli/cli_command_ctrs_remove.go @@ -28,8 +28,9 @@ type removeCmd struct { } type removeConfig struct { - force bool - name string + force bool + name string + timeout string } func (cc *removeCmd) init(cli *cli) { @@ -48,22 +49,28 @@ func (cc *removeCmd) init(cli *cli) { func (cc *removeCmd) run(args []string) error { var ( - ctr *types.Container - err error - ctx = context.Background() - errs errorutil.CompoundError + ctr *types.Container + err error + ctx = context.Background() + errs errorutil.CompoundError + stopOpts *types.StopOpts ) - // parse parameters + if cc.config.force && cc.config.timeout != "" { + stopOpts = &types.StopOpts{Force: true} + if stopOpts.Timeout, err = durationStringToSeconds(cc.config.timeout); err != nil { + return err + } + } if len(args) == 0 { if ctr, err = utilcli.ValidateContainerByNameArgsSingle(ctx, nil, cc.config.name, cc.cli.gwManClient); err != nil { return err } - return cc.cli.gwManClient.Remove(ctx, ctr.ID, cc.config.force) + return cc.cli.gwManClient.Remove(ctx, ctr.ID, cc.config.force, stopOpts) } for _, arg := range args { ctr, err = utilcli.ValidateContainerByNameArgsSingle(ctx, []string{arg}, cc.config.name, cc.cli.gwManClient) if err == nil { - if err = cc.cli.gwManClient.Remove(ctx, ctr.ID, cc.config.force); err != nil { + if err = cc.cli.gwManClient.Remove(ctx, ctr.ID, cc.config.force, stopOpts); err != nil { errs.Append(err) } } else { @@ -83,4 +90,5 @@ func (cc *removeCmd) setupFlags() { flagSet.BoolVarP(&cc.config.force, "force", "f", false, "Force stopping before removing a container") // init name flags flagSet.StringVarP(&cc.config.name, "name", "n", "", "Remove a container with a specific name.") + flagSet.StringVarP(&cc.config.timeout, "time", "t", "", "Sets the timeout period to gracefully stop the container as duration string, e.g. 15s or 1m15s. When timeout expires the container process would be forcibly killed. If not specified the daemon default container stop timeout will be used.") } diff --git a/containerm/cli/cli_command_ctrs_remove_test.go b/containerm/cli/cli_command_ctrs_remove_test.go index 9a531dc..f625ba8 100644 --- a/containerm/cli/cli_command_ctrs_remove_test.go +++ b/containerm/cli/cli_command_ctrs_remove_test.go @@ -27,8 +27,9 @@ import ( const ( // command flags - removeCmdFlagForce = "force" - removeCmdFlagName = "name" + removeCmdFlagForce = "force" + removeCmdFlagName = "name" + removeCmdFlagTimeout = "time" // test input constants removeContainerID = "test-ctr" @@ -53,12 +54,14 @@ func TestRemoveCmdSetupFlags(t *testing.T) { rmCliTest.init() expectedCfg := removeConfig{ - force: true, - name: removeContainerName, + force: true, + name: removeContainerName, + timeout: "50", } flagsToApply := map[string]string{ - removeCmdFlagForce: strconv.FormatBool(expectedCfg.force), - removeCmdFlagName: expectedCfg.name, + removeCmdFlagForce: strconv.FormatBool(expectedCfg.force), + removeCmdFlagName: expectedCfg.name, + removeCmdFlagTimeout: expectedCfg.timeout, } execTestSetupFlags(t, rmCliTest, flagsToApply, expectedCfg) @@ -87,8 +90,9 @@ func (rmTc *removeCommandTest) commandConfig() interface{} { func (rmTc *removeCommandTest) commandConfigDefault() interface{} { return removeConfig{ - force: false, - name: "", + force: false, + name: "", + timeout: "", } } @@ -176,6 +180,22 @@ func (rmTc *removeCommandTest) generateRunExecutionConfigs() map[string]testRunE flags: map[string]string{removeCmdFlagForce: "true"}, mockExecution: rmTc.mockExecForceRemoveError, }, + "test_remove_with_timeout": { + args: stopCmdArgs, + flags: map[string]string{ + removeCmdFlagForce: "true", + stopCmdFlagTimeout: "20s", + }, + mockExecution: rmTc.mockExecRemoveWithTimeout, + }, + "test_remove_with_timeout_round": { + args: stopCmdArgs, + flags: map[string]string{ + removeCmdFlagForce: "true", + stopCmdFlagTimeout: "19.7s", + }, + mockExecution: rmTc.mockExecRemoveWithTimeout, + }, } } @@ -183,7 +203,7 @@ func (rmTc *removeCommandTest) generateRunExecutionConfigs() map[string]testRunE func (rmTc *removeCommandTest) mockExecRemoveIDAndName(args []string) error { rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(0) rmTc.mockClient.EXPECT().List(context.Background(), gomock.Any()).Times(0) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false).Times(0) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false, nil).Times(0) return log.NewError("Container ID and --name (-n) cannot be provided at the same time - use only one of them") } @@ -201,7 +221,7 @@ func (rmTc *removeCommandTest) mockExecRemoveNoErrors(args []string) error { Name: removeContainerName, } rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(ctr, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false).Times(1).Return(nil) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false, nil).Times(1).Return(nil) // no error expected return nil } @@ -218,8 +238,8 @@ func (rmTc *removeCommandTest) mockExecRemoveMultipleNoErrors(args []string) err } rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(ctr1, nil) rmTc.mockClient.EXPECT().Get(context.Background(), args[1]).Times(1).Return(ctr2, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false).Times(1).Return(nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[1], false).Times(1).Return(nil) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false, nil).Times(1).Return(nil) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[1], false, nil).Times(1).Return(nil) // no error expected return nil } @@ -235,7 +255,7 @@ func (rmTc *removeCommandTest) mockExecRemoveMultipleWithErrors(args []string) e } rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(nil, err1) rmTc.mockClient.EXPECT().Get(context.Background(), args[1]).Times(1).Return(ctr, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[1], false).Times(1).Return(err2) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[1], false, nil).Times(1).Return(err2) errs.Append(err1, err2) return errors.New(errs.ErrorWithMessage("containers couldn't be removed due to the following reasons: ")) @@ -249,7 +269,7 @@ func (rmTc *removeCommandTest) mockExecRemoveError(args []string) error { Name: removeContainerName, } rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(ctr, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false).Times(1).Return(err) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false, nil).Times(1).Return(err) return err } @@ -257,7 +277,7 @@ func (rmTc *removeCommandTest) mockExecRemoveByNameNoErrors(args []string) error // setup expected calls ctrs := []*types.Container{{ID: removeContainerID, Name: removeContainerName}} rmTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(removeContainerName))).Times(1).Return(ctrs, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), ctrs[0].ID, false).Times(1).Return(nil) + rmTc.mockClient.EXPECT().Remove(context.Background(), ctrs[0].ID, false, nil).Times(1).Return(nil) // no error expected return nil } @@ -267,7 +287,7 @@ func (rmTc *removeCommandTest) mockExecRemoveByNameError(args []string) error { err := errors.New("failed to remove container") ctrs := []*types.Container{{ID: removeContainerID, Name: removeContainerName}} rmTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(removeContainerName))).Times(1).Return(ctrs, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), ctrs[0].ID, false).Times(1).Return(err) + rmTc.mockClient.EXPECT().Remove(context.Background(), ctrs[0].ID, false, nil).Times(1).Return(err) // no error expected return err } @@ -276,35 +296,35 @@ func (rmTc *removeCommandTest) mockExecRemoveByNameListError(args []string) erro // setup expected calls err := errors.New("failed to list containers") rmTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(removeContainerName))).Times(1).Return(nil, err) - rmTc.mockClient.EXPECT().Remove(context.Background(), gomock.Any(), false).Times(0) + rmTc.mockClient.EXPECT().Remove(context.Background(), gomock.Any(), false, nil).Times(0) return err } func (rmTc *removeCommandTest) mockExecRemoveByNameListNilCtrs(args []string) error { // setup expected calls rmTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(removeContainerName))).Times(1).Return(nil, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), gomock.Any(), false).Times(0) + rmTc.mockClient.EXPECT().Remove(context.Background(), gomock.Any(), false, nil).Times(0) return log.NewErrorf("The requested container with name = %s was not found. Try using an ID instead.", removeContainerName) } func (rmTc *removeCommandTest) mockExecRemoveByNameListZeroCtrs(args []string) error { // setup expected calls rmTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(removeContainerName))).Times(1).Return([]*types.Container{}, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), gomock.Any(), false).Times(0) + rmTc.mockClient.EXPECT().Remove(context.Background(), gomock.Any(), false, nil).Times(0) return log.NewErrorf("The requested container with name = %s was not found. Try using an ID instead.", removeContainerName) } func (rmTc *removeCommandTest) mockExecRemoveByNameListMoreThanOneCtrs(args []string) error { // setup expected calls rmTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(removeContainerName))).Times(1).Return([]*types.Container{{}, {}}, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), gomock.Any(), false).Times(0) + rmTc.mockClient.EXPECT().Remove(context.Background(), gomock.Any(), false, nil).Times(0) return log.NewErrorf("There are more than one containers with name = %s. Try using an ID instead.", removeContainerName) } func (rmTc *removeCommandTest) mockExecRemoveGetNilError(args []string) error { // setup expected calls rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(nil, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false).Times(0) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false, nil).Times(0) return log.NewErrorf("The requested container with ID = %s was not found.", args[0]) } @@ -312,7 +332,7 @@ func (rmTc *removeCommandTest) mockExecRemoveGetError(args []string) error { // setup expected calls err := errors.New("failed to remove container") rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(nil, err) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false).Times(0) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], false, nil).Times(0) return err } @@ -323,7 +343,7 @@ func (rmTc *removeCommandTest) mockExecForceRemoveNoErrors(args []string) error Name: removeContainerName, } rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(ctr, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], true).Times(1).Return(nil) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], true, nil).Times(1).Return(nil) // no error expected return nil } @@ -336,7 +356,16 @@ func (rmTc *removeCommandTest) mockExecForceRemoveError(args []string) error { Name: removeContainerName, } rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(ctr, nil) - rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], true).Times(1).Return(err) + rmTc.mockClient.EXPECT().Remove(context.Background(), args[0], true, nil).Times(1).Return(err) // no error expected return err } + +func (rmTc *removeCommandTest) mockExecRemoveWithTimeout(args []string) error { + ctr := &types.Container{ + ID: args[0], + } + rmTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(ctr, nil) + rmTc.mockClient.EXPECT().Remove(context.Background(), ctr.ID, true, &types.StopOpts{Timeout: 20, Force: true}).Times(1).Return(nil) + return nil +} diff --git a/containerm/cli/cli_command_ctrs_stop.go b/containerm/cli/cli_command_ctrs_stop.go index 7ca2a02..2fbf94c 100644 --- a/containerm/cli/cli_command_ctrs_stop.go +++ b/containerm/cli/cli_command_ctrs_stop.go @@ -14,7 +14,7 @@ package main import ( "context" - "math" + "time" "github.com/eclipse-kanto/container-management/containerm/containers/types" "github.com/eclipse-kanto/container-management/containerm/util" @@ -28,7 +28,7 @@ type stopCmd struct { } type stopConfig struct { - timeout int64 + timeout string name string force bool signal string @@ -63,8 +63,8 @@ func (cc *stopCmd) run(args []string) error { Force: cc.config.force, Signal: cc.config.signal, } - if cc.config.timeout != math.MinInt64 { - stopOpts.Timeout = cc.config.timeout + if stopOpts.Timeout, err = durationStringToSeconds(cc.config.timeout); err != nil { + return err } if err = util.ValidateStopOpts(stopOpts); err != nil { return err @@ -72,10 +72,21 @@ func (cc *stopCmd) run(args []string) error { return cc.cli.gwManClient.Stop(ctx, container.ID, stopOpts) } +func durationStringToSeconds(duration string) (int64, error) { + if duration == "" { + return 0, nil + } + stopTime, err := time.ParseDuration(duration) + if err != nil { + return 0, err + } + return int64(stopTime.Round(time.Second).Seconds()), nil +} + func (cc *stopCmd) setupFlags() { flagSet := cc.cmd.Flags() // init timeout flag - flagSet.Int64VarP(&cc.config.timeout, "time", "t", math.MinInt64, "Sets the timeout period in seconds to gracefully stop the container. When timeout expires the container process would be forcibly killed.") + flagSet.StringVarP(&cc.config.timeout, "time", "t", "", "Sets the timeout period to gracefully stop the container as duration string, e.g. 15s or 1m15s. When timeout expires the container process would be forcibly killed. If not specified the daemon default container stop timeout will be used.") // init name flag flagSet.StringVarP(&cc.config.name, "name", "n", "", "Stop a container with a specific name.") // init force flag diff --git a/containerm/cli/cli_command_ctrs_stop_test.go b/containerm/cli/cli_command_ctrs_stop_test.go index 3f3c29e..cbcb9b7 100644 --- a/containerm/cli/cli_command_ctrs_stop_test.go +++ b/containerm/cli/cli_command_ctrs_stop_test.go @@ -14,7 +14,6 @@ package main import ( "context" - "math" "strconv" "testing" @@ -65,14 +64,16 @@ func TestStopCmdFlags(t *testing.T) { stopCliTest.init() expectedCfg := stopConfig{ - timeout: 50, + timeout: "50", name: stopContainerName, force: true, signal: sigterm, } + conv, _ := strconv.ParseInt(expectedCfg.timeout, 10, 64) + flagsToApply := map[string]string{ - stopCmdFlagTimeout: strconv.FormatInt(expectedCfg.timeout, 10), + stopCmdFlagTimeout: strconv.FormatInt(conv, 10), stopCmdFlagName: expectedCfg.name, stopCmdFlagForce: strconv.FormatBool(expectedCfg.force), } @@ -103,7 +104,7 @@ func (stopTc *stopCommandTest) commandConfig() interface{} { func (stopTc *stopCommandTest) commandConfigDefault() interface{} { return stopConfig{ - timeout: math.MinInt64, + timeout: "", name: "", force: false, signal: sigterm, @@ -175,7 +176,7 @@ func (stopTc *stopCommandTest) generateRunExecutionConfigs() map[string]testRunE "test_stop_with_timeout": { args: stopCmdArgs, flags: map[string]string{ - stopCmdFlagTimeout: "20", + stopCmdFlagTimeout: "20s", }, mockExecution: stopTc.mockExecStopWithTimeout, }, @@ -186,7 +187,7 @@ func (stopTc *stopCommandTest) generateRunExecutionConfigs() map[string]testRunE "test_stop_with_negative_timeout_error": { args: stopCmdArgs, flags: map[string]string{ - stopCmdFlagTimeout: "-10", + stopCmdFlagTimeout: "-10s", }, mockExecution: stopTc.mockExecStopWithNegativeTimeoutError, }, @@ -198,7 +199,7 @@ func (stopTc *stopCommandTest) generateRunExecutionConfigs() map[string]testRunE }, "test_stop_by_name_with_timeout": { flags: map[string]string{ - stopCmdFlagTimeout: "20", + stopCmdFlagTimeout: "20s", stopCmdFlagName: stopContainerName, }, mockExecution: stopTc.mockExecStopByNameWithTimeout, @@ -211,7 +212,7 @@ func (stopTc *stopCommandTest) generateRunExecutionConfigs() map[string]testRunE }, "test_stop_by_name_with_negative_timeout_error": { flags: map[string]string{ - stopCmdFlagTimeout: "-10", + stopCmdFlagTimeout: "-10s", stopCmdFlagName: stopContainerName, }, mockExecution: stopTc.mockExecStopByNameWithNegativeTimeoutError, @@ -275,6 +276,7 @@ func (stopTc *stopCommandTest) mockExecStopNoIDorName(args []string) error { stopTc.mockClient.EXPECT().Stop(context.Background(), gomock.Any(), gomock.Any()).Times(0) return log.NewError("You must provide either an ID or a name for the container via --name (-n) ") } + func (stopTc *stopCommandTest) mockExecStopByIDGetErr(args []string) error { err := log.NewError("could not get container") stopTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(nil, err) @@ -282,6 +284,7 @@ func (stopTc *stopCommandTest) mockExecStopByIDGetErr(args []string) error { stopTc.mockClient.EXPECT().Stop(context.Background(), gomock.Any(), gomock.Any()).Times(0) return err } + func (stopTc *stopCommandTest) mockExecStopByIDGetNilCtr(args []string) error { stopTc.mockClient.EXPECT().Get(context.Background(), args[0]).Times(1).Return(nil, nil) stopTc.mockClient.EXPECT().List(context.Background(), gomock.Any()).Times(0) @@ -296,24 +299,28 @@ func (stopTc *stopCommandTest) mockExecStopByNameListErr(args []string) error { stopTc.mockClient.EXPECT().Stop(context.Background(), gomock.Any(), gomock.Any()).Times(0) return err } + func (stopTc *stopCommandTest) mockExecStopByNameListNilCtrs(args []string) error { stopTc.mockClient.EXPECT().Get(context.Background(), gomock.Any()).Times(0) stopTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(startContainerName))).Times(1).Return(nil, nil) stopTc.mockClient.EXPECT().Stop(context.Background(), gomock.Any(), gomock.Any()).Times(0) return log.NewErrorf("The requested container with name = %s was not found. Try using an ID instead.", startContainerName) } + func (stopTc *stopCommandTest) mockExecStopByNameListZeroCtrs(args []string) error { stopTc.mockClient.EXPECT().Get(context.Background(), gomock.Any()).Times(0) stopTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(startContainerName))).Times(1).Return([]*types.Container{}, nil) stopTc.mockClient.EXPECT().Stop(context.Background(), gomock.Any(), gomock.Any()).Times(0) return log.NewErrorf("The requested container with name = %s was not found. Try using an ID instead.", startContainerName) } + func (stopTc *stopCommandTest) mockExecStopByNameListMoreThanOneCtrs(args []string) error { stopTc.mockClient.EXPECT().Get(context.Background(), gomock.Any()).Times(0) stopTc.mockClient.EXPECT().List(context.Background(), gomock.AssignableToTypeOf(client.WithName(startContainerName))).Times(1).Return([]*types.Container{{}, {}}, nil) stopTc.mockClient.EXPECT().Stop(context.Background(), gomock.Any(), gomock.Any()).Times(0) return log.NewErrorf("There are more than one containers with name = %s. Try using an ID instead.", startContainerName) } + func (stopTc *stopCommandTest) mockExecStopNoErrors(args []string) error { // setup expected calls ctr := &types.Container{ @@ -345,6 +352,7 @@ func (stopTc *stopCommandTest) mockExecStopError(args []string) error { stopTc.mockClient.EXPECT().Stop(context.Background(), ctr.ID, defaultStopOpts).Times(1).Return(err) return err } + func (stopTc *stopCommandTest) mockExecStopWithNegativeTimeoutError(args []string) error { err := log.NewError("the timeout = -10 shouldn't be negative") ctr := &types.Container{ @@ -365,6 +373,7 @@ func (stopTc *stopCommandTest) mockExecStopByNameWithNegativeTimeoutError(args [ stopTc.mockClient.EXPECT().Stop(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) return err } + func (stopTc *stopCommandTest) mockExecStopByNameNoErrors(args []string) error { // setup expected calls ctrs := []*types.Container{{ diff --git a/containerm/client/client.go b/containerm/client/client.go index 361d61a..2baeb19 100644 --- a/containerm/client/client.go +++ b/containerm/client/client.go @@ -149,8 +149,8 @@ func (cl *client) Rename(ctx context.Context, id string, name string) error { } // Remove removes a container, it may be running or stopped and so on. -func (cl *client) Remove(ctx context.Context, id string, force bool) error { - _, err := cl.grpcContainersClient.Remove(ctx, &pbcontainers.RemoveContainerRequest{Id: id, Force: force}) +func (cl *client) Remove(ctx context.Context, id string, force bool, stopOpts *types.StopOpts) error { + _, err := cl.grpcContainersClient.Remove(ctx, &pbcontainers.RemoveContainerRequest{Id: id, Force: force, StopOptions: protobuf.ToProtoStopOptions(stopOpts)}) return err } diff --git a/containerm/client/client_api.go b/containerm/client/client_api.go index 9fd3590..b130ee0 100644 --- a/containerm/client/client_api.go +++ b/containerm/client/client_api.go @@ -59,7 +59,7 @@ type Client interface { Rename(ctx context.Context, id string, name string) error // Remove removes a container, it may be running or stopped and so on. - Remove(ctx context.Context, id string, force bool) error + Remove(ctx context.Context, id string, force bool, stopOpts *types.StopOpts) error ProjectInfo(ctx context.Context) (sysinfotypes.ProjectInfo, error) diff --git a/containerm/client/client_test.go b/containerm/client/client_test.go index b726f13..ccd393d 100644 --- a/containerm/client/client_test.go +++ b/containerm/client/client_test.go @@ -605,9 +605,10 @@ func TestUpdate(t *testing.T) { } type testRemoveArgs struct { - ctx context.Context - id string - force bool + ctx context.Context + id string + force bool + stopOpts *types.StopOpts } type mockExecRemove func(args testRemoveArgs) error @@ -634,6 +635,14 @@ func TestRemove(t *testing.T) { }, mockExecution: mockExecRemoveErrors, }, + "test_remove_stop_opts": { + args: testRemoveArgs{ + ctx: testCtx, + force: true, + stopOpts: &types.StopOpts{Timeout: 10}, + }, + mockExecution: mockExecRemoveStopOpts, + }, } // execute tests @@ -643,7 +652,7 @@ func TestRemove(t *testing.T) { expectedRunErr := testCase.mockExecution(testCase.args) - resultErr := testClient.Remove(testCase.args.ctx, testCase.args.id, testCase.args.force) + resultErr := testClient.Remove(testCase.args.ctx, testCase.args.id, testCase.args.force, testCase.args.stopOpts) testutil.AssertError(t, expectedRunErr, resultErr) }) @@ -998,6 +1007,18 @@ func mockExecRemoveNoErrors(args testRemoveArgs) error { return nil } +func mockExecRemoveStopOpts(args testRemoveArgs) error { + mockContainersClient.EXPECT().Remove(args.ctx, gomock.Eq(&pbcontainers.RemoveContainerRequest{ + Id: args.id, + Force: args.force, + StopOptions: &containers.StopOptions{ + Timeout: args.stopOpts.Timeout, + Force: args.stopOpts.Force, + }, + })).Times(1).Return(nil, nil) + return nil +} + func mockExecRemoveErrors(args testRemoveArgs) error { err := errors.New("failed to remove") mockContainersClient.EXPECT().Remove(args.ctx, gomock.Eq(&pbcontainers.RemoveContainerRequest{ diff --git a/containerm/ctr/ctrd_client_test.go b/containerm/ctr/ctrd_client_test.go index ec03eed..307218e 100644 --- a/containerm/ctr/ctrd_client_test.go +++ b/containerm/ctr/ctrd_client_test.go @@ -596,10 +596,10 @@ func TestAttachContainer(t *testing.T) { }() mockIoMgr.EXPECT().GetIO(testCtr.ID).Return(mockIO) + mockIO.EXPECT().Stream().Return(mockStream) mockReadCloser.EXPECT().Read(gomock.Any()).DoAndReturn(func(p []byte) (int, error) { return -1, io.EOF - }) - mockIO.EXPECT().Stream().Return(mockStream) + }).AnyTimes() mockStream.EXPECT().Attach(ctx, gomock.AssignableToTypeOf(attachConfig)).Return(errChan) return nil diff --git a/containerm/daemon/daemon_command.go b/containerm/daemon/daemon_command.go index e85ddf7..f21ae67 100644 --- a/containerm/daemon/daemon_command.go +++ b/containerm/daemon/daemon_command.go @@ -42,7 +42,7 @@ func setupCommandFlags(cmd *cobra.Command) { flagSet.StringVar(&cfg.ManagerConfig.MgrExecPath, "cm-exec-root-dir", cfg.ManagerConfig.MgrExecPath, "Specify the exec root directory of the container manager service") flagSet.StringVar(&cfg.ManagerConfig.MgrCtrClientServiceID, "cm-cc-sid", cfg.ManagerConfig.MgrCtrClientServiceID, "Specify the ID of the container runtime client service to be used by the container manager service") flagSet.StringVar(&cfg.ManagerConfig.MgrNetMgrServiceID, "cm-net-sid", cfg.ManagerConfig.MgrNetMgrServiceID, "Specify the ID of the network manager service to be used by container manager service") - flagSet.Int64Var(&cfg.ManagerConfig.MgrDefaultCtrsStopTimeout, "cm-deflt-ctrs-stop-timeout", cfg.ManagerConfig.MgrDefaultCtrsStopTimeout, "Specify the default timeout that the container manager service will wait before killing the container's process") + flagSet.StringVar(&cfg.ManagerConfig.MgrDefaultCtrsStopTimeout, "cm-deflt-ctrs-stop-timeout", cfg.ManagerConfig.MgrDefaultCtrsStopTimeout, "Specify the default timeout that the container manager service will wait before killing the container's process") // init container client flags flagSet.StringVar(&cfg.ContainerClientConfig.CtrNamespace, "ccl-default-ns", cfg.ContainerClientConfig.CtrNamespace, "Specify the default namespace to be used for container management isolation") diff --git a/containerm/daemon/daemon_config.go b/containerm/daemon/daemon_config.go index 2d1fe0c..5b46359 100644 --- a/containerm/daemon/daemon_config.go +++ b/containerm/daemon/daemon_config.go @@ -15,6 +15,7 @@ package main import ( "encoding/json" "fmt" + "strconv" "strings" "time" @@ -48,7 +49,33 @@ type managerConfig struct { MgrExecPath string `json:"exec_root_dir,omitempty"` MgrCtrClientServiceID string `json:"container_client_sid,omitempty"` MgrNetMgrServiceID string `json:"network_manager_sid,omitempty"` - MgrDefaultCtrsStopTimeout int64 `json:"default_ctrs_stop_timeout,omitempty"` + MgrDefaultCtrsStopTimeout string `json:"default_ctrs_stop_timeout,omitempty"` +} + +func (mc *managerConfig) UnmarshalJSON(data []byte) error { + type managerConfigPlain managerConfig + + plain := (*managerConfigPlain)(mc) + err := json.Unmarshal(data, &plain) + if err == nil { + return nil + } + + tmp := struct { + MgrDefaultCtrsStopTimeout int `json:"default_ctrs_stop_timeout,omitempty"` + *managerConfigPlain + }{ + managerConfigPlain: plain, + } + + if err = json.Unmarshal(data, &tmp); err != nil { + return err + } + + if tmp.MgrDefaultCtrsStopTimeout != 0 { + mc.MgrDefaultCtrsStopTimeout = strconv.Itoa(tmp.MgrDefaultCtrsStopTimeout) + } + return nil } // container client config- e.g. containerd diff --git a/containerm/daemon/daemon_config_default.go b/containerm/daemon/daemon_config_default.go index eb215b0..d121c85 100644 --- a/containerm/daemon/daemon_config_default.go +++ b/containerm/daemon/daemon_config_default.go @@ -40,7 +40,7 @@ const ( managerExecRootPathDefault = "/var/run/container-management" managerContainerClientServiceIDDefault = ctr.ContainerdClientServiceLocalID managerNetworkManagerServiceIDDefault = network.LibnetworkManagerServiceLocalID - managerNetworkManagerStopTimeout = 30 + managerContainerStopTimeoutDefault = "30s" // default container client config containerClientNamespaceDefault = "kanto-cm" @@ -126,7 +126,7 @@ func getDefaultInstance() *config { MgrExecPath: managerExecRootPathDefault, MgrCtrClientServiceID: managerContainerClientServiceIDDefault, MgrNetMgrServiceID: managerNetworkManagerServiceIDDefault, - MgrDefaultCtrsStopTimeout: managerNetworkManagerStopTimeout, + MgrDefaultCtrsStopTimeout: managerContainerStopTimeoutDefault, }, ContainerClientConfig: &containerRuntimeConfig{ CtrNamespace: containerClientNamespaceDefault, diff --git a/containerm/daemon/daemon_config_util.go b/containerm/daemon/daemon_config_util.go index e24b9ef..b36fd5b 100644 --- a/containerm/daemon/daemon_config_util.go +++ b/containerm/daemon/daemon_config_util.go @@ -18,6 +18,7 @@ import ( "io/ioutil" "os" "reflect" + "strconv" "time" "github.com/eclipse-kanto/container-management/containerm/containers/types" @@ -78,12 +79,15 @@ func extractNetManagerConfigOptions(daemonConfig *config) []network.NetOpt { func extractContainerManagerOptions(daemonConfig *config) []mgr.ContainerManagerOpt { mgrOpts := []mgr.ContainerManagerOpt{} + if _, err := strconv.Atoi(daemonConfig.ManagerConfig.MgrDefaultCtrsStopTimeout); err == nil { + daemonConfig.ManagerConfig.MgrDefaultCtrsStopTimeout = fmt.Sprintf("%ss", daemonConfig.ManagerConfig.MgrDefaultCtrsStopTimeout) + } mgrOpts = append(mgrOpts, mgr.WithMgrMetaPath(daemonConfig.ManagerConfig.MgrMetaPath), mgr.WithMgrRootExec(daemonConfig.ManagerConfig.MgrExecPath), mgr.WithMgrContainerClientServiceID(daemonConfig.ManagerConfig.MgrCtrClientServiceID), mgr.WithMgrNetworkManagerServiceID(daemonConfig.ManagerConfig.MgrNetMgrServiceID), - mgr.WithMgrDefaultContainerStopTimeout(daemonConfig.ManagerConfig.MgrDefaultCtrsStopTimeout), + mgr.WithMgrDefaultContainerStopTimeout(parseDuration(daemonConfig.ManagerConfig.MgrDefaultCtrsStopTimeout, managerContainerStopTimeoutDefault)), ) return mgrOpts } @@ -265,7 +269,7 @@ func dumpContManager(configInstance *config) { log.Debug("[daemon_cfg][cm-exec-root-dir] : %s", configInstance.ManagerConfig.MgrExecPath) log.Debug("[daemon_cfg][cm-cc-sid] : %s", configInstance.ManagerConfig.MgrCtrClientServiceID) log.Debug("[daemon_cfg][cm-net-sid] : %s", configInstance.ManagerConfig.MgrNetMgrServiceID) - log.Debug("[daemon_cfg][cm-deflt-ctrs-stop-timeout] : %d", configInstance.ManagerConfig.MgrDefaultCtrsStopTimeout) + log.Debug("[daemon_cfg][cm-deflt-ctrs-stop-timeout] : %s", configInstance.ManagerConfig.MgrDefaultCtrsStopTimeout) } } diff --git a/containerm/daemon/daemon_test.go b/containerm/daemon/daemon_test.go index 4610571..046b2b4 100644 --- a/containerm/daemon/daemon_test.go +++ b/containerm/daemon/daemon_test.go @@ -93,34 +93,48 @@ func TestThingsTLSConfig(t *testing.T) { testutil.AssertEqual(t, &tlsConfig{RootCA: "ca.crt", ClientCert: "client.crt", ClientKey: "client.key"}, local.ThingsConfig.ThingsConnectionConfig.Transport) } +func TestMgrDefaultCtrsStopTimeoutConfig(t *testing.T) { + local := &config{} + _ = loadLocalConfig("../pkg/testutil/config/daemon-mgr-default-ctrs-stop-timeout-config.json", local) + testutil.AssertEqual(t, local.ManagerConfig.MgrDefaultCtrsStopTimeout, "15") +} + func TestExtractOpts(t *testing.T) { t.Run("test_extract_ctr_client_opts", func(t *testing.T) { opts := extractCtrClientConfigOptions(cfg) - if opts == nil || len(opts) == 0 { + if len(opts) == 0 { t.Error("no ctr client opts after extraction") } }) t.Run("test_extract_net_mgr_opts", func(t *testing.T) { opts := extractNetManagerConfigOptions(cfg) - if opts == nil || len(opts) == 0 { + if len(opts) == 0 { t.Error("no net mgr opts after extraction") } }) t.Run("test_extract_ctr_mgr_opts", func(t *testing.T) { opts := extractContainerManagerOptions(cfg) - if opts == nil || len(opts) == 0 { + if len(opts) == 0 { + t.Error("no ctr mgr opts after extraction") + } + }) + t.Run("test_extract_ctr_mgr_opts_with_not_suffixed_stop_timeout", func(t *testing.T) { + config := &config{ManagerConfig: &managerConfig{MgrDefaultCtrsStopTimeout: "10"}} + opts := extractContainerManagerOptions(config) + if len(opts) == 0 { t.Error("no ctr mgr opts after extraction") } + testutil.AssertEqual(t, "10s", config.ManagerConfig.MgrDefaultCtrsStopTimeout) }) t.Run("test_extract_grpc_opts", func(t *testing.T) { opts := extractGrpcOptions(cfg) - if opts == nil || len(opts) == 0 { + if len(opts) == 0 { t.Error("no grpc opts after extraction") } }) t.Run("test_extract_things_opts", func(t *testing.T) { opts := extractThingsOptions(cfg) - if opts == nil || len(opts) == 0 { + if len(opts) == 0 { t.Error("no things opts after extraction") } }) @@ -216,7 +230,7 @@ func TestSetCommandFlags(t *testing.T) { }, "test_flags_cm-deflt-ctrs-stop-timeout": { flag: "cm-deflt-ctrs-stop-timeout", - expectedType: reflect.Int64.String(), + expectedType: reflect.String.String(), }, "test_flags_ccl-default-ns": { flag: "ccl-default-ns", diff --git a/containerm/deployment/deployment_internal.go b/containerm/deployment/deployment_internal.go index eb49c78..121fe8a 100644 --- a/containerm/deployment/deployment_internal.go +++ b/containerm/deployment/deployment_internal.go @@ -166,7 +166,7 @@ func stopContainer(ctx context.Context, ctrMgr mgr.ContainerManager, container * } func removeContainer(ctx context.Context, ctrMgr mgr.ContainerManager, container *types.Container) { - if removeErr := ctrMgr.Remove(ctx, container.ID, true); removeErr != nil { + if removeErr := ctrMgr.Remove(ctx, container.ID, true, nil); removeErr != nil { log.WarnErr(removeErr, "could not remove container with ID = %s, name = %s and image name = %s", container.ID, container.Name, container.Image.Name) } else { log.Debug("successfully removed container with ID = %s, name = %s and image name = %s", container.ID, container.Name, container.Image.Name) diff --git a/containerm/deployment/deployment_test.go b/containerm/deployment/deployment_test.go index e3461f3..102bbad 100755 --- a/containerm/deployment/deployment_test.go +++ b/containerm/deployment/deployment_test.go @@ -461,7 +461,7 @@ func TestUpdate(t *testing.T) { mockMgr.EXPECT().Create(testContext, testContainerMatcher).Return(newContainer, nil).Times(1) mockMgr.EXPECT().Stop(testContext, oldID, testStopOpts).Return(nil).Times(1) mockMgr.EXPECT().Start(testContext, newID).Return(nil).Times(1) - mockMgr.EXPECT().Remove(testContext, oldID, true).Do(func(ctx context.Context, ctrID string, force bool) { + mockMgr.EXPECT().Remove(testContext, oldID, true, nil).Do(func(ctx context.Context, ctrID string, force bool, stopOpts *types.StopOpts) { testWaitGroup.Done() }).Return(nil).Times(1) return nil @@ -482,7 +482,7 @@ func TestUpdate(t *testing.T) { mockMgr.EXPECT().List(testContext).Return([]*types.Container{testCtr}, nil) mockMgr.EXPECT().Create(testContext, testContainerMatcher).Return(newContainer, nil).Times(1) mockMgr.EXPECT().Start(testContext, newID).Return(nil).Times(1) - mockMgr.EXPECT().Remove(testContext, oldID, true).Do(func(ctx context.Context, ctrID string, force bool) { + mockMgr.EXPECT().Remove(testContext, oldID, true, nil).Do(func(ctx context.Context, ctrID string, force bool, stopOpts *types.StopOpts) { testWaitGroup.Done() }).Return(nil).Times(1) return nil @@ -519,7 +519,7 @@ func TestUpdate(t *testing.T) { mockMgr.EXPECT().Stop(testContext, oldID, testStopOpts).Return(nil).Times(1) mockMgr.EXPECT().Start(testContext, newID).Return(log.NewError("test error")).Times(1) mockMgr.EXPECT().Start(testContext, oldID).Return(nil).Times(1) - mockMgr.EXPECT().Remove(testContext, newID, true).Do(func(ctx context.Context, ctrID string, force bool) { + mockMgr.EXPECT().Remove(testContext, newID, true, nil).Do(func(ctx context.Context, ctrID string, force bool, stopOpts *types.StopOpts) { testWaitGroup.Done() }).Return(nil).Times(1) return nil @@ -542,7 +542,7 @@ func TestUpdate(t *testing.T) { mockMgr.EXPECT().Create(testContext, testContainerMatcher).Return(newContainer, nil).Times(1) mockMgr.EXPECT().Stop(testContext, oldID, testStopOpts).Return(log.NewError("test error")).Times(1) mockMgr.EXPECT().Start(testContext, newID).Return(log.NewError("test error")).Times(1) - mockMgr.EXPECT().Remove(testContext, newID, true).Do(func(ctx context.Context, ctrID string, force bool) { + mockMgr.EXPECT().Remove(testContext, newID, true, nil).Do(func(ctx context.Context, ctrID string, force bool, stopOpts *types.StopOpts) { testWaitGroup.Done() }).Return(nil).Times(1) return nil @@ -565,7 +565,7 @@ func TestUpdate(t *testing.T) { mockMgr.EXPECT().Create(testContext, testContainerMatcher).Return(newContainer, nil).Times(1) mockMgr.EXPECT().Stop(testContext, oldID, testStopOpts).Return(nil).Times(1) mockMgr.EXPECT().Start(testContext, newID).Return(nil).Times(1) - mockMgr.EXPECT().Remove(testContext, oldID, true).Do(func(ctx context.Context, ctrID string, force bool) { + mockMgr.EXPECT().Remove(testContext, oldID, true, nil).Do(func(ctx context.Context, ctrID string, force bool, stopOpts *types.StopOpts) { testWaitGroup.Done() }).Return(log.NewError("test error")).Times(1) return nil diff --git a/containerm/mgr/mgr.go b/containerm/mgr/mgr.go index 61191e1..9b8b581 100644 --- a/containerm/mgr/mgr.go +++ b/containerm/mgr/mgr.go @@ -49,7 +49,7 @@ func init() { type containerMgr struct { metaPath string execPath string - defaultCtrsStopTimeout int64 + defaultCtrsStopTimeout time.Duration ctrClient ctr.ContainerAPIClient netMgr network.ContainerNetworkManager eventsMgr events.ContainerEventsManager @@ -423,7 +423,7 @@ func (mgr *containerMgr) Rename(ctx context.Context, id string, name string) err } // Remove removes a container, it may be running or stopped and so on. -func (mgr *containerMgr) Remove(ctx context.Context, id string, force bool) error { +func (mgr *containerMgr) Remove(ctx context.Context, id string, force bool, stopOpts *types.StopOpts) error { container := mgr.getContainerFromCache(id) if container == nil { return log.NewErrorf(noSuchContainerErrorMsg, id) @@ -444,7 +444,15 @@ func (mgr *containerMgr) Remove(ctx context.Context, id string, force bool) erro if (!util.IsContainerRunningOrPaused(container)) || force { mgr.cancelContainerRestartManager(container) - stopOpts := mgr.getContainerStopOptions(force) + if stopOpts == nil { + stopOpts = mgr.getContainerStopOptions(force) + } else { + mgr.fillContainerStopDefaults(stopOpts) + } + if err := util.ValidateStopOpts(stopOpts); err != nil { + log.ErrorErr(err, "invalid stop options for container id = %s", container.ID) + return err + } _, _, err := mgr.ctrClient.DestroyContainer(ctx, container, stopOpts, true) if err != nil && !(strings.Contains(err.Error(), "does not exist") || strings.Contains(err.Error(), "not found")) { diff --git a/containerm/mgr/mgr_api.go b/containerm/mgr/mgr_api.go index 595a47b..ea060ee 100644 --- a/containerm/mgr/mgr_api.go +++ b/containerm/mgr/mgr_api.go @@ -62,7 +62,7 @@ type ContainerManager interface { Rename(ctx context.Context, id string, name string) error // Remove removes a container, it may be running or stopped and so on - Remove(ctx context.Context, id string, force bool) error + Remove(ctx context.Context, id string, force bool, stopOpts *types.StopOpts) error // Metrics retrieves metrics data about a container Metrics(ctx context.Context, id string) (*types.Metrics, error) diff --git a/containerm/mgr/mgr_internal.go b/containerm/mgr/mgr_internal.go index 382f525..9e3171e 100644 --- a/containerm/mgr/mgr_internal.go +++ b/containerm/mgr/mgr_internal.go @@ -390,10 +390,11 @@ func (mgr *containerMgr) stopManagerService(ctx context.Context) error { } return nil } + func (mgr *containerMgr) fillContainerStopDefaults(stopOpts *types.StopOpts) { if stopOpts != nil { if stopOpts.Timeout == 0 { - stopOpts.Timeout = mgr.defaultCtrsStopTimeout + stopOpts.Timeout = int64(mgr.defaultCtrsStopTimeout.Seconds()) } if stopOpts.Signal == "" { stopOpts.Signal = sigterm @@ -402,7 +403,7 @@ func (mgr *containerMgr) fillContainerStopDefaults(stopOpts *types.StopOpts) { } func (mgr *containerMgr) getContainerStopOptions(force bool) *types.StopOpts { return &types.StopOpts{ - Timeout: mgr.defaultCtrsStopTimeout, + Timeout: int64(mgr.defaultCtrsStopTimeout.Seconds()), Force: force, Signal: sigterm, } diff --git a/containerm/mgr/mgr_internal_init.go b/containerm/mgr/mgr_internal_init.go index 6341675..db9338e 100644 --- a/containerm/mgr/mgr_internal_init.go +++ b/containerm/mgr/mgr_internal_init.go @@ -14,6 +14,7 @@ package mgr import ( "fmt" + "time" "github.com/eclipse-kanto/container-management/containerm/containers/types" "github.com/eclipse-kanto/container-management/containerm/ctr" @@ -23,7 +24,7 @@ import ( "github.com/eclipse-kanto/container-management/containerm/util" ) -func newContainerMgr(metaPath string, execPath string, defaultCtrsStopTimeout int64, ctrClient ctr.ContainerAPIClient, netMgr network.ContainerNetworkManager, eventsMgr events.ContainerEventsManager) (ContainerManager, error) { +func newContainerMgr(metaPath string, execPath string, defaultCtrsStopTimeout time.Duration, ctrClient ctr.ContainerAPIClient, netMgr network.ContainerNetworkManager, eventsMgr events.ContainerEventsManager) (ContainerManager, error) { if err := util.MkDir(execPath); err != nil { return nil, err } @@ -46,6 +47,7 @@ func newContainerMgr(metaPath string, execPath string, defaultCtrsStopTimeout in restartCtrsMgrCache: newRestartMgrCache(), containerRepository: &ctrRepository, } + fmt.Println("mgr:", defaultCtrsStopTimeout.String()) ctrClient.SetContainerExitHooks(manager.exitedAndRelease) return manager, nil diff --git a/containerm/mgr/mgr_opts.go b/containerm/mgr/mgr_opts.go index 9021880..cb827b6 100644 --- a/containerm/mgr/mgr_opts.go +++ b/containerm/mgr/mgr_opts.go @@ -12,6 +12,12 @@ package mgr +import ( + "time" + + "github.com/eclipse-kanto/container-management/containerm/log" +) + // ContainerManagerOpt provides container manager options type ContainerManagerOpt func(mgrOptions *mgrOpts) error @@ -20,7 +26,7 @@ type mgrOpts struct { rootExec string containerClientServiceID string networkManagerServiceID string - defaultCtrsStopTimeout int64 + defaultCtrsStopTimeout time.Duration } func applyOptsMgr(mgrOpts *mgrOpts, opts ...ContainerManagerOpt) error { @@ -65,9 +71,16 @@ func WithMgrNetworkManagerServiceID(networkManagerServiceID string) ContainerMan } // WithMgrDefaultContainerStopTimeout sets default container stop timeout. -func WithMgrDefaultContainerStopTimeout(networkManagerCtrsStopTimeout int64) ContainerManagerOpt { +func WithMgrDefaultContainerStopTimeout(managerCtrsStopTimeout interface{}) ContainerManagerOpt { return func(mgrOptions *mgrOpts) error { - mgrOptions.defaultCtrsStopTimeout = networkManagerCtrsStopTimeout + switch v := managerCtrsStopTimeout.(type) { + case int64: + mgrOptions.defaultCtrsStopTimeout = time.Duration(managerCtrsStopTimeout.(int64)) * time.Second + case time.Duration: + mgrOptions.defaultCtrsStopTimeout = managerCtrsStopTimeout.(time.Duration) + default: + return log.NewErrorf("unexpected stop timeout type: %v", v) + } return nil } } diff --git a/containerm/mgr/mgr_opts_test.go b/containerm/mgr/mgr_opts_test.go index 80ff878..4d1b8bc 100644 --- a/containerm/mgr/mgr_opts_test.go +++ b/containerm/mgr/mgr_opts_test.go @@ -75,10 +75,22 @@ func TestMgrOpts(t *testing.T) { networkManagerServiceID: testNetworkManagerServiceID, }, }, - "test_mgs_default_container_stop_timeout": { - testOpt: WithMgrDefaultContainerStopTimeout(testDefaultContainerStopTimeout), + "test_mgr_default_container_stop_timeout": { + testOpt: WithMgrDefaultContainerStopTimeout(testContainerStopTimeout), expectedOpts: &mgrOpts{ - defaultCtrsStopTimeout: testDefaultContainerStopTimeout, + defaultCtrsStopTimeout: testContainerStopTimeout, + }, + }, + "test_mgr_default_container_stop_timeout_int64": { + testOpt: WithMgrDefaultContainerStopTimeout(int64(10)), + expectedOpts: &mgrOpts{ + defaultCtrsStopTimeout: testContainerStopTimeout, + }, + }, + "test_mgr_default_container_stop_timeout_string": { + testOpt: WithMgrDefaultContainerStopTimeout("10"), + expectedOpts: &mgrOpts{ + defaultCtrsStopTimeout: 0, }, }, } diff --git a/containerm/mgr/mgr_test.go b/containerm/mgr/mgr_test.go index afdcec7..3ab1086 100644 --- a/containerm/mgr/mgr_test.go +++ b/containerm/mgr/mgr_test.go @@ -42,8 +42,8 @@ const ( testRootExec = "testRootExec" testContainerClientServiceID = "testContainerClientServiceID" testNetworkManagerServiceID = "testNetworkManagerServiceID" - testDefaultContainerStopTimeout = 10 - testNetworkManagerStopTimeout = 30 + testContainerStopTimeout = time.Duration(10) * time.Second + testDefaultContainerStopTimeout = time.Duration(30) * time.Second ) func TestGetContainer(t *testing.T) { @@ -528,7 +528,7 @@ func TestDeleteContainerFromManager(t *testing.T) { testutil.AssertEqual(t, 1, len(containerCheck)) // Act - unitUnderTest.Remove(context.Background(), containerCheck[0].ID, true) + unitUnderTest.Remove(context.Background(), containerCheck[0].ID, true, &types.StopOpts{Force: true}) containerCheckAfter, err := unitUnderTest.List(context.Background()) testutil.AssertNil(t, err) @@ -1260,7 +1260,7 @@ func createContainerManagerWithCustomMocks( return containerMgr{ metaPath: metaPath, execPath: testRootExec, - defaultCtrsStopTimeout: testNetworkManagerStopTimeout, + defaultCtrsStopTimeout: testDefaultContainerStopTimeout, ctrClient: mockCtrClient, netMgr: mockNetworkManager, eventsMgr: mockEventsManager, diff --git a/containerm/pkg/testutil/config/daemon-config.json b/containerm/pkg/testutil/config/daemon-config.json index 473a48f..538e97b 100644 --- a/containerm/pkg/testutil/config/daemon-config.json +++ b/containerm/pkg/testutil/config/daemon-config.json @@ -12,7 +12,7 @@ "exec_root_dir": "/var/run/container-management", "container_client_sid": "container-management.service.local.v1.service-containerd-client", "network_manager_sid": "container-management.service.local.v1.service-libnetwork-manager", - "default_ctrs_stop_timeout": 30 + "default_ctrs_stop_timeout": "30s" }, "containers": { "default_ns": "kanto-cm", diff --git a/containerm/pkg/testutil/config/daemon-mgr-default-ctrs-stop-timeout-config.json b/containerm/pkg/testutil/config/daemon-mgr-default-ctrs-stop-timeout-config.json new file mode 100644 index 0000000..cc49960 --- /dev/null +++ b/containerm/pkg/testutil/config/daemon-mgr-default-ctrs-stop-timeout-config.json @@ -0,0 +1,5 @@ +{ + "manager": { + "default_ctrs_stop_timeout": 15 + } +} \ No newline at end of file diff --git a/containerm/pkg/testutil/mocks/client/mock_client_api.go b/containerm/pkg/testutil/mocks/client/mock_client_api.go index c29b3c2..ffa9e9c 100644 --- a/containerm/pkg/testutil/mocks/client/mock_client_api.go +++ b/containerm/pkg/testutil/mocks/client/mock_client_api.go @@ -160,17 +160,17 @@ func (mr *MockClientMockRecorder) ProjectInfo(arg0 interface{}) *gomock.Call { } // Remove mocks base method. -func (m *MockClient) Remove(arg0 context.Context, arg1 string, arg2 bool) error { +func (m *MockClient) Remove(arg0 context.Context, arg1 string, arg2 bool, arg3 *types.StopOpts) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Remove", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Remove", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // Remove indicates an expected call of Remove. -func (mr *MockClientMockRecorder) Remove(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Remove(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockClient)(nil).Remove), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockClient)(nil).Remove), arg0, arg1, arg2, arg3) } // Rename mocks base method. diff --git a/containerm/pkg/testutil/mocks/mgr/mock_mgr_api.go b/containerm/pkg/testutil/mocks/mgr/mock_mgr_api.go index 877c559..7308fa6 100644 --- a/containerm/pkg/testutil/mocks/mgr/mock_mgr_api.go +++ b/containerm/pkg/testutil/mocks/mgr/mock_mgr_api.go @@ -150,17 +150,17 @@ func (mr *MockContainerManagerMockRecorder) Pause(arg0, arg1 interface{}) *gomoc } // Remove mocks base method. -func (m *MockContainerManager) Remove(arg0 context.Context, arg1 string, arg2 bool) error { +func (m *MockContainerManager) Remove(arg0 context.Context, arg1 string, arg2 bool, arg3 *types.StopOpts) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Remove", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Remove", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // Remove indicates an expected call of Remove. -func (mr *MockContainerManagerMockRecorder) Remove(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockContainerManagerMockRecorder) Remove(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockContainerManager)(nil).Remove), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockContainerManager)(nil).Remove), arg0, arg1, arg2, arg3) } // Rename mocks base method. diff --git a/containerm/services/grpc_containers_service.go b/containerm/services/grpc_containers_service.go index 21f39dc..c840a8b 100644 --- a/containerm/services/grpc_containers_service.go +++ b/containerm/services/grpc_containers_service.go @@ -169,7 +169,7 @@ func (server *containers) Rename(ctx context.Context, request *pbcontainers.Rena } func (server *containers) Remove(ctx context.Context, request *pbcontainers.RemoveContainerRequest) (*empty.Empty, error) { - err := server.mgr.Remove(ctx, request.Id, request.Force) + err := server.mgr.Remove(ctx, request.Id, request.Force, protobuf.ToInternalStopOptions(request.StopOptions)) if err != nil { return nil, err } diff --git a/containerm/services/grpc_services_test.go b/containerm/services/grpc_services_test.go index 05c86b7..2e4834f 100644 --- a/containerm/services/grpc_services_test.go +++ b/containerm/services/grpc_services_test.go @@ -517,6 +517,20 @@ func TestRemove(t *testing.T) { }, mockExecution: mockExecRemoveErrors, }, + "test_remove_timeout": { + args: testRemoveArgs{ + ctx: testCtx, + request: &pbcontainers.RemoveContainerRequest{ + Id: containerID, + Force: true, + StopOptions: &pbcontainerstypes.StopOptions{ + Timeout: 20, + Force: true, + }, + }, + }, + mockExecution: mockExecRemoveNoErrors, + }, } // execute tests @@ -807,8 +821,7 @@ func mockExecStopNoErrors(args testStopArgs) (*empty.Empty, error) { } func mockExecStopDefaultOpts(args testStopArgs) (*empty.Empty, error) { - stopOpts := &types.StopOpts{} - mockContainerManager.EXPECT().Stop(args.ctx, args.request.Id, gomock.Eq(stopOpts)).Times(1).Return(nil) + mockContainerManager.EXPECT().Stop(args.ctx, args.request.Id, nil).Times(1).Return(nil) return &empty.Empty{}, nil } @@ -844,18 +857,20 @@ func mockExecUnpauseErrors(args testUnpauseArgs) (*empty.Empty, error) { // Remove ------------------------------------------------------------- func mockExecRemoveNoErrors(args testRemoveArgs) (*empty.Empty, error) { - mockContainerManager.EXPECT().Remove(args.ctx, args.request.Id, args.request.Force).Times(1).Return(nil) + mockContainerManager.EXPECT().Remove(args.ctx, args.request.Id, args.request.Force, gomock.Eq(protobuf.ToInternalStopOptions(args.request.StopOptions))).Times(1).Return(nil) return &empty.Empty{}, nil } func mockExecRemoveForce(args testRemoveArgs) (*empty.Empty, error) { - mockContainerManager.EXPECT().Remove(args.ctx, args.request.Id, true).Times(1).Return(nil) + mockContainerManager.EXPECT().Remove(args.ctx, args.request.Id, true, gomock.Eq(protobuf.ToInternalStopOptions(args.request.StopOptions))).Times(1).Return(nil) return &empty.Empty{}, nil } +//TODO add tests for timeout + func mockExecRemoveErrors(args testRemoveArgs) (*empty.Empty, error) { err := errors.New("failed to remove container") - mockContainerManager.EXPECT().Remove(args.ctx, args.request.Id, args.request.Force).Times(1).Return(err) + mockContainerManager.EXPECT().Remove(args.ctx, args.request.Id, args.request.Force, gomock.Eq(protobuf.ToInternalStopOptions(args.request.StopOptions))).Times(1).Return(err) return nil, err } diff --git a/containerm/things/features_container.go b/containerm/things/features_container.go index 8973694..dd066c1 100644 --- a/containerm/things/features_container.go +++ b/containerm/things/features_container.go @@ -152,7 +152,7 @@ func (ctrFeature *containerFeature) stopWithOptions(ctx context.Context, opts *s }) } func (ctrFeature *containerFeature) remove(ctx context.Context, force bool) error { - return ctrFeature.mgr.Remove(ctx, extractContainerID(ctrFeature.id), force) + return ctrFeature.mgr.Remove(ctx, extractContainerID(ctrFeature.id), force, nil) } func (ctrFeature *containerFeature) rename(ctx context.Context, name string) error { diff --git a/containerm/things/features_container_test.go b/containerm/things/features_container_test.go index a96a0fc..eeb9bed 100644 --- a/containerm/things/features_container_test.go +++ b/containerm/things/features_container_test.go @@ -272,7 +272,7 @@ func TestFeatureOperationsHandlerRemove(t *testing.T) { "test_feature_operations_handler_remove_no_errors": { opts: testValidUnmarshaled, mockExecution: func() error { - mockContainerManager.EXPECT().Remove(gomock.Any(), testContainerID, true).Times(1) + mockContainerManager.EXPECT().Remove(gomock.Any(), testContainerID, true, nil).Times(1) return nil }, }, @@ -296,7 +296,7 @@ func TestFeatureOperationsHandlerRemove(t *testing.T) { opts: testValidUnmarshaled, mockExecution: func() error { err := log.NewError("error while removing") - mockContainerManager.EXPECT().Remove(gomock.Any(), testContainerID, true).Times(1).Return(err) + mockContainerManager.EXPECT().Remove(gomock.Any(), testContainerID, true, nil).Times(1).Return(err) return err }, }, @@ -454,7 +454,7 @@ func TestFeatureOperationsUnsupportedOperation(t *testing.T) { mockContainerManager.EXPECT().Stop(gomock.Any(), testContainerID, gomock.Any()).Times(0) mockContainerManager.EXPECT().Pause(gomock.Any(), testContainerID).Times(0) mockContainerManager.EXPECT().Unpause(gomock.Any(), testContainerID).Times(0) - mockContainerManager.EXPECT().Remove(gomock.Any(), testContainerID, true).Times(0) + mockContainerManager.EXPECT().Remove(gomock.Any(), testContainerID, true, nil).Times(0) t.Run("test_feature_operations_handler_unsupported_operation", func(t *testing.T) { result, resultErr := containerFeature.featureOperationsHandler(testUnsupportedOperationName, nil) diff --git a/containerm/things/features_rollouts_software_updatable_internal.go b/containerm/things/features_rollouts_software_updatable_internal.go index a2d9983..cc50b51 100644 --- a/containerm/things/features_rollouts_software_updatable_internal.go +++ b/containerm/things/features_rollouts_software_updatable_internal.go @@ -258,7 +258,7 @@ func (su *softwareUpdatable) removeDependency(toRemove *datatypes.DependencyDesc log.Warn("container with ID = %s does not exist", toRemove.Name) err = log.NewErrorf("container with ID = %s does not exist", toRemove.Name) } else { - err = su.mgr.Remove(ctx, toRemove.Name, true) // TODO currently matching only on Name - the container id + err = su.mgr.Remove(ctx, toRemove.Name, true, nil) // TODO currently matching only on Name - the container id } return err } diff --git a/containerm/things/features_rollouts_software_updatable_internal_test.go b/containerm/things/features_rollouts_software_updatable_internal_test.go index 232ceca..51ed6da 100644 --- a/containerm/things/features_rollouts_software_updatable_internal_test.go +++ b/containerm/things/features_rollouts_software_updatable_internal_test.go @@ -414,7 +414,7 @@ func mockExecInstallErrorNoAtrifacts(t *testing.T) error { func mockExecRemoveNoDependencyDescription(t *testing.T) error { setupSUFeature(t) - mockContainerManager.EXPECT().Remove(gomock.Any(), gomock.Any(), gomock.Any()).Times(0).Return(nil) + mockContainerManager.EXPECT().Remove(gomock.Any(), gomock.Any(), gomock.Any(), nil).Times(0).Return(nil) mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), gomock.Any(), gomock.Any()).Times(0).Return(nil) return client.NewMessagesParameterInvalidError("there are no DependencyDescriptions to be removed") } @@ -466,14 +466,14 @@ func mockExecSURemoveForced(t *testing.T) error { gomock.InOrder( mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastOperationProperty, testOperationRemoveStatusRemoving).Times(1).Return(nil), mockContainerManager.EXPECT().Get(gomock.Any(), testSoftwareName).Return(&types.Container{}, nil), - mockContainerManager.EXPECT().Remove(gomock.Any(), testSoftwareName, true).Return(nil), + mockContainerManager.EXPECT().Remove(gomock.Any(), testSoftwareName, true, nil).Return(nil), mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastOperationProperty, testOperationRemovingStatusRemoved).Times(1).Return(nil), mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastOperationProperty, testOperationRemoveStatusRemoving).Times(1).Return(nil), mockContainerManager.EXPECT().Get(gomock.Any(), testSoftwareName).Return(&types.Container{}, nil), - mockContainerManager.EXPECT().Remove(gomock.Any(), testSoftwareName, true).Return(log.NewErrorf(testOperationRemoveErrorWhileRemovingMessage)), + mockContainerManager.EXPECT().Remove(gomock.Any(), testSoftwareName, true, nil).Return(log.NewErrorf(testOperationRemoveErrorWhileRemovingMessage)), mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastOperationProperty, testOperationRemoveStatusRemoving).Times(1).Return(nil), mockContainerManager.EXPECT().Get(gomock.Any(), testSoftwareName).Return(&types.Container{}, nil), - mockContainerManager.EXPECT().Remove(gomock.Any(), testSoftwareName, true).Return(nil), + mockContainerManager.EXPECT().Remove(gomock.Any(), testSoftwareName, true, nil).Return(nil), mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastOperationProperty, testOperationRemovingStatusRemoved).Times(1).Return(nil), mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastFailedOperationProperty, testOperationRemoveStatusRemovingFinishedError).Times(1).Return(nil), mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastOperationProperty, testOperationRemoveStatusRemovingFinishedError).Times(1).Return(nil), @@ -488,7 +488,7 @@ func mockExecSURemoveNoSuchContainer(t *testing.T) error { gomock.InOrder( mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastOperationProperty, testOperationRemoveStatusRemoving).Times(1).Return(nil), mockContainerManager.EXPECT().Get(gomock.Any(), testSoftwareName).Return(nil, nil), - mockContainerManager.EXPECT().Remove(gomock.Any(), testSoftwareName, true).Times(0).Return(nil), + mockContainerManager.EXPECT().Remove(gomock.Any(), testSoftwareName, true, nil).Times(0).Return(nil), mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastFailedOperationProperty, testOperationRemoveStatusRemovingFinishedErrorNoSuchContainer).Times(1).Return(nil), mockThing.EXPECT().SetFeatureProperty(testSUFeature.GetID(), testLastOperationProperty, testOperationRemoveStatusRemovingFinishedErrorNoSuchContainer).Times(1).Return(nil), ) diff --git a/containerm/updateagent/update_operation.go b/containerm/updateagent/update_operation.go index 3422bfb..43fcc95 100644 --- a/containerm/updateagent/update_operation.go +++ b/containerm/updateagent/update_operation.go @@ -611,7 +611,7 @@ func (o *operation) ensureRunningContainer(current *ctrtypes.Container) error { func (o *operation) removeContainer(container *ctrtypes.Container) error { log.Debug("container [%s] is not desired - will be removed", container.Name) - if err := o.updateManager.mgr.Remove(o.ctx, container.ID, true); err != nil { + if err := o.updateManager.mgr.Remove(o.ctx, container.ID, true, nil); err != nil { log.ErrorErr(err, "could not remove undesired container [%s]", container.Name) return err } diff --git a/containerm/updateagent/update_operation_test.go b/containerm/updateagent/update_operation_test.go index 614ac85..96e0e4d 100644 --- a/containerm/updateagent/update_operation_test.go +++ b/containerm/updateagent/update_operation_test.go @@ -83,9 +83,9 @@ func TestExecute(t *testing.T) { expActions1 := copyAndUpdateActions(testctx.actions, 4, types.ActionStatusRemovalSuccess, "Old container instance is removed.") gomock.InOrder( // call to ContainerManager to remove old instance of test-container-2 - mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[1].ID, true).Return(nil), + mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[1].ID, true, nil).Return(nil), // call to ContainerManager to remove instance of test-container-5 - mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[3].ID, true).Return(nil), + mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[3].ID, true, nil).Return(nil), expectFeedback(t, mockCallback, testctx.activityID, testctx.baseline, types.BaselineStatusCleanupSuccess, expActions1), expectFeedback(t, mockCallback, testctx.activityID, testctx.baseline, types.StatusCompleted, expActions1), ) @@ -151,7 +151,7 @@ func TestExecute(t *testing.T) { expect: func(t *testing.T, mockContainerManager *mgrmocks.MockContainerManager, mockCallback *ummocks.MockUpdateManagerCallback, testctx *testContext) { gomock.InOrder( // call to ContainerManager to remove old instance of test-container-2 - mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[1].ID, true).Return(nil), + mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[1].ID, true, nil).Return(nil), expectFeedback(t, mockCallback, testctx.activityID, testctx.baseline, types.BaselineStatusCleanupSuccess, testctx.actions), ) }, @@ -249,7 +249,7 @@ func TestExecute(t *testing.T) { expActions1 := copyAndUpdateActions(testctx.actions, 4, types.ActionStatusRemovalSuccess, "Old container instance is removed.") gomock.InOrder( // call to ContainerManager to remove instance of test-container-5 - mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[3].ID, true).Return(nil), + mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[3].ID, true, nil).Return(nil), expectFeedback(t, mockCallback, testctx.activityID, testctx.baseline, types.BaselineStatusCleanupSuccess, expActions1), ) testctx.actions = expActions1 @@ -636,9 +636,9 @@ func TestExecute(t *testing.T) { expActions1 := copyAndUpdateActions(testctx.actions, 4, types.ActionStatusRemovalSuccess, "Old container instance is removed.") gomock.InOrder( // call to ContainerManager to remove old instance of test-container-2 - mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[1].ID, true).Return(errors.New("cannot remove old container instance")), + mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[1].ID, true, nil).Return(errors.New("cannot remove old container instance")), // call to ContainerManager to remove instance of test-container-5 - mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[3].ID, true).Return(nil), + mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[3].ID, true, nil).Return(nil), expectFeedback(t, mockCallback, testctx.activityID, testctx.baseline, types.BaselineStatusCleanupSuccess, expActions1), expectFeedback(t, mockCallback, testctx.activityID, testctx.baseline, types.StatusCompleted, expActions1), ) @@ -667,9 +667,9 @@ func TestExecute(t *testing.T) { expActions1 := copyAndUpdateActions(testctx.actions, 4, types.ActionStatusRemovalFailure, "old container instance cannot be removed") gomock.InOrder( // call to ContainerManager to remove old instance of test-container-2 - mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[1].ID, true).Return(nil), + mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[1].ID, true, nil).Return(nil), // call to ContainerManager to remove instance of test-container-5 - mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[3].ID, true).Return(errors.New("old container instance cannot be removed")), + mockContainerManager.EXPECT().Remove(context.Background(), testctx.currentContainers[3].ID, true, nil).Return(errors.New("old container instance cannot be removed")), expectFeedback(t, mockCallback, testctx.activityID, testctx.baseline, types.BaselineStatusCleanupFailure, expActions1), expectFeedback(t, mockCallback, testctx.activityID, testctx.baseline, types.StatusIncomplete, expActions1), ) diff --git a/containerm/util/protobuf/convert_test.go b/containerm/util/protobuf/convert_test.go index 5f2505f..af574aa 100644 --- a/containerm/util/protobuf/convert_test.go +++ b/containerm/util/protobuf/convert_test.go @@ -204,9 +204,7 @@ func TestConvertContainer(t *testing.T) { util.SetContainerStatusCreated(ctr) t.Run("test_convert_container", func(t *testing.T) { - protoCtr := ToProtoContainer(ctr) - internalCtr := ToInternalContainer(protoCtr) - testutil.AssertEqual(t, ctr, internalCtr) + testutil.AssertEqual(t, ctr, ToInternalContainer(ToProtoContainer(ctr))) }) } @@ -215,9 +213,7 @@ func TestConvertContainerEmpty(t *testing.T) { Image: internalImage, } t.Run("test_convert_container_empty", func(t *testing.T) { - protoCtr := ToProtoContainer(ctr) - internalCtr := ToInternalContainer(protoCtr) - testutil.AssertEqual(t, ctr, internalCtr) + testutil.AssertEqual(t, ctr, ToInternalContainer(ToProtoContainer(ctr))) }) } @@ -330,12 +326,15 @@ func TestToInternalStopOpts(t *testing.T) { stopOpts := &internaltypes.StopOpts{ Timeout: 20, Force: true, + Signal: "SIGTERM", } t.Run("test_convert_stop_options", func(t *testing.T) { - protoStopOpts := ToProtoStopOptions(stopOpts) - internalStopOpts := ToInternalStopOptions(protoStopOpts) - testutil.AssertEqual(t, stopOpts, internalStopOpts) + testutil.AssertEqual(t, stopOpts, ToInternalStopOptions(ToProtoStopOptions(stopOpts))) + }) + + t.Run("test_convert_stop_options_nil", func(t *testing.T) { + testutil.AssertNil(t, ToInternalStopOptions(ToProtoStopOptions(nil))) }) } @@ -348,9 +347,7 @@ func TestToInternalProjectInfo(t *testing.T) { } t.Run("test_convert_projet_info", func(t *testing.T) { - protoProjectInfo := ToProtoProjectInfo(projectInfo) - internalProjectInfo := ToInternalProjectInfo(protoProjectInfo) - testutil.AssertEqual(t, projectInfo, internalProjectInfo) + testutil.AssertEqual(t, projectInfo, ToInternalProjectInfo(ToProtoProjectInfo(projectInfo))) }) } @@ -369,8 +366,10 @@ func TestToInternalUpdateOpts(t *testing.T) { } t.Run("test_convert_update_options", func(t *testing.T) { - protoUpdateOpts := ToProtoUpdateOptions(updateOpts) - internalUpdateOpts := ToInternalUpdateOptions(protoUpdateOpts) - testutil.AssertEqual(t, updateOpts, internalUpdateOpts) + testutil.AssertEqual(t, updateOpts, ToInternalUpdateOptions(ToProtoUpdateOptions(updateOpts))) + }) + + t.Run("test_convert_update_options_nil", func(t *testing.T) { + testutil.AssertEqual(t, &internaltypes.UpdateOpts{RestartPolicy: nil, Resources: nil}, ToInternalUpdateOptions(ToProtoUpdateOptions(nil))) }) } diff --git a/containerm/util/protobuf/convert_to_internal_util.go b/containerm/util/protobuf/convert_to_internal_util.go index 34b5da1..0f71f7e 100644 --- a/containerm/util/protobuf/convert_to_internal_util.go +++ b/containerm/util/protobuf/convert_to_internal_util.go @@ -360,7 +360,7 @@ func ToInternalLogModeConfig(grpcLogModeConfig *apitypescontainers.LogModeConfig // ToInternalStopOptions converts a types.StopOptions to an internal StopOptions func ToInternalStopOptions(grpcStopOptions *apitypescontainers.StopOptions) *internaltypes.StopOpts { if grpcStopOptions == nil { - return &internaltypes.StopOpts{} + return nil } return &internaltypes.StopOpts{ Timeout: grpcStopOptions.Timeout, diff --git a/containerm/util/protobuf/convert_to_proto_util.go b/containerm/util/protobuf/convert_to_proto_util.go index d97f525..595dbca 100644 --- a/containerm/util/protobuf/convert_to_proto_util.go +++ b/containerm/util/protobuf/convert_to_proto_util.go @@ -327,6 +327,9 @@ func ToProtoLogModeConfig(internalLogModeConfig *internaltypes.LogModeConfigurat // ToProtoStopOptions converts an internal StopOpts instance to a types.StopOpts one func ToProtoStopOptions(intenralStopOpts *internaltypes.StopOpts) *apitypescontainers.StopOptions { + if intenralStopOpts == nil { + return nil + } return &apitypescontainers.StopOptions{ Timeout: intenralStopOpts.Timeout, Force: intenralStopOpts.Force, @@ -336,6 +339,9 @@ func ToProtoStopOptions(intenralStopOpts *internaltypes.StopOpts) *apitypesconta // ToProtoUpdateOptions converts an internal UpdateOpts instance to a types.UpdateOpts one func ToProtoUpdateOptions(intenralUpdateOpts *internaltypes.UpdateOpts) *apitypescontainers.UpdateOptions { + if intenralUpdateOpts == nil { + return nil + } return &apitypescontainers.UpdateOptions{ RestartPolicy: ToProtoRestartPolicy(intenralUpdateOpts.RestartPolicy), Resources: ToProtoResource(intenralUpdateOpts.Resources), diff --git a/integration/testdata/list-test.yaml b/integration/testdata/list-test.yaml index a7e685f..f809fcd 100644 --- a/integration/testdata/list-test.yaml +++ b/integration/testdata/list-test.yaml @@ -12,7 +12,7 @@ command: args: ["list", "--host", "$KANTO_HOST"] expected: exitCode: 0 - out: "No found containers." + out: "No containers found." --- name: list_all_containers setupCmd: @@ -112,7 +112,7 @@ command: args: ["list", "--host", "$KANTO_HOST", "-n", "invalid"] expected: exitCode: 0 - out: "No found containers." + out: "No containers found." --- name: list_invalid_arg command: