From fdd3800b7857f7c45180300bc49a56b3a3ef237a Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Mon, 11 Apr 2022 17:35:07 +0000 Subject: [PATCH 1/3] [ws-manager] Introduce workspace classes --- components/ws-manager-api/core.proto | 6 + components/ws-manager-api/go/config/config.go | 55 ++- components/ws-manager-api/go/core.pb.go | 461 +++++++++--------- .../typescript/src/core_pb.d.ts | 78 +-- .../ws-manager-api/typescript/src/core_pb.js | 64 ++- components/ws-manager/pkg/manager/create.go | 60 ++- .../ws-manager/pkg/manager/create_test.go | 103 ++-- .../pkg/manager/integration_test.go | 43 +- components/ws-manager/pkg/manager/manager.go | 13 +- components/ws-manager/pkg/manager/status.go | 1 + .../pkg/manager/testdata/cdwp_class.golden | 269 ++++++++++ .../pkg/manager/testdata/cdwp_class.json | 79 +++ .../status_firstUserActivity_RUNNING.golden | 3 +- .../status_firstUserActivity_RUNNING.json | 3 +- .../ws-manager/pkg/manager/testing_test.go | 23 +- 15 files changed, 893 insertions(+), 368 deletions(-) create mode 100644 components/ws-manager/pkg/manager/testdata/cdwp_class.golden create mode 100644 components/ws-manager/pkg/manager/testdata/cdwp_class.json diff --git a/components/ws-manager-api/core.proto b/components/ws-manager-api/core.proto index 4427d9f5862e40..60efd04d853edf 100644 --- a/components/ws-manager-api/core.proto +++ b/components/ws-manager-api/core.proto @@ -307,6 +307,9 @@ message WorkspaceSpec { // ide_image is the name of the Docker image used as IDE IDEImage ide_image = 8; + + // class names the class of this workspace + string class = 9; } // PortSpec describes a networking port exposed on a workspace @@ -493,6 +496,9 @@ message StartWorkspaceSpec { // ide_image is the Docker image name of the IDE image IDEImage ide_image = 12; + + // Class denotes the class of the workspace we ought to start + string class = 13; } // WorkspaceFeatureFlag enable non-standard behaviour in workspaces diff --git a/components/ws-manager-api/go/config/config.go b/components/ws-manager-api/go/config/config.go index 6346d64fcc3211..f81787a7d2842f 100644 --- a/components/ws-manager-api/go/config/config.go +++ b/components/ws-manager-api/go/config/config.go @@ -69,15 +69,11 @@ type Configuration struct { SchedulerName string `json:"schedulerName"` // SeccompProfile names the seccomp profile workspaces will use SeccompProfile string `json:"seccompProfile"` - // Container configures all three workspace containers - Container AllContainerConfiguration `json:"container"` // Timeouts configures how long workspaces can be without activity before they're shut down. // All values in here must be valid time.Duration Timeouts WorkspaceTimeoutConfiguration `json:"timeouts"` // InitProbe configures the ready-probe of workspaces which signal when the initialization is finished InitProbe InitProbeConfiguration `json:"initProbe"` - // WorkspacePodTemplate is a path to a workspace pod template YAML file - WorkspacePodTemplate WorkspacePodTemplateConfiguration `json:"podTemplate,omitempty"` // WorkspaceCACertSecret optionally names a secret which is mounted in `/etc/ssl/certs/gp-custom.crt` // in all workspace pods. WorkspaceCACertSecret string `json:"caCertSecret,omitempty"` @@ -113,11 +109,13 @@ type Configuration struct { RegistryFacadeHost string `json:"registryFacadeHost"` // Cluster host under which workspaces are served, e.g. ws-eu11.gitpod.io WorkspaceClusterHost string `json:"workspaceClusterHost"` + // WorkspaceClasses provide different resource classes for workspaces + WorkspaceClasses map[string]*WorkspaceClass `json:"workspaceClass"` } -// AllContainerConfiguration contains the configuration for all container in a workspace pod -type AllContainerConfiguration struct { - Workspace ContainerConfiguration `json:"workspace"` +type WorkspaceClass struct { + Container ContainerConfiguration `json:"container"` + Templates WorkspacePodTemplateConfiguration `json:"templates"` } // WorkspaceTimeoutConfiguration configures the timeout behaviour of workspaces @@ -185,10 +183,6 @@ type WorkspaceDaemonConfiguration struct { // Validate validates the configuration to catch issues during startup and not at runtime func (c *Configuration) Validate() error { - if err := c.Container.Workspace.Validate(); err != nil { - return xerrors.Errorf("container.workspace: %w", err) - } - err := validation.ValidateStruct(&c.Timeouts, validation.Field(&c.Timeouts.AfterClose, validation.Required), validation.Field(&c.Timeouts.HeadlessWorkspace, validation.Required), @@ -206,16 +200,6 @@ func (c *Configuration) Validate() error { return xerrors.Errorf("stopping timeout must be greater than content finalization timeout") } - err = validation.ValidateStruct(&c.WorkspacePodTemplate, - validation.Field(&c.WorkspacePodTemplate.DefaultPath, validPodTemplate), - validation.Field(&c.WorkspacePodTemplate.PrebuildPath, validPodTemplate), - validation.Field(&c.WorkspacePodTemplate.ProbePath, validPodTemplate), - validation.Field(&c.WorkspacePodTemplate.RegularPath, validPodTemplate), - ) - if err != nil { - return xerrors.Errorf("workspacePodTemplate: %w", err) - } - err = validation.ValidateStruct(c, validation.Field(&c.WorkspaceURLTemplate, validation.Required, validWorkspaceURLTemplate), validation.Field(&c.WorkspaceHostPath, validation.Required), @@ -223,6 +207,26 @@ func (c *Configuration) Validate() error { validation.Field(&c.GitpodHostURL, validation.Required, is.URL), validation.Field(&c.ReconnectionInterval, validation.Required), ) + if err != nil { + return err + } + + for name, class := range c.WorkspaceClasses { + if err := class.Container.Validate(); err != nil { + return xerrors.Errorf("workspace class %s: %w", name, err) + } + + err = validation.ValidateStruct(&class.Templates, + validation.Field(&class.Templates.DefaultPath, validPodTemplate), + validation.Field(&class.Templates.PrebuildPath, validPodTemplate), + validation.Field(&class.Templates.ProbePath, validPodTemplate), + validation.Field(&class.Templates.RegularPath, validPodTemplate), + ) + if err != nil { + return xerrors.Errorf("workspace class %s: %w", name, err) + } + } + return err } @@ -256,15 +260,13 @@ var validWorkspaceURLTemplate = validation.By(func(o interface{}) error { // ContainerConfiguration configures properties of workspace pod container type ContainerConfiguration struct { - Image string `json:"image"` - Requests ResourceConfiguration `json:"requests"` - Limits ResourceConfiguration `json:"limits"` + Requests *ResourceConfiguration `json:"requests,omitempty"` + Limits *ResourceConfiguration `json:"limits,omitempty"` } // Validate validates a container configuration func (c *ContainerConfiguration) Validate() error { return validation.ValidateStruct(c, - validation.Field(&c.Image, validation.Required), validation.Field(&c.Requests, validResourceConfig), validation.Field(&c.Limits, validResourceConfig), ) @@ -298,6 +300,9 @@ var validResourceConfig = validation.By(func(o interface{}) error { // ResourceList parses the quantities in the resource config func (r *ResourceConfiguration) ResourceList() (corev1.ResourceList, error) { + if r == nil { + return corev1.ResourceList{}, nil + } res := map[corev1.ResourceName]string{ corev1.ResourceCPU: r.CPU, corev1.ResourceMemory: r.Memory, diff --git a/components/ws-manager-api/go/core.pb.go b/components/ws-manager-api/go/core.pb.go index 52a650a8a66749..497124d2630217 100644 --- a/components/ws-manager-api/go/core.pb.go +++ b/components/ws-manager-api/go/core.pb.go @@ -1870,6 +1870,8 @@ type WorkspaceSpec struct { Timeout string `protobuf:"bytes,7,opt,name=timeout,proto3" json:"timeout,omitempty"` // ide_image is the name of the Docker image used as IDE IdeImage *IDEImage `protobuf:"bytes,8,opt,name=ide_image,json=ideImage,proto3" json:"ide_image,omitempty"` + // class names the class of this workspace + Class string `protobuf:"bytes,9,opt,name=class,proto3" json:"class,omitempty"` } func (x *WorkspaceSpec) Reset() { @@ -1960,6 +1962,13 @@ func (x *WorkspaceSpec) GetIdeImage() *IDEImage { return nil } +func (x *WorkspaceSpec) GetClass() string { + if x != nil { + return x.Class + } + return "" +} + // PortSpec describes a networking port exposed on a workspace type PortSpec struct { state protoimpl.MessageState @@ -2393,6 +2402,8 @@ type StartWorkspaceSpec struct { Admission AdmissionLevel `protobuf:"varint,11,opt,name=admission,proto3,enum=wsman.AdmissionLevel" json:"admission,omitempty"` // ide_image is the Docker image name of the IDE image IdeImage *IDEImage `protobuf:"bytes,12,opt,name=ide_image,json=ideImage,proto3" json:"ide_image,omitempty"` + // Class denotes the class of the workspace we ought to start + Class string `protobuf:"bytes,13,opt,name=class,proto3" json:"class,omitempty"` } func (x *StartWorkspaceSpec) Reset() { @@ -2504,6 +2515,13 @@ func (x *StartWorkspaceSpec) GetIdeImage() *IDEImage { return nil } +func (x *StartWorkspaceSpec) GetClass() string { + if x != nil { + return x.Class + } + return "" +} + // GitSpec configures the Git available within the workspace type GitSpec struct { state protoimpl.MessageState @@ -2891,7 +2909,7 @@ var file_core_proto_rawDesc = []byte{ 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x66, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, - 0x73, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x22, 0xc0, 0x02, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x73, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x22, 0xd6, 0x02, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6d, 0x61, 0x67, @@ -2911,231 +2929,234 @@ var file_core_proto_rawDesc = []byte{ 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x2c, 0x0a, 0x09, 0x69, 0x64, 0x65, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x49, 0x44, 0x45, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, - 0x08, 0x69, 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x6d, 0x0a, 0x08, 0x50, 0x6f, 0x72, - 0x74, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x35, 0x0a, 0x0a, 0x76, 0x69, 0x73, - 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, - 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x72, 0x6c, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xd3, 0x04, 0x0a, 0x13, 0x57, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x16, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, - 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x12, 0x44, 0x0a, 0x0e, 0x70, 0x75, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6d, - 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x77, 0x73, 0x6d, - 0x61, 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x0d, 0x70, 0x75, 0x6c, 0x6c, 0x69, - 0x6e, 0x67, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x12, 0x51, 0x0a, 0x15, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x62, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x06, 0x20, + 0x08, 0x69, 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, + 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x22, + 0x6d, 0x0a, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, + 0x35, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x50, 0x6f, 0x72, 0x74, + 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xd3, + 0x04, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x18, + 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x44, 0x0a, 0x0e, 0x70, 0x75, 0x6c, 0x6c, + 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, + 0x0d, 0x70, 0x75, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1a, + 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x51, 0x0a, 0x15, 0x66, 0x69, + 0x6e, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, + 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x13, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x39, 0x0a, + 0x08, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x08, + 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, - 0x6f, 0x6c, 0x52, 0x13, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x43, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x64, 0x65, 0x70, 0x6c, 0x6f, - 0x79, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, - 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x08, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, - 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6e, 0x6f, - 0x74, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, + 0x6f, 0x6c, 0x52, 0x0f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4e, 0x6f, 0x74, 0x52, 0x65, + 0x61, 0x64, 0x79, 0x12, 0x4a, 0x0a, 0x13, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x11, 0x66, 0x69, + 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x12, + 0x30, 0x0a, 0x14, 0x68, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x61, 0x73, 0x6b, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x68, + 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x54, 0x61, 0x73, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x65, + 0x64, 0x12, 0x4b, 0x0a, 0x12, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x0f, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4e, 0x6f, 0x74, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x4a, 0x0a, - 0x13, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x11, 0x66, 0x69, 0x72, 0x73, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x68, 0x65, 0x61, - 0x64, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, - 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x68, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, - 0x73, 0x54, 0x61, 0x73, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x4b, 0x0a, 0x12, 0x73, - 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x10, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x42, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x8a, - 0x02, 0x0a, 0x11, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x65, - 0x74, 0x61, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, - 0x61, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x4b, - 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x6e, - 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, - 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, - 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x67, 0x0a, 0x14, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6f, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x70, 0x6f, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6e, - 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, - 0x64, 0x65, 0x49, 0x70, 0x22, 0x6f, 0x0a, 0x17, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x33, 0x0a, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x10, 0x73, 0x74, + 0x6f, 0x70, 0x70, 0x65, 0x64, 0x42, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x04, + 0x08, 0x04, 0x10, 0x05, 0x22, 0x8a, 0x02, 0x0a, 0x11, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x61, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x77, 0x73, 0x6d, 0x61, + 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x67, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, + 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6f, 0x64, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6f, 0x64, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x70, 0x22, 0x6f, 0x0a, 0x17, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, + 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, + 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xc0, 0x04, 0x0a, 0x12, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x70, + 0x65, 0x63, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x64, + 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x65, 0x5f, 0x69, 0x6d, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x64, 0x65, 0x70, 0x72, 0x65, + 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x40, 0x0a, + 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, + 0x67, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, + 0x46, 0x0a, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, + 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x52, 0x0b, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x25, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x50, + 0x6f, 0x72, 0x74, 0x53, 0x70, 0x65, 0x63, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x34, + 0x0a, 0x07, 0x65, 0x6e, 0x76, 0x76, 0x61, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, + 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x07, 0x65, 0x6e, 0x76, + 0x76, 0x61, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x11, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x03, 0x67, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x47, 0x69, 0x74, 0x53, 0x70, 0x65, 0x63, + 0x52, 0x03, 0x67, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, + 0x33, 0x0a, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xaa, 0x04, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x27, 0x0a, 0x0f, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x65, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x12, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, - 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1b, - 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x0c, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x46, 0x0a, 0x0b, 0x69, 0x6e, 0x69, - 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x72, 0x52, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, - 0x72, 0x12, 0x25, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x09, 0x69, 0x64, 0x65, 0x5f, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, + 0x49, 0x44, 0x45, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x08, 0x69, 0x64, 0x65, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x22, 0x3b, + 0x0a, 0x07, 0x47, 0x69, 0x74, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0xc3, 0x01, 0x0a, 0x13, + 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, + 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, + 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x4b, 0x65, 0x79, 0x52, 0x65, 0x66, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x1a, 0x41, + 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x66, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x22, 0x35, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, + 0x73, 0x12, 0x25, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x70, 0x65, - 0x63, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x6e, 0x76, 0x76, - 0x61, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x77, 0x73, 0x6d, 0x61, - 0x6e, 0x2e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x07, 0x65, 0x6e, 0x76, 0x76, 0x61, 0x72, 0x73, 0x12, 0x2d, - 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, - 0x03, 0x67, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x77, 0x73, 0x6d, - 0x61, 0x6e, 0x2e, 0x47, 0x69, 0x74, 0x53, 0x70, 0x65, 0x63, 0x52, 0x03, 0x67, 0x69, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x33, 0x0a, 0x09, 0x61, 0x64, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x77, - 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, - 0x0a, 0x09, 0x69, 0x64, 0x65, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x49, 0x44, 0x45, 0x49, 0x6d, 0x61, - 0x67, 0x65, 0x52, 0x08, 0x69, 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4a, 0x04, 0x08, 0x07, - 0x10, 0x08, 0x22, 0x3b, 0x0a, 0x07, 0x47, 0x69, 0x74, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1a, 0x0a, - 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, - 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, - 0xc3, 0x01, 0x0a, 0x13, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x27, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, - 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x53, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x66, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x1a, 0x41, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, - 0x65, 0x66, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x35, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, - 0x50, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x50, 0x6f, 0x72, - 0x74, 0x53, 0x70, 0x65, 0x63, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2a, 0x34, 0x0a, 0x13, - 0x53, 0x74, 0x6f, 0x70, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x4c, 0x59, 0x10, - 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4d, 0x4d, 0x45, 0x44, 0x49, 0x41, 0x54, 0x45, 0x4c, 0x59, - 0x10, 0x01, 0x2a, 0x3a, 0x0a, 0x0e, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, 0x4d, 0x49, 0x54, 0x5f, 0x4f, 0x57, - 0x4e, 0x45, 0x52, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x44, - 0x4d, 0x49, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x52, 0x59, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x2a, 0x49, - 0x0a, 0x0e, 0x50, 0x6f, 0x72, 0x74, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, - 0x12, 0x1b, 0x0a, 0x17, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x56, 0x49, 0x53, 0x49, 0x42, 0x49, 0x4c, - 0x49, 0x54, 0x59, 0x5f, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, - 0x16, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x56, 0x49, 0x53, 0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, - 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x01, 0x2a, 0x38, 0x0a, 0x16, 0x57, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, - 0x6f, 0x6f, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x4c, 0x53, 0x45, 0x10, 0x00, 0x12, 0x08, - 0x0a, 0x04, 0x54, 0x52, 0x55, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x4d, 0x50, 0x54, - 0x59, 0x10, 0x02, 0x2a, 0x83, 0x01, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, - 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x10, - 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x03, - 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x0f, 0x0a, - 0x0b, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x52, 0x55, 0x50, 0x54, 0x45, 0x44, 0x10, 0x07, 0x12, 0x0c, - 0x0a, 0x08, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, - 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x06, 0x2a, 0x85, 0x01, 0x0a, 0x14, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, - 0x61, 0x67, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, - 0x46, 0x55, 0x4c, 0x4c, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x53, 0x50, 0x41, 0x43, 0x45, 0x5f, 0x42, - 0x41, 0x43, 0x4b, 0x55, 0x50, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x49, 0x58, 0x45, 0x44, - 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x53, 0x10, 0x05, 0x12, 0x1b, 0x0a, 0x17, - 0x50, 0x45, 0x52, 0x53, 0x49, 0x53, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x4f, 0x4c, 0x55, 0x4d, - 0x45, 0x5f, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x10, 0x07, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x22, - 0x04, 0x08, 0x02, 0x10, 0x02, 0x22, 0x04, 0x08, 0x03, 0x10, 0x03, 0x22, 0x04, 0x08, 0x06, 0x10, - 0x06, 0x2a, 0x4b, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x47, 0x55, 0x4c, 0x41, 0x52, 0x10, 0x00, 0x12, - 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x45, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10, 0x01, 0x12, 0x09, 0x0a, - 0x05, 0x50, 0x52, 0x4f, 0x42, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4d, 0x41, 0x47, - 0x45, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10, 0x04, 0x22, 0x04, 0x08, 0x03, 0x10, 0x03, 0x32, 0xe5, - 0x06, 0x0a, 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x12, 0x4c, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x47, 0x65, 0x74, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x4f, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x70, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x70, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x57, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x58, 0x0a, 0x11, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x44, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x44, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x0f, 0x42, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x2e, - 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, - 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x57, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, - 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x17, 0x2e, 0x77, 0x73, - 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x30, 0x01, 0x12, 0x43, 0x0a, 0x0a, 0x4d, 0x61, 0x72, 0x6b, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, - 0x12, 0x18, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x61, 0x72, 0x6b, 0x41, 0x63, 0x74, - 0x69, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x77, 0x73, 0x6d, - 0x61, 0x6e, 0x2e, 0x4d, 0x61, 0x72, 0x6b, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x54, 0x69, - 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x65, - 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x19, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0b, - 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x19, 0x2e, 0x77, 0x73, - 0x6d, 0x61, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0c, 0x54, 0x61, 0x6b, 0x65, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x54, 0x61, 0x6b, - 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x54, 0x61, 0x6b, 0x65, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x55, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, - 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x77, 0x73, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2a, 0x34, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x70, + 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, + 0x0c, 0x0a, 0x08, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x0f, 0x0a, + 0x0b, 0x49, 0x4d, 0x4d, 0x45, 0x44, 0x49, 0x41, 0x54, 0x45, 0x4c, 0x59, 0x10, 0x01, 0x2a, 0x3a, + 0x0a, 0x0e, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x14, 0x0a, 0x10, 0x41, 0x44, 0x4d, 0x49, 0x54, 0x5f, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x5f, + 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x44, 0x4d, 0x49, 0x54, 0x5f, + 0x45, 0x56, 0x45, 0x52, 0x59, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x2a, 0x49, 0x0a, 0x0e, 0x50, 0x6f, + 0x72, 0x74, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x17, + 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x56, 0x49, 0x53, 0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, + 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x4f, 0x52, + 0x54, 0x5f, 0x56, 0x49, 0x53, 0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x50, 0x55, 0x42, + 0x4c, 0x49, 0x43, 0x10, 0x01, 0x2a, 0x38, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6f, 0x6c, 0x12, + 0x09, 0x0a, 0x05, 0x46, 0x41, 0x4c, 0x53, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x52, + 0x55, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x10, 0x02, 0x2a, + 0x83, 0x01, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x68, 0x61, + 0x73, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, + 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, + 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, + 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4e, 0x54, + 0x45, 0x52, 0x52, 0x55, 0x50, 0x54, 0x45, 0x44, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, + 0x4f, 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4f, 0x50, + 0x50, 0x45, 0x44, 0x10, 0x06, 0x2a, 0x85, 0x01, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x08, + 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x46, 0x55, 0x4c, 0x4c, + 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x53, 0x50, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x55, + 0x50, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x53, + 0x4f, 0x55, 0x52, 0x43, 0x45, 0x53, 0x10, 0x05, 0x12, 0x1b, 0x0a, 0x17, 0x50, 0x45, 0x52, 0x53, + 0x49, 0x53, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x4f, 0x4c, 0x55, 0x4d, 0x45, 0x5f, 0x43, 0x4c, + 0x41, 0x49, 0x4d, 0x10, 0x07, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x22, 0x04, 0x08, 0x02, 0x10, + 0x02, 0x22, 0x04, 0x08, 0x03, 0x10, 0x03, 0x22, 0x04, 0x08, 0x06, 0x10, 0x06, 0x2a, 0x4b, 0x0a, + 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, + 0x0a, 0x07, 0x52, 0x45, 0x47, 0x55, 0x4c, 0x41, 0x52, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, + 0x52, 0x45, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x52, 0x4f, + 0x42, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x42, 0x55, 0x49, + 0x4c, 0x44, 0x10, 0x04, 0x22, 0x04, 0x08, 0x03, 0x10, 0x03, 0x32, 0xe5, 0x06, 0x0a, 0x10, 0x57, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, + 0x4c, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, + 0x12, 0x1b, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, + 0x0e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x1c, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, + 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, + 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x70, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x1b, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x77, + 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x11, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x12, 0x1f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x62, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x0f, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x73, 0x6d, 0x61, + 0x6e, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, + 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x17, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x43, + 0x0a, 0x0a, 0x4d, 0x61, 0x72, 0x6b, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x18, 0x2e, 0x77, + 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x61, 0x72, 0x6b, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, + 0x61, 0x72, 0x6b, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, + 0x74, 0x12, 0x18, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x77, 0x73, + 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x19, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x49, 0x0a, 0x0c, 0x54, 0x61, 0x6b, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x12, 0x1a, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x54, 0x61, 0x6b, 0x65, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, + 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x54, 0x61, 0x6b, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x10, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x41, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x1e, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x41, + 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x77, 0x73, 0x6d, 0x61, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x41, + 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, + 0x64, 0x2f, 0x77, 0x73, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/components/ws-manager-api/typescript/src/core_pb.d.ts b/components/ws-manager-api/typescript/src/core_pb.d.ts index 8f448d83870822..99454d1c1b3637 100644 --- a/components/ws-manager-api/typescript/src/core_pb.d.ts +++ b/components/ws-manager-api/typescript/src/core_pb.d.ts @@ -14,7 +14,7 @@ import * as jspb from "google-protobuf"; import * as content_service_api_initializer_pb from "@gitpod/content-service/lib"; import * as google_protobuf_timestamp_pb from "google-protobuf/google/protobuf/timestamp_pb"; -export class MetadataFilter extends jspb.Message { +export class MetadataFilter extends jspb.Message { getOwner(): string; setOwner(value: string): MetadataFilter; getMetaId(): string; @@ -42,7 +42,7 @@ export namespace MetadataFilter { } } -export class GetWorkspacesRequest extends jspb.Message { +export class GetWorkspacesRequest extends jspb.Message { hasMustMatch(): boolean; clearMustMatch(): void; @@ -65,7 +65,7 @@ export namespace GetWorkspacesRequest { } } -export class GetWorkspacesResponse extends jspb.Message { +export class GetWorkspacesResponse extends jspb.Message { clearStatusList(): void; getStatusList(): Array; setStatusList(value: Array): GetWorkspacesResponse; @@ -87,7 +87,7 @@ export namespace GetWorkspacesResponse { } } -export class StartWorkspaceRequest extends jspb.Message { +export class StartWorkspaceRequest extends jspb.Message { getId(): string; setId(value: string): StartWorkspaceRequest; getServicePrefix(): string; @@ -125,7 +125,7 @@ export namespace StartWorkspaceRequest { } } -export class StartWorkspaceResponse extends jspb.Message { +export class StartWorkspaceResponse extends jspb.Message { getUrl(): string; setUrl(value: string): StartWorkspaceResponse; getOwnerToken(): string; @@ -148,7 +148,7 @@ export namespace StartWorkspaceResponse { } } -export class StopWorkspaceRequest extends jspb.Message { +export class StopWorkspaceRequest extends jspb.Message { getId(): string; setId(value: string): StopWorkspaceRequest; getPolicy(): StopWorkspacePolicy; @@ -171,7 +171,7 @@ export namespace StopWorkspaceRequest { } } -export class StopWorkspaceResponse extends jspb.Message { +export class StopWorkspaceResponse extends jspb.Message { serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): StopWorkspaceResponse.AsObject; @@ -188,7 +188,7 @@ export namespace StopWorkspaceResponse { } } -export class DescribeWorkspaceRequest extends jspb.Message { +export class DescribeWorkspaceRequest extends jspb.Message { getId(): string; setId(value: string): DescribeWorkspaceRequest; @@ -208,7 +208,7 @@ export namespace DescribeWorkspaceRequest { } } -export class DescribeWorkspaceResponse extends jspb.Message { +export class DescribeWorkspaceResponse extends jspb.Message { hasStatus(): boolean; clearStatus(): void; @@ -234,7 +234,7 @@ export namespace DescribeWorkspaceResponse { } } -export class SubscribeRequest extends jspb.Message { +export class SubscribeRequest extends jspb.Message { hasMustMatch(): boolean; clearMustMatch(): void; @@ -257,7 +257,7 @@ export namespace SubscribeRequest { } } -export class SubscribeResponse extends jspb.Message { +export class SubscribeResponse extends jspb.Message { hasStatus(): boolean; clearStatus(): void; @@ -285,7 +285,7 @@ export namespace SubscribeResponse { } } -export class MarkActiveRequest extends jspb.Message { +export class MarkActiveRequest extends jspb.Message { getId(): string; setId(value: string): MarkActiveRequest; getClosed(): boolean; @@ -308,7 +308,7 @@ export namespace MarkActiveRequest { } } -export class MarkActiveResponse extends jspb.Message { +export class MarkActiveResponse extends jspb.Message { serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MarkActiveResponse.AsObject; @@ -325,7 +325,7 @@ export namespace MarkActiveResponse { } } -export class SetTimeoutRequest extends jspb.Message { +export class SetTimeoutRequest extends jspb.Message { getId(): string; setId(value: string): SetTimeoutRequest; getDuration(): string; @@ -348,7 +348,7 @@ export namespace SetTimeoutRequest { } } -export class SetTimeoutResponse extends jspb.Message { +export class SetTimeoutResponse extends jspb.Message { serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SetTimeoutResponse.AsObject; @@ -365,7 +365,7 @@ export namespace SetTimeoutResponse { } } -export class ControlPortRequest extends jspb.Message { +export class ControlPortRequest extends jspb.Message { getId(): string; setId(value: string): ControlPortRequest; getExpose(): boolean; @@ -394,7 +394,7 @@ export namespace ControlPortRequest { } } -export class ControlPortResponse extends jspb.Message { +export class ControlPortResponse extends jspb.Message { serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ControlPortResponse.AsObject; @@ -411,7 +411,7 @@ export namespace ControlPortResponse { } } -export class TakeSnapshotRequest extends jspb.Message { +export class TakeSnapshotRequest extends jspb.Message { getId(): string; setId(value: string): TakeSnapshotRequest; getReturnImmediately(): boolean; @@ -434,7 +434,7 @@ export namespace TakeSnapshotRequest { } } -export class TakeSnapshotResponse extends jspb.Message { +export class TakeSnapshotResponse extends jspb.Message { getUrl(): string; setUrl(value: string): TakeSnapshotResponse; @@ -454,7 +454,7 @@ export namespace TakeSnapshotResponse { } } -export class ControlAdmissionRequest extends jspb.Message { +export class ControlAdmissionRequest extends jspb.Message { getId(): string; setId(value: string): ControlAdmissionRequest; getLevel(): AdmissionLevel; @@ -477,7 +477,7 @@ export namespace ControlAdmissionRequest { } } -export class ControlAdmissionResponse extends jspb.Message { +export class ControlAdmissionResponse extends jspb.Message { serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ControlAdmissionResponse.AsObject; @@ -494,7 +494,7 @@ export namespace ControlAdmissionResponse { } } -export class BackupWorkspaceRequest extends jspb.Message { +export class BackupWorkspaceRequest extends jspb.Message { getId(): string; setId(value: string): BackupWorkspaceRequest; @@ -514,7 +514,7 @@ export namespace BackupWorkspaceRequest { } } -export class BackupWorkspaceResponse extends jspb.Message { +export class BackupWorkspaceResponse extends jspb.Message { getUrl(): string; setUrl(value: string): BackupWorkspaceResponse; @@ -534,7 +534,7 @@ export namespace BackupWorkspaceResponse { } } -export class WorkspaceStatus extends jspb.Message { +export class WorkspaceStatus extends jspb.Message { getId(): string; setId(value: string): WorkspaceStatus; getStatusVersion(): number; @@ -599,7 +599,7 @@ export namespace WorkspaceStatus { } } -export class IDEImage extends jspb.Message { +export class IDEImage extends jspb.Message { getWebRef(): string; setWebRef(value: string): IDEImage; getDesktopRef(): string; @@ -625,7 +625,7 @@ export namespace IDEImage { } } -export class WorkspaceSpec extends jspb.Message { +export class WorkspaceSpec extends jspb.Message { getWorkspaceImage(): string; setWorkspaceImage(value: string): WorkspaceSpec; getDeprecatedIdeImage(): string; @@ -647,6 +647,8 @@ export class WorkspaceSpec extends jspb.Message { clearIdeImage(): void; getIdeImage(): IDEImage | undefined; setIdeImage(value?: IDEImage): WorkspaceSpec; + getClass(): string; + setClass(value: string): WorkspaceSpec; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): WorkspaceSpec.AsObject; @@ -668,10 +670,11 @@ export namespace WorkspaceSpec { type: WorkspaceType, timeout: string, ideImage?: IDEImage.AsObject, + pb_class: string, } } -export class PortSpec extends jspb.Message { +export class PortSpec extends jspb.Message { getPort(): number; setPort(value: number): PortSpec; getVisibility(): PortVisibility; @@ -697,7 +700,7 @@ export namespace PortSpec { } } -export class WorkspaceConditions extends jspb.Message { +export class WorkspaceConditions extends jspb.Message { getFailed(): string; setFailed(value: string): WorkspaceConditions; getTimeout(): string; @@ -747,7 +750,7 @@ export namespace WorkspaceConditions { } } -export class WorkspaceMetadata extends jspb.Message { +export class WorkspaceMetadata extends jspb.Message { getOwner(): string; setOwner(value: string): WorkspaceMetadata; getMetaId(): string; @@ -781,7 +784,7 @@ export namespace WorkspaceMetadata { } } -export class WorkspaceRuntimeInfo extends jspb.Message { +export class WorkspaceRuntimeInfo extends jspb.Message { getNodeName(): string; setNodeName(value: string): WorkspaceRuntimeInfo; getPodName(): string; @@ -807,7 +810,7 @@ export namespace WorkspaceRuntimeInfo { } } -export class WorkspaceAuthentication extends jspb.Message { +export class WorkspaceAuthentication extends jspb.Message { getAdmission(): AdmissionLevel; setAdmission(value: AdmissionLevel): WorkspaceAuthentication; getOwnerToken(): string; @@ -830,7 +833,7 @@ export namespace WorkspaceAuthentication { } } -export class StartWorkspaceSpec extends jspb.Message { +export class StartWorkspaceSpec extends jspb.Message { getWorkspaceImage(): string; setWorkspaceImage(value: string): StartWorkspaceSpec; getDeprecatedIdeImage(): string; @@ -868,6 +871,8 @@ export class StartWorkspaceSpec extends jspb.Message { clearIdeImage(): void; getIdeImage(): IDEImage | undefined; setIdeImage(value?: IDEImage): StartWorkspaceSpec; + getClass(): string; + setClass(value: string): StartWorkspaceSpec; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): StartWorkspaceSpec.AsObject; @@ -892,10 +897,11 @@ export namespace StartWorkspaceSpec { timeout: string, admission: AdmissionLevel, ideImage?: IDEImage.AsObject, + pb_class: string, } } -export class GitSpec extends jspb.Message { +export class GitSpec extends jspb.Message { getUsername(): string; setUsername(value: string): GitSpec; getEmail(): string; @@ -918,7 +924,7 @@ export namespace GitSpec { } } -export class EnvironmentVariable extends jspb.Message { +export class EnvironmentVariable extends jspb.Message { getName(): string; setName(value: string): EnvironmentVariable; getValue(): string; @@ -947,7 +953,7 @@ export namespace EnvironmentVariable { } - export class SecretKeyRef extends jspb.Message { + export class SecretKeyRef extends jspb.Message { getSecretName(): string; setSecretName(value: string): SecretKeyRef; getKey(): string; @@ -972,7 +978,7 @@ export namespace EnvironmentVariable { } -export class ExposedPorts extends jspb.Message { +export class ExposedPorts extends jspb.Message { clearPortsList(): void; getPortsList(): Array; setPortsList(value: Array): ExposedPorts; diff --git a/components/ws-manager-api/typescript/src/core_pb.js b/components/ws-manager-api/typescript/src/core_pb.js index 951f8c58c5a44f..29b855477a4c74 100644 --- a/components/ws-manager-api/typescript/src/core_pb.js +++ b/components/ws-manager-api/typescript/src/core_pb.js @@ -5095,7 +5095,8 @@ proto.wsman.WorkspaceSpec.toObject = function(includeInstance, msg) { proto.wsman.PortSpec.toObject, includeInstance), type: jspb.Message.getFieldWithDefault(msg, 6, 0), timeout: jspb.Message.getFieldWithDefault(msg, 7, ""), - ideImage: (f = msg.getIdeImage()) && proto.wsman.IDEImage.toObject(includeInstance, f) + ideImage: (f = msg.getIdeImage()) && proto.wsman.IDEImage.toObject(includeInstance, f), + pb_class: jspb.Message.getFieldWithDefault(msg, 9, "") }; if (includeInstance) { @@ -5166,6 +5167,10 @@ proto.wsman.WorkspaceSpec.deserializeBinaryFromReader = function(msg, reader) { reader.readMessage(value,proto.wsman.IDEImage.deserializeBinaryFromReader); msg.setIdeImage(value); break; + case 9: + var value = /** @type {string} */ (reader.readString()); + msg.setClass(value); + break; default: reader.skipField(); break; @@ -5253,6 +5258,13 @@ proto.wsman.WorkspaceSpec.serializeBinaryToWriter = function(message, writer) { proto.wsman.IDEImage.serializeBinaryToWriter ); } + f = message.getClass(); + if (f.length > 0) { + writer.writeString( + 9, + f + ); + } }; @@ -5439,6 +5451,24 @@ proto.wsman.WorkspaceSpec.prototype.hasIdeImage = function() { }; +/** + * optional string class = 9; + * @return {string} + */ +proto.wsman.WorkspaceSpec.prototype.getClass = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 9, "")); +}; + + +/** + * @param {string} value + * @return {!proto.wsman.WorkspaceSpec} returns this + */ +proto.wsman.WorkspaceSpec.prototype.setClass = function(value) { + return jspb.Message.setProto3StringField(this, 9, value); +}; + + @@ -6695,7 +6725,8 @@ proto.wsman.StartWorkspaceSpec.toObject = function(includeInstance, msg) { git: (f = msg.getGit()) && proto.wsman.GitSpec.toObject(includeInstance, f), timeout: jspb.Message.getFieldWithDefault(msg, 10, ""), admission: jspb.Message.getFieldWithDefault(msg, 11, 0), - ideImage: (f = msg.getIdeImage()) && proto.wsman.IDEImage.toObject(includeInstance, f) + ideImage: (f = msg.getIdeImage()) && proto.wsman.IDEImage.toObject(includeInstance, f), + pb_class: jspb.Message.getFieldWithDefault(msg, 13, "") }; if (includeInstance) { @@ -6783,6 +6814,10 @@ proto.wsman.StartWorkspaceSpec.deserializeBinaryFromReader = function(msg, reade reader.readMessage(value,proto.wsman.IDEImage.deserializeBinaryFromReader); msg.setIdeImage(value); break; + case 13: + var value = /** @type {string} */ (reader.readString()); + msg.setClass(value); + break; default: reader.skipField(); break; @@ -6894,6 +6929,13 @@ proto.wsman.StartWorkspaceSpec.serializeBinaryToWriter = function(message, write proto.wsman.IDEImage.serializeBinaryToWriter ); } + f = message.getClass(); + if (f.length > 0) { + writer.writeString( + 13, + f + ); + } }; @@ -7211,6 +7253,24 @@ proto.wsman.StartWorkspaceSpec.prototype.hasIdeImage = function() { }; +/** + * optional string class = 13; + * @return {string} + */ +proto.wsman.StartWorkspaceSpec.prototype.getClass = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 13, "")); +}; + + +/** + * @param {string} value + * @return {!proto.wsman.StartWorkspaceSpec} returns this + */ +proto.wsman.StartWorkspaceSpec.prototype.setClass = function(value) { + return jspb.Message.setProto3StringField(this, 13, value); +}; + + diff --git a/components/ws-manager/pkg/manager/create.go b/components/ws-manager/pkg/manager/create.go index 91a93f391e2028..b96bb74dfca597 100644 --- a/components/ws-manager/pkg/manager/create.go +++ b/components/ws-manager/pkg/manager/create.go @@ -42,20 +42,25 @@ var ( // createWorkspacePod creates the actual workspace pod based on the definite workspace pod and appropriate // templates. The result of this function is not expected to be modified prior to being passed to Kubernetes. func (m *Manager) createWorkspacePod(startContext *startWorkspaceContext) (*corev1.Pod, error) { - podTemplate, err := config.GetWorkspacePodTemplate(m.Config.WorkspacePodTemplate.DefaultPath) + var templates config.WorkspacePodTemplateConfiguration + if startContext.Class != nil { + templates = startContext.Class.Templates + } + + podTemplate, err := config.GetWorkspacePodTemplate(templates.DefaultPath) if err != nil { return nil, xerrors.Errorf("cannot read pod template - this is a configuration problem: %w", err) } var typeSpecificTpl *corev1.Pod switch startContext.Request.Type { case api.WorkspaceType_REGULAR: - typeSpecificTpl, err = config.GetWorkspacePodTemplate(m.Config.WorkspacePodTemplate.RegularPath) + typeSpecificTpl, err = config.GetWorkspacePodTemplate(templates.RegularPath) case api.WorkspaceType_PREBUILD: - typeSpecificTpl, err = config.GetWorkspacePodTemplate(m.Config.WorkspacePodTemplate.PrebuildPath) + typeSpecificTpl, err = config.GetWorkspacePodTemplate(templates.PrebuildPath) case api.WorkspaceType_PROBE: - typeSpecificTpl, err = config.GetWorkspacePodTemplate(m.Config.WorkspacePodTemplate.ProbePath) + typeSpecificTpl, err = config.GetWorkspacePodTemplate(templates.ProbePath) case api.WorkspaceType_IMAGEBUILD: - typeSpecificTpl, err = config.GetWorkspacePodTemplate(m.Config.WorkspacePodTemplate.ImagebuildPath) + typeSpecificTpl, err = config.GetWorkspacePodTemplate(templates.ImagebuildPath) } if err != nil { return nil, xerrors.Errorf("cannot read type-specific pod template - this is a configuration problem: %w", err) @@ -529,11 +534,16 @@ func removeVolume(pod *corev1.Pod, name string) { } func (m *Manager) createWorkspaceContainer(startContext *startWorkspaceContext) (*corev1.Container, error) { - limits, err := m.Config.Container.Workspace.Limits.ResourceList() + var containerConfig config.ContainerConfiguration + if startContext.Class != nil { + containerConfig = startContext.Class.Container + } + + limits, err := containerConfig.Limits.ResourceList() if err != nil { return nil, xerrors.Errorf("cannot parse workspace container limits: %w", err) } - requests, err := m.Config.Container.Workspace.Requests.ResourceList() + requests, err := containerConfig.Requests.ResourceList() if err != nil { return nil, xerrors.Errorf("cannot parse workspace container requests: %w", err) } @@ -669,7 +679,7 @@ func (m *Manager) createWorkspaceEnvironment(startContext *startWorkspaceContext heartbeatInterval := time.Duration(m.Config.HeartbeatInterval) result = append(result, corev1.EnvVar{Name: "GITPOD_INTERVAL", Value: fmt.Sprintf("%d", int64(heartbeatInterval/time.Millisecond))}) - res, err := m.Config.Container.Workspace.Requests.ResourceList() + res, err := startContext.ContainerConfiguration().Requests.ResourceList() if err != nil { return nil, xerrors.Errorf("cannot create environment: %w", err) } @@ -776,17 +786,30 @@ func (m *Manager) newStartWorkspaceContext(ctx context.Context, req *api.StartWo workspaceSpan := opentracing.StartSpan("workspace", opentracing.FollowsFrom(opentracing.SpanFromContext(ctx).Context())) traceID := tracing.GetTraceID(workspaceSpan) + labels := map[string]string{ + "app": "gitpod", + "component": "workspace", + wsk8s.WorkspaceIDLabel: req.Id, + wsk8s.OwnerLabel: req.Metadata.Owner, + wsk8s.MetaIDLabel: req.Metadata.MetaId, + wsk8s.TypeLabel: workspaceType, + headlessLabel: fmt.Sprintf("%v", headless), + markerLabel: "true", + } + + var class *config.WorkspaceClass + if cls, ok := m.Config.WorkspaceClasses[req.Spec.Class]; ok { + class = cls + if req.Spec.Class != "" { + labels[workspaceClassLabel] = req.Spec.Class + } + } else { + // TODO(cw): in the future we should fail the request here. Until we've migrated server, let's not be that strict + // return nil, status.Errorf(codes.InvalidArgument, "workspace class \"%s\" is unknown", req.Spec.Class) + } + return &startWorkspaceContext{ - Labels: map[string]string{ - "app": "gitpod", - "component": "workspace", - wsk8s.WorkspaceIDLabel: req.Id, - wsk8s.OwnerLabel: req.Metadata.Owner, - wsk8s.MetaIDLabel: req.Metadata.MetaId, - wsk8s.TypeLabel: workspaceType, - headlessLabel: fmt.Sprintf("%v", headless), - markerLabel: "true", - }, + Labels: labels, CLIAPIKey: cliAPIKey, OwnerToken: ownerToken, Request: req, @@ -795,6 +818,7 @@ func (m *Manager) newStartWorkspaceContext(ctx context.Context, req *api.StartWo WorkspaceURL: workspaceURL, TraceID: traceID, Headless: headless, + Class: class, }, nil } diff --git a/components/ws-manager/pkg/manager/create_test.go b/components/ws-manager/pkg/manager/create_test.go index f9511ef3a663e9..131b6e2cb02588 100644 --- a/components/ws-manager/pkg/manager/create_test.go +++ b/components/ws-manager/pkg/manager/create_test.go @@ -7,6 +7,8 @@ package manager import ( "context" "encoding/json" + "fmt" + "path/filepath" "testing" "testing/fstest" @@ -20,10 +22,7 @@ import ( ) func TestCreateDefiniteWorkspacePod(t *testing.T) { - type fixture struct { - Spec *json.RawMessage `json:"spec,omitempty"` // *api.StartWorkspaceSpec - Request *json.RawMessage `json:"request,omitempty"` // *api.StartWorkspaceRequest - Context *startWorkspaceContext `json:"context,omitempty"` + type WorkspaceClass struct { DefaultTemplate *corev1.Pod `json:"defaultTemplate,omitempty"` PrebuildTemplate *corev1.Pod `json:"prebuildTemplate,omitempty"` ProbeTemplate *corev1.Pod `json:"probeTemplate,omitempty"` @@ -31,7 +30,29 @@ func TestCreateDefiniteWorkspacePod(t *testing.T) { RegularTemplate *corev1.Pod `json:"regularTemplate,omitempty"` ResourceRequests *config.ResourceConfiguration `json:"resourceRequests,omitempty"` ResourceLimits *config.ResourceConfiguration `json:"resourceLimits,omitempty"` - CACertSecret string `json:"caCertSecret,omitempty"` + } + type tpl struct { + FN string + Content interface{} + Setter func(fn string) + } + toTpl := func(path string, cls WorkspaceClass, c *config.WorkspacePodTemplateConfiguration) []tpl { + return []tpl{ + {filepath.Join(path, "default-template.yaml"), cls.DefaultTemplate, func(fn string) { c.DefaultPath = fn }}, + {filepath.Join(path, "prebuild-template.yaml"), cls.PrebuildTemplate, func(fn string) { c.PrebuildPath = fn }}, + {filepath.Join(path, "probe-template.yaml"), cls.ProbeTemplate, func(fn string) { c.ProbePath = fn }}, + {filepath.Join(path, "imagebuild-template.yaml"), cls.ImagebuildTemplate, func(fn string) { c.ImagebuildPath = fn }}, + {filepath.Join(path, "regular-template.yaml"), cls.RegularTemplate, func(fn string) { c.RegularPath = fn }}, + } + } + type fixture struct { + WorkspaceClass + + Spec *json.RawMessage `json:"spec,omitempty"` // *api.StartWorkspaceSpec + Request *json.RawMessage `json:"request,omitempty"` // *api.StartWorkspaceRequest + Context *startWorkspaceContext `json:"context,omitempty"` + CACertSecret string `json:"caCertSecret,omitempty"` + Classes map[string]WorkspaceClass `json:"classes,omitempty"` EnforceAffinity bool `json:"enforceAffinity,omitempty"` } @@ -48,59 +69,57 @@ func TestCreateDefiniteWorkspacePod(t *testing.T) { mgmtCfg := forTestingOnlyManagerConfig() mgmtCfg.WorkspaceCACertSecret = fixture.CACertSecret - if fixture.ResourceRequests != nil { - var ( - cont = mgmtCfg.Container - ws = cont.Workspace - ) - ws.Requests = *fixture.ResourceRequests - cont.Workspace = ws - mgmtCfg.Container = cont + + if fixture.Classes == nil { + fixture.Classes = make(map[string]WorkspaceClass) } - if fixture.ResourceLimits != nil { - var ( - cont = mgmtCfg.Container - ws = cont.Workspace - ) - ws.Limits = *fixture.ResourceLimits - cont.Workspace = ws - mgmtCfg.Container = cont + var ( + files []tpl + classes = make(map[string]*config.WorkspaceClass) + ) + classes[""] = mgmtCfg.WorkspaceClasses[""] + fixture.Classes[""] = fixture.WorkspaceClass + if fixture.Classes[""].ResourceLimits == nil { + v := fixture.Classes[""] + v.ResourceLimits = mgmtCfg.WorkspaceClasses[""].Container.Limits + fixture.Classes[""] = v } + if fixture.Classes[""].ResourceRequests == nil { + v := fixture.Classes[""] + v.ResourceRequests = mgmtCfg.WorkspaceClasses[""].Container.Requests + fixture.Classes[""] = v + } + for n, cls := range fixture.Classes { + var cfgCls config.WorkspaceClass + cfgCls.Container.Requests = cls.ResourceRequests + cfgCls.Container.Limits = cls.ResourceLimits + + files = append(files, toTpl(n, cls, &cfgCls.Templates)...) + classes[n] = &cfgCls + } + mgmtCfg.WorkspaceClasses = classes manager := &Manager{Config: mgmtCfg} // create in-memory file system mapFS := fstest.MapFS{} - - config.FS = mapFS - - files := []struct { - tplfn string - ctnt interface{} - setter func(fn string) - }{ - {"default-template.yaml", fixture.DefaultTemplate, func(fn string) { manager.Config.WorkspacePodTemplate.DefaultPath = fn }}, - {"prebuild-template.yaml", fixture.PrebuildTemplate, func(fn string) { manager.Config.WorkspacePodTemplate.PrebuildPath = fn }}, - {"probe-template.yaml", fixture.ProbeTemplate, func(fn string) { manager.Config.WorkspacePodTemplate.ProbePath = fn }}, - {"imagebuild-template.yaml", fixture.ImagebuildTemplate, func(fn string) { manager.Config.WorkspacePodTemplate.ImagebuildPath = fn }}, - {"regular-template.yaml", fixture.RegularTemplate, func(fn string) { manager.Config.WorkspacePodTemplate.RegularPath = fn }}, - } for _, f := range files { - if f.ctnt == nil { + if f.Content == nil { continue } - b, err := yaml.Marshal(f.ctnt) + b, err := yaml.Marshal(f.Content) if err != nil { - t.Errorf("cannot re-marshal %s template: %v", f.tplfn, err) + t.Errorf("cannot re-marshal %s template: %v", f.FN, err) return nil } - mapFS[f.tplfn] = &fstest.MapFile{Data: b} + mapFS[f.FN] = &fstest.MapFile{Data: b} - f.setter(f.tplfn) + f.Setter(f.FN) } + config.FS = mapFS if fixture.Context == nil { var req api.StartWorkspaceRequest @@ -135,6 +154,10 @@ func TestCreateDefiniteWorkspacePod(t *testing.T) { } } + if req.Spec.Class == "" { + fmt.Println() + } + ctx, err := manager.newStartWorkspaceContext(context.Background(), &req) if err != nil { t.Errorf("cannot create startWorkspaceContext: %v", err) diff --git a/components/ws-manager/pkg/manager/integration_test.go b/components/ws-manager/pkg/manager/integration_test.go index baeb8cd696c869..6e4b2603ad95f4 100644 --- a/components/ws-manager/pkg/manager/integration_test.go +++ b/components/ws-manager/pkg/manager/integration_test.go @@ -70,15 +70,17 @@ func forIntegrationTestGetManager(t *testing.T) *Manager { WorkspaceURLTemplate: "{{ .ID }}-{{ .Prefix }}-{{ .Host }}", WorkspacePortURLTemplate: "{{ .Host }}:{{ .IngressPort }}", RegistryFacadeHost: "registry-facade:8080", - Container: config.AllContainerConfiguration{ - Workspace: config.ContainerConfiguration{ - Limits: config.ResourceConfiguration{ - CPU: "900m", - Memory: "1000M", - }, - Requests: config.ResourceConfiguration{ - CPU: "1m", - Memory: "1m", + WorkspaceClasses: map[string]*config.WorkspaceClass{ + "": { + Container: config.ContainerConfiguration{ + Limits: &config.ResourceConfiguration{ + CPU: "900m", + Memory: "1000M", + }, + Requests: &config.ResourceConfiguration{ + CPU: "1m", + Memory: "1m", + }, }, }, }, @@ -307,15 +309,30 @@ func (test *SingleWorkspaceIntegrationTest) Run(t *testing.T) { // create in-memory file system fs := fstest.MapFS{} + updateDefaultTemplate := func(update func(*config.WorkspacePodTemplateConfiguration)) { + c := manager.Config.WorkspaceClasses[""] + tpls := c.Templates + update(&tpls) + c.Templates = tpls + manager.Config.WorkspaceClasses[""] = c + } files := []struct { tplfn string ctnt interface{} setter func(fn string) }{ - {"default-template.yaml", test.PodTemplates.Default, func(fn string) { manager.Config.WorkspacePodTemplate.DefaultPath = fn }}, - {"prebuild-template.yaml", test.PodTemplates.Prebuild, func(fn string) { manager.Config.WorkspacePodTemplate.PrebuildPath = fn }}, - {"probe-template.yaml", test.PodTemplates.Probe, func(fn string) { manager.Config.WorkspacePodTemplate.ProbePath = fn }}, - {"regular-template.yaml", test.PodTemplates.Regular, func(fn string) { manager.Config.WorkspacePodTemplate.RegularPath = fn }}, + {"default-template.yaml", test.PodTemplates.Default, func(fn string) { + updateDefaultTemplate(func(wptc *config.WorkspacePodTemplateConfiguration) { wptc.DefaultPath = fn }) + }}, + {"prebuild-template.yaml", test.PodTemplates.Prebuild, func(fn string) { + updateDefaultTemplate(func(wptc *config.WorkspacePodTemplateConfiguration) { wptc.PrebuildPath = fn }) + }}, + {"probe-template.yaml", test.PodTemplates.Probe, func(fn string) { + updateDefaultTemplate(func(wptc *config.WorkspacePodTemplateConfiguration) { wptc.ProbePath = fn }) + }}, + {"regular-template.yaml", test.PodTemplates.Regular, func(fn string) { + updateDefaultTemplate(func(wptc *config.WorkspacePodTemplateConfiguration) { wptc.RegularPath = fn }) + }}, } for _, f := range files { if f.ctnt == nil { diff --git a/components/ws-manager/pkg/manager/manager.go b/components/ws-manager/pkg/manager/manager.go index 4e3d8fb5abe90e..9429ae235a9571 100644 --- a/components/ws-manager/pkg/manager/manager.go +++ b/components/ws-manager/pkg/manager/manager.go @@ -77,6 +77,15 @@ type startWorkspaceContext struct { WorkspaceURL string `json:"workspaceURL"` TraceID string `json:"traceID"` Headless bool `json:"headless"` + Class *config.WorkspaceClass `json:"class"` +} + +func (swctx *startWorkspaceContext) ContainerConfiguration() config.ContainerConfiguration { + var res config.ContainerConfiguration + if swctx.Class != nil { + res = swctx.Class.Container + } + return res } const ( @@ -88,6 +97,8 @@ const ( markerLabel = "gpwsman" // headlessLabel marks a workspace as headless headlessLabel = "headless" + // workspaceClassLabel denotes the class of a workspace + workspaceClassLabel = "gitpod.io/workspaceClass" ) const ( @@ -182,7 +193,7 @@ func (m *Manager) StartWorkspace(ctx context.Context, req *api.StartWorkspaceReq span.LogKV("event", "workspace does not exist") err = validateStartWorkspaceRequest(req) if err != nil { - return nil, xerrors.Errorf("cannot start workspace: %w", err) + return nil, status.Errorf(codes.InvalidArgument, "invalid start workspace request: %v", err) } span.LogKV("event", "validated workspace start request") // create the objects required to start the workspace pod/service diff --git a/components/ws-manager/pkg/manager/status.go b/components/ws-manager/pkg/manager/status.go index ded8b39ae78565..c6c478881d98cc 100644 --- a/components/ws-manager/pkg/manager/status.go +++ b/components/ws-manager/pkg/manager/status.go @@ -246,6 +246,7 @@ func (m *Manager) getWorkspaceStatus(wso workspaceObjects) (*api.WorkspaceStatus Url: wsurl, Type: tpe, Timeout: timeout, + Class: wso.Pod.Labels[workspaceClassLabel], }, Conditions: &api.WorkspaceConditions{ Snapshot: wso.Pod.Annotations[workspaceSnapshotAnnotation], diff --git a/components/ws-manager/pkg/manager/testdata/cdwp_class.golden b/components/ws-manager/pkg/manager/testdata/cdwp_class.golden new file mode 100644 index 00000000000000..75ba46a50a9b6c --- /dev/null +++ b/components/ws-manager/pkg/manager/testdata/cdwp_class.golden @@ -0,0 +1,269 @@ +{ + "reason": { + "metadata": { + "name": "ws-test", + "namespace": "default", + "creationTimestamp": null, + "labels": { + "app": "gitpod", + "component": "workspace", + "gitpod.io/networkpolicy": "default", + "gitpod.io/workspaceClass": "foobar", + "gpwsman": "true", + "headless": "false", + "metaID": "foobar", + "owner": "tester", + "workspaceID": "test", + "workspaceType": "regular" + }, + "annotations": { + "cluster-autoscaler.kubernetes.io/safe-to-evict": "false", + "container.apparmor.security.beta.kubernetes.io/workspace": "unconfined", + "gitpod/admission": "admit_owner_only", + "gitpod/contentInitializer": "GmcKZXdvcmtzcGFjZXMvY3J5cHRpYy1pZC1nb2VzLWhlcmcvZmQ2MjgwNGItNGNhYi0xMWU5LTg0M2EtNGU2NDUzNzMwNDhlLnRhckBnaXRwb2QtZGV2LXVzZXItY2hyaXN0ZXN0aW5n", + "gitpod/id": "test", + "gitpod/imageSpec": "CrwBZXUuZ2NyLmlvL2dpdHBvZC1kZXYvd29ya3NwYWNlLWltYWdlcy9hYzFjMDc1NTAwNzk2NmU0ZDZlMDkwZWE4MjE3MjlhYzc0N2QyMmFjL2V1Lmdjci5pby9naXRwb2QtZGV2L3dvcmtzcGFjZS1iYXNlLWltYWdlcy9naXRodWIuY29tL3R5cGVmb3gvZ2l0cG9kOjgwYTdkNDI3YTFmY2QzNDZkNDIwNjAzZDgwYTMxZDU3Y2Y3NWE3YWYSNGV1Lmdjci5pby9naXRwb2QtY29yZS1kZXYvYnVpZC90aGVpYS1pZGU6c29tZXZlcnNpb24=", + "gitpod/never-ready": "true", + "gitpod/ownerToken": "%7J'[Of/8NDiWE+9F,I6^Jcj_1\u0026}-F8p", + "gitpod/servicePrefix": "foobarservice", + "gitpod/traceid": "", + "gitpod/url": "test-foobarservice-gitpod.io", + "prometheus.io/path": "/metrics", + "prometheus.io/port": "23000", + "prometheus.io/scrape": "true", + "seccomp.security.alpha.kubernetes.io/pod": "localhost/workspace-default" + }, + "finalizers": [ + "gitpod.io/finalizer" + ] + }, + "spec": { + "volumes": [ + { + "name": "vol-this-workspace", + "hostPath": { + "path": "/tmp/workspaces/test", + "type": "DirectoryOrCreate" + } + }, + { + "name": "daemon-mount", + "hostPath": { + "path": "/tmp/workspaces/test-daemon", + "type": "DirectoryOrCreate" + } + } + ], + "containers": [ + { + "name": "workspace", + "image": "registry-facade:8080/remote/test", + "command": [ + "/.supervisor/workspacekit", + "ring0" + ], + "ports": [ + { + "containerPort": 23000 + } + ], + "env": [ + { + "name": "GITPOD_REPO_ROOT", + "value": "/workspace" + }, + { + "name": "GITPOD_CLI_APITOKEN", + "value": "Ab=5=rRA*9:C'T{;RRB\u003e]vK2p6`fFfrS" + }, + { + "name": "GITPOD_WORKSPACE_ID", + "value": "foobar" + }, + { + "name": "GITPOD_INSTANCE_ID", + "value": "test" + }, + { + "name": "GITPOD_THEIA_PORT", + "value": "23000" + }, + { + "name": "THEIA_WORKSPACE_ROOT", + "value": "/workspace" + }, + { + "name": "GITPOD_HOST", + "value": "gitpod.io" + }, + { + "name": "GITPOD_WORKSPACE_URL", + "value": "test-foobarservice-gitpod.io" + }, + { + "name": "THEIA_SUPERVISOR_ENDPOINT", + "value": ":22999" + }, + { + "name": "THEIA_WEBVIEW_EXTERNAL_ENDPOINT", + "value": "webview-{{hostname}}" + }, + { + "name": "THEIA_MINI_BROWSER_HOST_PATTERN", + "value": "browser-{{hostname}}" + }, + { + "name": "GITPOD_GIT_USER_NAME", + "value": "usernameGoesHere" + }, + { + "name": "GITPOD_GIT_USER_EMAIL", + "value": "some@user.com" + }, + { + "name": "foo", + "value": "bar" + }, + { + "name": "GITPOD_INTERVAL", + "value": "30000" + }, + { + "name": "GITPOD_MEMORY", + "value": "0" + }, + { + "name": "some-envvar", + "value": "foofoo" + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "vol-this-workspace", + "mountPath": "/workspace", + "mountPropagation": "HostToContainer" + }, + { + "name": "daemon-mount", + "mountPath": "/.workspace", + "mountPropagation": "HostToContainer" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/_supervisor/v1/status/content/wait/true", + "port": 22999, + "scheme": "HTTP" + }, + "initialDelaySeconds": 3, + "timeoutSeconds": 1, + "periodSeconds": 1, + "successThreshold": 1, + "failureThreshold": 600 + }, + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent", + "securityContext": { + "capabilities": { + "add": [ + "AUDIT_WRITE", + "FSETID", + "KILL", + "NET_BIND_SERVICE", + "SYS_PTRACE" + ], + "drop": [ + "SETPCAP", + "CHOWN", + "NET_RAW", + "DAC_OVERRIDE", + "FOWNER", + "SYS_CHROOT", + "SETFCAP", + "SETUID", + "SETGID" + ] + }, + "privileged": false, + "runAsUser": 33333, + "runAsGroup": 33333, + "runAsNonRoot": true, + "readOnlyRootFilesystem": false, + "allowPrivilegeEscalation": true + } + } + ], + "restartPolicy": "Never", + "dnsPolicy": "None", + "serviceAccountName": "workspace", + "automountServiceAccountToken": false, + "imagePullSecrets": [ + { + "name": "dockerhub-typefox" + }, + { + "name": "eu.gcr.io-gitpod" + } + ], + "hostname": "foobar", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "gitpod.io/workload_workspace_regular", + "operator": "Exists" + }, + { + "key": "gitpod.io/ws-daemon_ready_ns_default", + "operator": "Exists" + }, + { + "key": "gitpod.io/registry-facade_ready_ns_default", + "operator": "Exists" + }, + { + "key": "gitpod.io/workload_workspace_regular", + "operator": "In", + "values": [ + "true" + ] + } + ] + } + ] + } + } + }, + "tolerations": [ + { + "key": "node.kubernetes.io/disk-pressure", + "operator": "Exists", + "effect": "NoExecute" + }, + { + "key": "node.kubernetes.io/memory-pressure", + "operator": "Exists", + "effect": "NoExecute" + }, + { + "key": "node.kubernetes.io/network-unavailable", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 30 + } + ], + "dnsConfig": { + "nameservers": [ + "1.1.1.1", + "8.8.8.8" + ] + }, + "enableServiceLinks": false + }, + "status": {} + } +} \ No newline at end of file diff --git a/components/ws-manager/pkg/manager/testdata/cdwp_class.json b/components/ws-manager/pkg/manager/testdata/cdwp_class.json new file mode 100644 index 00000000000000..3569f982309d66 --- /dev/null +++ b/components/ws-manager/pkg/manager/testdata/cdwp_class.json @@ -0,0 +1,79 @@ +{ + "classes": { + "foobar": { + "defaultTemplate": { + "spec": { + "dnsConfig": { + "nameservers": [ + "1.1.1.1", + "8.8.8.8" + ] + }, + "dnsPolicy": "None", + "imagePullSecrets": [ + { + "name": "dockerhub-typefox" + }, + { + "name": "eu.gcr.io-gitpod" + } + ], + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "gitpod.io/workload_workspace_regular", + "operator": "In", + "values": [ + "true" + ] + } + ] + } + ] + } + } + }, + "containers": [ + { + "name": "workspace", + "env": [ + { "name": "some-envvar", "value": "foofoo" } + ] + } + ] + } + } + } + }, + "spec": { + "ideImage": { + "webRef": "eu.gcr.io/gitpod-core-dev/buid/theia-ide:someversion" + }, + "workspaceImage": "eu.gcr.io/gitpod-dev/workspace-images/ac1c0755007966e4d6e090ea821729ac747d22ac/eu.gcr.io/gitpod-dev/workspace-base-images/github.com/typefox/gitpod:80a7d427a1fcd346d420603d80a31d57cf75a7af", + "initializer": { + "snapshot": { + "snapshot": "workspaces/cryptic-id-goes-herg/fd62804b-4cab-11e9-843a-4e645373048e.tar@gitpod-dev-user-christesting" + } + }, + "ports": [ + { + "port": 8080 + } + ], + "envvars": [ + { + "name": "foo", + "value": "bar" + } + ], + "git": { + "username": "usernameGoesHere", + "email": "some@user.com" + }, + "class": "foobar" + } +} \ No newline at end of file diff --git a/components/ws-manager/pkg/manager/testdata/status_firstUserActivity_RUNNING.golden b/components/ws-manager/pkg/manager/testdata/status_firstUserActivity_RUNNING.golden index d963b3257062e6..019abb33cac7fe 100644 --- a/components/ws-manager/pkg/manager/testdata/status_firstUserActivity_RUNNING.golden +++ b/components/ws-manager/pkg/manager/testdata/status_firstUserActivity_RUNNING.golden @@ -59,7 +59,8 @@ } ], "timeout": "60m", - "ide_image": {} + "ide_image": {}, + "class": "some-class" }, "phase": 4, "conditions": { diff --git a/components/ws-manager/pkg/manager/testdata/status_firstUserActivity_RUNNING.json b/components/ws-manager/pkg/manager/testdata/status_firstUserActivity_RUNNING.json index 46fc0cf2190c58..48e54e7ed55438 100644 --- a/components/ws-manager/pkg/manager/testdata/status_firstUserActivity_RUNNING.json +++ b/components/ws-manager/pkg/manager/testdata/status_firstUserActivity_RUNNING.json @@ -16,7 +16,8 @@ "metaID": "c372bd58-ef61-4fc0-9083-bd61ef96ad9f", "owner": "ec566d71-62a8-492e-8040-51850d9a97c4", "workspaceID": "df376c57-7a0e-4233-976a-7a021e6f088c", - "workspaceType": "regular" + "workspaceType": "regular", + "gitpod.io/workspaceClass": "some-class" }, "annotations": { "cni.projectcalico.org/podIP": "10.4.5.45/32", diff --git a/components/ws-manager/pkg/manager/testing_test.go b/components/ws-manager/pkg/manager/testing_test.go index d2248934a976ec..f4bbcbe5863cf9 100644 --- a/components/ws-manager/pkg/manager/testing_test.go +++ b/components/ws-manager/pkg/manager/testing_test.go @@ -40,17 +40,18 @@ func forTestingOnlyManagerConfig() config.Configuration { WorkspaceURLTemplate: "{{ .ID }}-{{ .Prefix }}-{{ .Host }}", WorkspacePortURLTemplate: "{{ .WorkspacePort }}-{{ .ID }}-{{ .Prefix }}-{{ .Host }}", RegistryFacadeHost: "registry-facade:8080", - Container: config.AllContainerConfiguration{ - Workspace: config.ContainerConfiguration{ - Image: "workspace-image", - Limits: config.ResourceConfiguration{ - CPU: "900m", - Memory: "1000M", - }, - Requests: config.ResourceConfiguration{ - CPU: "899m", - EphemeralStorage: "5Gi", - Memory: "999M", + WorkspaceClasses: map[string]*config.WorkspaceClass{ + "": { + Container: config.ContainerConfiguration{ + Limits: &config.ResourceConfiguration{ + CPU: "900m", + Memory: "1000M", + }, + Requests: &config.ResourceConfiguration{ + CPU: "899m", + EphemeralStorage: "5Gi", + Memory: "999M", + }, }, }, }, From 5fa856012e1f42ff82175c96af1986c339a6a9ec Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Mon, 11 Apr 2022 22:10:25 +0000 Subject: [PATCH 2/3] [installer] Add support for workspace classes --- components/ws-manager-api/go/config/config.go | 2 +- .../pkg/manager/testdata/cdwp_class.golden | 5 ++ .../pkg/components/ws-manager/configmap.go | 86 ++++++++++++++----- .../components/ws-manager/configmap_test.go | 3 +- install/installer/pkg/config/v1/config.go | 2 - .../config/v1/experimental/experimental.go | 21 ++++- 6 files changed, 91 insertions(+), 28 deletions(-) diff --git a/components/ws-manager-api/go/config/config.go b/components/ws-manager-api/go/config/config.go index f81787a7d2842f..c9750abd81d734 100644 --- a/components/ws-manager-api/go/config/config.go +++ b/components/ws-manager-api/go/config/config.go @@ -273,7 +273,7 @@ func (c *ContainerConfiguration) Validate() error { } var validResourceConfig = validation.By(func(o interface{}) error { - rc, ok := o.(ResourceConfiguration) + rc, ok := o.(*ResourceConfiguration) if !ok { return xerrors.Errorf("can only validate ResourceConfiguration") } diff --git a/components/ws-manager/pkg/manager/testdata/cdwp_class.golden b/components/ws-manager/pkg/manager/testdata/cdwp_class.golden index 75ba46a50a9b6c..e36345f886b706 100644 --- a/components/ws-manager/pkg/manager/testdata/cdwp_class.golden +++ b/components/ws-manager/pkg/manager/testdata/cdwp_class.golden @@ -19,6 +19,7 @@ "annotations": { "cluster-autoscaler.kubernetes.io/safe-to-evict": "false", "container.apparmor.security.beta.kubernetes.io/workspace": "unconfined", + "gitpod.io/attemptingToCreate": "true", "gitpod/admission": "admit_owner_only", "gitpod/contentInitializer": "GmcKZXdvcmtzcGFjZXMvY3J5cHRpYy1pZC1nb2VzLWhlcmcvZmQ2MjgwNGItNGNhYi0xMWU5LTg0M2EtNGU2NDUzNzMwNDhlLnRhckBnaXRwb2QtZGV2LXVzZXItY2hyaXN0ZXN0aW5n", "gitpod/id": "test", @@ -76,6 +77,10 @@ "name": "GITPOD_CLI_APITOKEN", "value": "Ab=5=rRA*9:C'T{;RRB\u003e]vK2p6`fFfrS" }, + { + "name": "GITPOD_OWNER_ID", + "value": "tester" + }, { "name": "GITPOD_WORKSPACE_ID", "value": "foobar" diff --git a/install/installer/pkg/components/ws-manager/configmap.go b/install/installer/pkg/components/ws-manager/configmap.go index cef13800fded8c..d8b2bdefd6c3f1 100644 --- a/install/installer/pkg/components/ws-manager/configmap.go +++ b/install/installer/pkg/components/ws-manager/configmap.go @@ -17,6 +17,7 @@ import ( storageconfig "github.com/gitpod-io/gitpod/content-service/api/config" "github.com/gitpod-io/gitpod/installer/pkg/common" configv1 "github.com/gitpod-io/gitpod/installer/pkg/config/v1" + "github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental" "github.com/gitpod-io/gitpod/ws-manager/api/config" corev1 "k8s.io/api/core/v1" @@ -25,7 +26,11 @@ import ( ) func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { - templatesCfg, tpls, err := buildWorkspaceTemplates(ctx) + cfgTpls := ctx.Config.Workspace.Templates + if cfgTpls == nil { + cfgTpls = &configv1.WorkspaceTemplates{} + } + templatesCfg, tpls, err := buildWorkspaceTemplates(ctx, cfgTpls, "") if err != nil { return nil, err } @@ -48,6 +53,60 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { customCASecret = ctx.Config.CustomCACert.Name } + classes := map[string]*config.WorkspaceClass{ + "": { + Container: config.ContainerConfiguration{ + Requests: &config.ResourceConfiguration{ + CPU: quantityString(ctx.Config.Workspace.Resources.Requests, corev1.ResourceCPU), + Memory: quantityString(ctx.Config.Workspace.Resources.Requests, corev1.ResourceMemory), + EphemeralStorage: quantityString(ctx.Config.Workspace.Resources.Requests, corev1.ResourceEphemeralStorage), + }, + Limits: &config.ResourceConfiguration{ + CPU: quantityString(ctx.Config.Workspace.Resources.Limits, corev1.ResourceCPU), + Memory: quantityString(ctx.Config.Workspace.Resources.Limits, corev1.ResourceMemory), + EphemeralStorage: quantityString(ctx.Config.Workspace.Resources.Limits, corev1.ResourceEphemeralStorage), + }, + }, + Templates: templatesCfg, + }, + } + err = ctx.WithExperimental(func(ucfg *experimental.Config) error { + if ucfg.Workspace == nil { + return nil + } + for k, c := range ucfg.Workspace.WorkspaceClasses { + tplsCfg, ctpls, err := buildWorkspaceTemplates(ctx, &configv1.WorkspaceTemplates{ + Default: c.Templates.Default, + Prebuild: c.Templates.Prebuild, + ImageBuild: c.Templates.ImageBuild, + Regular: c.Templates.Regular, + }, k) + if err != nil { + return err + } + classes[k] = &config.WorkspaceClass{ + Container: config.ContainerConfiguration{ + Requests: &config.ResourceConfiguration{ + CPU: quantityString(c.Resources.Requests, corev1.ResourceCPU), + Memory: quantityString(c.Resources.Requests, corev1.ResourceMemory), + EphemeralStorage: quantityString(c.Resources.Requests, corev1.ResourceEphemeralStorage), + }, + Limits: &config.ResourceConfiguration{ + CPU: quantityString(c.Resources.Limits, corev1.ResourceCPU), + Memory: quantityString(c.Resources.Limits, corev1.ResourceMemory), + EphemeralStorage: quantityString(c.Resources.Limits, corev1.ResourceEphemeralStorage), + }, + }, + Templates: tplsCfg, + } + tpls = append(tpls, ctpls...) + } + return nil + }) + if err != nil { + return nil, err + } + wsmcfg := config.ServiceConfiguration{ Manager: config.Configuration{ Namespace: ctx.Namespace, @@ -65,21 +124,7 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { PrivateKey: "/ws-daemon-tls-certs/tls.key", }, }, - Container: config.AllContainerConfiguration{ - Workspace: config.ContainerConfiguration{ - Requests: config.ResourceConfiguration{ - CPU: quantityString(ctx.Config.Workspace.Resources.Requests, corev1.ResourceCPU), - Memory: quantityString(ctx.Config.Workspace.Resources.Requests, corev1.ResourceMemory), - EphemeralStorage: quantityString(ctx.Config.Workspace.Resources.Requests, corev1.ResourceEphemeralStorage), - }, - Limits: config.ResourceConfiguration{ - CPU: quantityString(ctx.Config.Workspace.Resources.Limits, corev1.ResourceCPU), - Memory: quantityString(ctx.Config.Workspace.Resources.Limits, corev1.ResourceMemory), - EphemeralStorage: quantityString(ctx.Config.Workspace.Resources.Requests, corev1.ResourceEphemeralStorage), - }, - Image: "OVERWRITTEN-IN-REQUEST", - }, - }, + WorkspaceClasses: classes, HeartbeatInterval: util.Duration(30 * time.Second), GitpodHostURL: "https://" + ctx.Config.Domain, WorkspaceClusterHost: fmt.Sprintf("ws.%s", ctx.Config.Domain), @@ -89,7 +134,6 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { WorkspaceURLTemplate: fmt.Sprintf("https://{{ .Prefix }}.ws.%s", ctx.Config.Domain), WorkspacePortURLTemplate: fmt.Sprintf("https://{{ .WorkspacePort }}-{{ .Prefix }}.ws.%s", ctx.Config.Domain), WorkspaceHostPath: wsdaemon.HostWorkingArea, - WorkspacePodTemplate: templatesCfg, Timeouts: config.WorkspaceTimeoutConfiguration{ AfterClose: timeoutAfterClose, HeadlessWorkspace: util.Duration(1 * time.Hour), @@ -165,14 +209,13 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { return res, nil } -func buildWorkspaceTemplates(ctx *common.RenderContext) (config.WorkspacePodTemplateConfiguration, []runtime.Object, error) { +func buildWorkspaceTemplates(ctx *common.RenderContext, cfgTpls *configv1.WorkspaceTemplates, className string) (config.WorkspacePodTemplateConfiguration, []runtime.Object, error) { var ( cfg config.WorkspacePodTemplateConfiguration tpls = make(map[string]string) ) - cfgTpls := ctx.Config.Workspace.Templates if cfgTpls == nil { - cfgTpls = &configv1.WorkspaceTemplates{} + cfgTpls = new(configv1.WorkspaceTemplates) } ops := []struct { @@ -184,7 +227,6 @@ func buildWorkspaceTemplates(ctx *common.RenderContext) (config.WorkspacePodTemp {Name: "imagebuild", Path: &cfg.ImagebuildPath, Tpl: cfgTpls.ImageBuild}, {Name: "prebuild", Path: &cfg.PrebuildPath, Tpl: cfgTpls.Prebuild}, {Name: "regular", Path: &cfg.RegularPath, Tpl: cfgTpls.Regular}, - {Name: "probe", Path: &cfg.ProbePath, Tpl: cfgTpls.Probe}, } for _, op := range ops { if op.Tpl == nil { @@ -194,7 +236,7 @@ func buildWorkspaceTemplates(ctx *common.RenderContext) (config.WorkspacePodTemp if err != nil { return cfg, nil, fmt.Errorf("unable to marshal %s workspace template: %w", op.Name, err) } - fn := op.Name + ".yaml" + fn := filepath.Join(className, op.Name+".yaml") *op.Path = filepath.Join(WorkspaceTemplatePath, fn) tpls[fn] = string(fc) } diff --git a/install/installer/pkg/components/ws-manager/configmap_test.go b/install/installer/pkg/components/ws-manager/configmap_test.go index f1007163de3c19..eb0c6e59599ba5 100644 --- a/install/installer/pkg/components/ws-manager/configmap_test.go +++ b/install/installer/pkg/components/ws-manager/configmap_test.go @@ -105,8 +105,7 @@ func TestBuildWorkspaceTemplates(t *testing.T) { act.TplConfig, objs, err = buildWorkspaceTemplates(&common.RenderContext{Config: configv1.Config{ ContainerRegistry: *test.ContainerRegistry, - Workspace: configv1.Workspace{Templates: test.Config}, - }}) + }}, test.Config, "") if err != nil { t.Error(err) } diff --git a/install/installer/pkg/config/v1/config.go b/install/installer/pkg/config/v1/config.go index 7c08e516d48db9..a8c2dc3187f713 100644 --- a/install/installer/pkg/config/v1/config.go +++ b/install/installer/pkg/config/v1/config.go @@ -234,10 +234,8 @@ type WorkspaceRuntime struct { type WorkspaceTemplates struct { Default *corev1.Pod `json:"default"` Prebuild *corev1.Pod `json:"prebuild"` - Ghost *corev1.Pod `json:"ghost"` ImageBuild *corev1.Pod `json:"imagebuild"` Regular *corev1.Pod `json:"regular"` - Probe *corev1.Pod `json:"probe"` } type Workspace struct { diff --git a/install/installer/pkg/config/v1/experimental/experimental.go b/install/installer/pkg/config/v1/experimental/experimental.go index a515371804f97a..8d9af1011f54aa 100644 --- a/install/installer/pkg/config/v1/experimental/experimental.go +++ b/install/installer/pkg/config/v1/experimental/experimental.go @@ -10,7 +10,10 @@ // If you use any setting herein, you forfeit support from Gitpod. package experimental -import "k8s.io/apimachinery/pkg/api/resource" +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) // Config contains all experimental configuration. type Config struct { @@ -58,6 +61,22 @@ type WorkspaceConfig struct { PasswordSecret string `json:"passwordSecret"` } `json:"redisCache"` } `json:"registryFacade"` + + WorkspaceClasses map[string]WorkspaceClass `json:"classes,omitempty"` +} + +type WorkspaceClass struct { + Resources struct { + Requests corev1.ResourceList `json:"requests" validate:"required"` + Limits corev1.ResourceList `json:"limits,omitempty"` + } `json:"resources" validate:"required"` + Templates WorkspaceTemplates `json:"templates,omitempty"` +} +type WorkspaceTemplates struct { + Default *corev1.Pod `json:"default"` + Prebuild *corev1.Pod `json:"prebuild"` + ImageBuild *corev1.Pod `json:"imagebuild"` + Regular *corev1.Pod `json:"regular"` } type WebAppConfig struct { From 7ff493376793e1376df5306e76ce91d32081cf65 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Thu, 28 Apr 2022 17:33:39 +0000 Subject: [PATCH 3/3] [ws-daemon] Support storage quota per class --- .werft/jobs/build/installer/post-process.sh | 2 -- components/ws-daemon-api/daemon.proto | 3 ++ components/ws-daemon-api/go/daemon.pb.go | 14 +++++++- .../typescript/src/daemon_pb.d.ts | 25 ++++++++------- .../ws-daemon-api/typescript/src/daemon_pb.js | 32 ++++++++++++++++++- components/ws-daemon/pkg/content/config.go | 4 --- components/ws-daemon/pkg/content/hooks.go | 7 ++-- components/ws-daemon/pkg/content/service.go | 1 + .../pkg/internal/session/workspace.go | 1 + components/ws-manager-api/go/config/config.go | 15 +++++++++ components/ws-manager/pkg/manager/monitor.go | 10 ++++++ 11 files changed, 92 insertions(+), 22 deletions(-) diff --git a/.werft/jobs/build/installer/post-process.sh b/.werft/jobs/build/installer/post-process.sh index 47954cdbe672e8..db8bfd24fb8987 100755 --- a/.werft/jobs/build/installer/post-process.sh +++ b/.werft/jobs/build/installer/post-process.sh @@ -183,8 +183,6 @@ while [ "$documentIndex" -le "$DOCS" ]; do | jq --arg REGISTRY_FACADE_HOST "$REGISTRY_FACADE_HOST" '.manager.registryFacadeHost = $REGISTRY_FACADE_HOST' \ | jq ".manager.wsdaemon.port = $WS_DAEMON_PORT" > /tmp/"$NAME"-cm-overrides.json - yq w -i -j /tmp/"$NAME"-cm-overrides.json manager.podTemplate.defaultPath /workspace-templates/default.yaml - touch /tmp/"$NAME"-cm-overrides.yaml # write a yaml file with the json as a multiline string yq w -i /tmp/"$NAME"-cm-overrides.yaml "data.[config.json]" -- "$(< /tmp/"$NAME"-cm-overrides.json)" diff --git a/components/ws-daemon-api/daemon.proto b/components/ws-daemon-api/daemon.proto index 1b783bdaf1cfe1..b5b5b164809469 100644 --- a/components/ws-daemon-api/daemon.proto +++ b/components/ws-daemon-api/daemon.proto @@ -52,6 +52,9 @@ message InitWorkspaceRequest { // remote_storage_disabled disables any support for remote storage operations, specifically backups and snapshots. // When any such operation is attempted, a FAILED_PRECONDITION error will be the result. bool remote_storage_disabled = 7; + + // storage_quota_bytes enforces a storage quate for the workspace if set to a value != 0 + int64 storage_quota_bytes = 8; } // WorkspaceMetadata is data associated with a workspace that's required for other parts of the system to function diff --git a/components/ws-daemon-api/go/daemon.pb.go b/components/ws-daemon-api/go/daemon.pb.go index 467c6a8ea4644f..ec20567ff4ddcf 100644 --- a/components/ws-daemon-api/go/daemon.pb.go +++ b/components/ws-daemon-api/go/daemon.pb.go @@ -106,6 +106,8 @@ type InitWorkspaceRequest struct { // remote_storage_disabled disables any support for remote storage operations, specifically backups and snapshots. // When any such operation is attempted, a FAILED_PRECONDITION error will be the result. RemoteStorageDisabled bool `protobuf:"varint,7,opt,name=remote_storage_disabled,json=remoteStorageDisabled,proto3" json:"remoteStorageDisabled,omitempty"` + // storage_quota_bytes enforces a storage quate for the workspace if set to a value != 0 + StorageQuotaBytes int64 `protobuf:"varint,8,opt,name=storage_quota_bytes,json=storageQuotaBytes,proto3" json:"storageQuotaBytes,omitempty"` } func (x *InitWorkspaceRequest) Reset() { @@ -182,6 +184,13 @@ func (x *InitWorkspaceRequest) GetRemoteStorageDisabled() bool { return false } +func (x *InitWorkspaceRequest) GetStorageQuotaBytes() int64 { + if x != nil { + return x.StorageQuotaBytes + } + return 0 +} + // WorkspaceMetadata is data associated with a workspace that's required for other parts of the system to function type WorkspaceMetadata struct { state protoimpl.MessageState `json:"state,omitempty"` @@ -691,7 +700,7 @@ var file_daemon_proto_rawDesc = []byte{ 0x77, 0x73, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x1a, 0x25, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0xc4, 0x02, 0x0a, 0x14, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0xf4, 0x02, 0x0a, 0x14, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x77, 0x73, 0x64, @@ -711,6 +720,9 @@ var file_daemon_proto_rawDesc = []byte{ 0x74, 0x65, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x71, 0x75, 0x6f, 0x74, + 0x61, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x42, 0x79, 0x74, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0x42, 0x0a, 0x11, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, diff --git a/components/ws-daemon-api/typescript/src/daemon_pb.d.ts b/components/ws-daemon-api/typescript/src/daemon_pb.d.ts index 481997dfc1a104..c9533914d7ddd8 100644 --- a/components/ws-daemon-api/typescript/src/daemon_pb.d.ts +++ b/components/ws-daemon-api/typescript/src/daemon_pb.d.ts @@ -13,7 +13,7 @@ import * as jspb from "google-protobuf"; import * as content_service_api_initializer_pb from "@gitpod/content-service/lib"; -export class InitWorkspaceRequest extends jspb.Message { +export class InitWorkspaceRequest extends jspb.Message { getId(): string; setId(value: string): InitWorkspaceRequest; @@ -34,6 +34,8 @@ export class InitWorkspaceRequest extends jspb.Message { setContentManifest(value: Uint8Array | string): InitWorkspaceRequest; getRemoteStorageDisabled(): boolean; setRemoteStorageDisabled(value: boolean): InitWorkspaceRequest; + getStorageQuotaBytes(): number; + setStorageQuotaBytes(value: number): InitWorkspaceRequest; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): InitWorkspaceRequest.AsObject; @@ -53,10 +55,11 @@ export namespace InitWorkspaceRequest { fullWorkspaceBackup: boolean, contentManifest: Uint8Array | string, remoteStorageDisabled: boolean, + storageQuotaBytes: number, } } -export class WorkspaceMetadata extends jspb.Message { +export class WorkspaceMetadata extends jspb.Message { getOwner(): string; setOwner(value: string): WorkspaceMetadata; getMetaId(): string; @@ -79,7 +82,7 @@ export namespace WorkspaceMetadata { } } -export class InitWorkspaceResponse extends jspb.Message { +export class InitWorkspaceResponse extends jspb.Message { serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): InitWorkspaceResponse.AsObject; @@ -96,7 +99,7 @@ export namespace InitWorkspaceResponse { } } -export class WaitForInitRequest extends jspb.Message { +export class WaitForInitRequest extends jspb.Message { getId(): string; setId(value: string): WaitForInitRequest; @@ -116,7 +119,7 @@ export namespace WaitForInitRequest { } } -export class WaitForInitResponse extends jspb.Message { +export class WaitForInitResponse extends jspb.Message { serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): WaitForInitResponse.AsObject; @@ -133,7 +136,7 @@ export namespace WaitForInitResponse { } } -export class TakeSnapshotRequest extends jspb.Message { +export class TakeSnapshotRequest extends jspb.Message { getId(): string; setId(value: string): TakeSnapshotRequest; getReturnImmediately(): boolean; @@ -156,7 +159,7 @@ export namespace TakeSnapshotRequest { } } -export class TakeSnapshotResponse extends jspb.Message { +export class TakeSnapshotResponse extends jspb.Message { getUrl(): string; setUrl(value: string): TakeSnapshotResponse; @@ -176,7 +179,7 @@ export namespace TakeSnapshotResponse { } } -export class DisposeWorkspaceRequest extends jspb.Message { +export class DisposeWorkspaceRequest extends jspb.Message { getId(): string; setId(value: string): DisposeWorkspaceRequest; getBackup(): boolean; @@ -202,7 +205,7 @@ export namespace DisposeWorkspaceRequest { } } -export class DisposeWorkspaceResponse extends jspb.Message { +export class DisposeWorkspaceResponse extends jspb.Message { hasGitStatus(): boolean; clearGitStatus(): void; @@ -225,7 +228,7 @@ export namespace DisposeWorkspaceResponse { } } -export class BackupWorkspaceRequest extends jspb.Message { +export class BackupWorkspaceRequest extends jspb.Message { getId(): string; setId(value: string): BackupWorkspaceRequest; @@ -245,7 +248,7 @@ export namespace BackupWorkspaceRequest { } } -export class BackupWorkspaceResponse extends jspb.Message { +export class BackupWorkspaceResponse extends jspb.Message { getUrl(): string; setUrl(value: string): BackupWorkspaceResponse; diff --git a/components/ws-daemon-api/typescript/src/daemon_pb.js b/components/ws-daemon-api/typescript/src/daemon_pb.js index c15f10ce02293c..e5a96e8ef520cc 100644 --- a/components/ws-daemon-api/typescript/src/daemon_pb.js +++ b/components/ws-daemon-api/typescript/src/daemon_pb.js @@ -303,7 +303,8 @@ proto.wsdaemon.InitWorkspaceRequest.toObject = function(includeInstance, msg) { initializer: (f = msg.getInitializer()) && content$service$api_initializer_pb.WorkspaceInitializer.toObject(includeInstance, f), fullWorkspaceBackup: jspb.Message.getBooleanFieldWithDefault(msg, 4, false), contentManifest: msg.getContentManifest_asB64(), - remoteStorageDisabled: jspb.Message.getBooleanFieldWithDefault(msg, 7, false) + remoteStorageDisabled: jspb.Message.getBooleanFieldWithDefault(msg, 7, false), + storageQuotaBytes: jspb.Message.getFieldWithDefault(msg, 8, 0) }; if (includeInstance) { @@ -366,6 +367,10 @@ proto.wsdaemon.InitWorkspaceRequest.deserializeBinaryFromReader = function(msg, var value = /** @type {boolean} */ (reader.readBool()); msg.setRemoteStorageDisabled(value); break; + case 8: + var value = /** @type {number} */ (reader.readInt64()); + msg.setStorageQuotaBytes(value); + break; default: reader.skipField(); break; @@ -439,6 +444,13 @@ proto.wsdaemon.InitWorkspaceRequest.serializeBinaryToWriter = function(message, f ); } + f = message.getStorageQuotaBytes(); + if (f !== 0) { + writer.writeInt64( + 8, + f + ); + } }; @@ -612,6 +624,24 @@ proto.wsdaemon.InitWorkspaceRequest.prototype.setRemoteStorageDisabled = functio }; +/** + * optional int64 storage_quota_bytes = 8; + * @return {number} + */ +proto.wsdaemon.InitWorkspaceRequest.prototype.getStorageQuotaBytes = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 8, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.wsdaemon.InitWorkspaceRequest} returns this + */ +proto.wsdaemon.InitWorkspaceRequest.prototype.setStorageQuotaBytes = function(value) { + return jspb.Message.setProto3IntField(this, 8, value); +}; + + diff --git a/components/ws-daemon/pkg/content/config.go b/components/ws-daemon/pkg/content/config.go index fac727f52efa19..8ce46961010353 100644 --- a/components/ws-daemon/pkg/content/config.go +++ b/components/ws-daemon/pkg/content/config.go @@ -11,7 +11,6 @@ import ( "github.com/gitpod-io/gitpod/common-go/util" cntntcfg "github.com/gitpod-io/gitpod/content-service/api/config" "github.com/gitpod-io/gitpod/ws-daemon/api" - "github.com/gitpod-io/gitpod/ws-daemon/pkg/quota" "golang.org/x/xerrors" ) @@ -29,9 +28,6 @@ type Config struct { // TmpDir is the temp working diretory for creating tar files during upload TmpDir string `json:"tempDir"` - // Limit limits the size of a sandbox - WorkspaceSizeLimit quota.Size `json:"workspaceSizeLimit"` - // Storage is some form of permanent file store to which we back up workspaces Storage cntntcfg.StorageConfig `json:"storage"` diff --git a/components/ws-daemon/pkg/content/hooks.go b/components/ws-daemon/pkg/content/hooks.go index 45e5fe56b77752..46a371d4ee5025 100644 --- a/components/ws-daemon/pkg/content/hooks.go +++ b/components/ws-daemon/pkg/content/hooks.go @@ -28,12 +28,12 @@ func workspaceLifecycleHooks(cfg Config, kubernetesNamespace string, workspaceEx session.WorkspaceInitializing: { hookSetupRemoteStorage(cfg), hookSetupWorkspaceLocation, - hookInstallQuota(xfs, cfg.WorkspaceSizeLimit), + hookInstallQuota(xfs), startIWS, }, session.WorkspaceReady: { hookSetupRemoteStorage(cfg), - hookInstallQuota(xfs, cfg.WorkspaceSizeLimit), + hookInstallQuota(xfs), startIWS, }, session.WorkspaceDisposed: { @@ -93,11 +93,12 @@ func hookSetupWorkspaceLocation(ctx context.Context, ws *session.Workspace) erro } // hookInstallQuota enforces filesystem quota on the workspace location (if the filesystem supports it) -func hookInstallQuota(xfs *quota.XFS, size quota.Size) session.WorkspaceLivecycleHook { +func hookInstallQuota(xfs *quota.XFS) session.WorkspaceLivecycleHook { return func(ctx context.Context, ws *session.Workspace) error { if xfs == nil { return nil } + size := quota.Size(ws.StorageQuota) if size == 0 { return nil } diff --git a/components/ws-daemon/pkg/content/service.go b/components/ws-daemon/pkg/content/service.go index c571d7c13574f3..81da919d6446dd 100644 --- a/components/ws-daemon/pkg/content/service.go +++ b/components/ws-daemon/pkg/content/service.go @@ -261,6 +261,7 @@ func (s *WorkspaceService) creator(req *api.InitWorkspaceRequest) session.Worksp FullWorkspaceBackup: req.FullWorkspaceBackup, ContentManifest: req.ContentManifest, RemoteStorageDisabled: req.RemoteStorageDisabled, + StorageQuota: int(req.StorageQuotaBytes), ServiceLocDaemon: filepath.Join(s.config.WorkingArea, ServiceDirName(req.Id)), ServiceLocNode: filepath.Join(s.config.WorkingAreaNode, ServiceDirName(req.Id)), diff --git a/components/ws-daemon/pkg/internal/session/workspace.go b/components/ws-daemon/pkg/internal/session/workspace.go index e8a6ee0b758755..e51e0a3fe7be14 100644 --- a/components/ws-daemon/pkg/internal/session/workspace.go +++ b/components/ws-daemon/pkg/internal/session/workspace.go @@ -68,6 +68,7 @@ type Workspace struct { ServiceLocDaemon string `json:"serviceLocDaemon"` RemoteStorageDisabled bool `json:"remoteStorageDisabled,omitempty"` + StorageQuota int `json:"storageQuota,omitempty"` XFSProjectID int `json:"xfsProjectID"` diff --git a/components/ws-manager-api/go/config/config.go b/components/ws-manager-api/go/config/config.go index c9750abd81d734..1a30833fc2aa54 100644 --- a/components/ws-manager-api/go/config/config.go +++ b/components/ws-manager-api/go/config/config.go @@ -295,9 +295,23 @@ var validResourceConfig = validation.By(func(o interface{}) error { return xerrors.Errorf("cannot parse EphemeralStorage quantity: %w", err) } } + if rc.Storage != "" { + _, err := resource.ParseQuantity(rc.Storage) + if err != nil { + return xerrors.Errorf("cannot parse Storage quantity: %w", err) + } + } return nil }) +func (r *ResourceConfiguration) StorageQuantity() (resource.Quantity, error) { + if r.Storage == "" { + res := resource.NewQuantity(0, resource.BinarySI) + return *res, nil + } + return resource.ParseQuantity(r.Storage) +} + // ResourceList parses the quantities in the resource config func (r *ResourceConfiguration) ResourceList() (corev1.ResourceList, error) { if r == nil { @@ -411,4 +425,5 @@ type ResourceConfiguration struct { CPU string `json:"cpu"` Memory string `json:"memory"` EphemeralStorage string `json:"ephemeral-storage"` + Storage string `json:"storage,omitempty"` } diff --git a/components/ws-manager/pkg/manager/monitor.go b/components/ws-manager/pkg/manager/monitor.go index 9e5d80ba65f60e..97f851e03ed8f8 100644 --- a/components/ws-manager/pkg/manager/monitor.go +++ b/components/ws-manager/pkg/manager/monitor.go @@ -669,6 +669,15 @@ func (m *Monitor) initializeWorkspaceContent(ctx context.Context, pod *corev1.Po return xerrors.Errorf("pod %s has no owner", pod.Name) } + class, ok := m.manager.Config.WorkspaceClasses[pod.Labels[workspaceClassLabel]] + if !ok { + return xerrors.Errorf("pod %s has unknown workspace class", pod.Name, pod.Labels[workspaceClassLabel]) + } + storage, err := class.Container.Limits.StorageQuantity() + if !ok { + return xerrors.Errorf("workspace class %s has invalid storage quantity: %w", pod.Labels[workspaceClassLabel], err) + } + var ( initializer csapi.WorkspaceInitializer snc wsdaemon.WorkspaceContentServiceClient @@ -746,6 +755,7 @@ func (m *Monitor) initializeWorkspaceContent(ctx context.Context, pod *corev1.Po FullWorkspaceBackup: fullWorkspaceBackup, ContentManifest: contentManifest, RemoteStorageDisabled: shouldDisableRemoteStorage(pod), + StorageQuotaBytes: storage.Value(), }) return err })