diff --git a/workspaces/backend/api/suite_test.go b/workspaces/backend/api/suite_test.go index ce03bdada..9c0c77fef 100644 --- a/workspaces/backend/api/suite_test.go +++ b/workspaces/backend/api/suite_test.go @@ -455,5 +455,34 @@ func NewExampleWorkspaceKind(name string) *kubefloworgv1beta1.WorkspaceKind { }, }, }, + Status: kubefloworgv1beta1.WorkspaceKindStatus{ + Workspaces: 1, + PodTemplateOptions: kubefloworgv1beta1.PodTemplateOptionsMetrics{ + ImageConfig: []kubefloworgv1beta1.OptionMetric{ + { + Id: "jupyterlab_scipy_180", + Workspaces: 1, + }, + { + Id: "jupyterlab_scipy_190", + Workspaces: 0, + }, + }, + PodConfig: []kubefloworgv1beta1.OptionMetric{ + { + Id: "tiny_cpu", + Workspaces: 1, + }, + { + Id: "small_cpu", + Workspaces: 0, + }, + { + Id: "big_gpu", + Workspaces: 0, + }, + }, + }, + }, } } diff --git a/workspaces/backend/internal/models/workspacekinds/funcs.go b/workspaces/backend/internal/models/workspacekinds/funcs.go index 98c77f0f4..268b458ed 100644 --- a/workspaces/backend/internal/models/workspacekinds/funcs.go +++ b/workspaces/backend/internal/models/workspacekinds/funcs.go @@ -36,6 +36,8 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin podAnnotations[k] = v } } + statusImageConfigMap := buildOptionMetricsMap(wsk.Status.PodTemplateOptions.ImageConfig) + statusPodConfigMap := buildOptionMetricsMap(wsk.Status.PodTemplateOptions.PodConfig) // TODO: icons can either be a remote URL or read from a ConfigMap. // in BOTH cases, we should cache and serve the image under a path on the backend API: @@ -60,6 +62,10 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin Hidden: ptr.Deref(wsk.Spec.Spawner.Hidden, false), Icon: iconRef, Logo: logoRef, + // TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context + ClusterMetrics: clusterMetrics{ + Workspaces: wsk.Status.Workspaces, + }, PodTemplate: PodTemplate{ PodMetadata: PodMetadata{ Labels: podLabels, @@ -71,18 +77,26 @@ func NewWorkspaceKindModelFromWorkspaceKind(wsk *kubefloworgv1beta1.WorkspaceKin Options: PodTemplateOptions{ ImageConfig: ImageConfig{ Default: wsk.Spec.PodTemplate.Options.ImageConfig.Spawner.Default, - Values: buildImageConfigValues(wsk.Spec.PodTemplate.Options.ImageConfig), + Values: buildImageConfigValues(wsk.Spec.PodTemplate.Options.ImageConfig, statusImageConfigMap), }, PodConfig: PodConfig{ Default: wsk.Spec.PodTemplate.Options.PodConfig.Spawner.Default, - Values: buildPodConfigValues(wsk.Spec.PodTemplate.Options.PodConfig), + Values: buildPodConfigValues(wsk.Spec.PodTemplate.Options.PodConfig, statusPodConfigMap), }, }, }, } } -func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig) []ImageConfigValue { +func buildOptionMetricsMap(metrics []kubefloworgv1beta1.OptionMetric) map[string]int32 { + resultMap := make(map[string]int32) + for _, metric := range metrics { + resultMap[metric.Id] = metric.Workspaces + } + return resultMap +} + +func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig, statusImageConfigMap map[string]int32) []ImageConfigValue { imageConfigValues := make([]ImageConfigValue, len(imageConfig.Values)) for i := range imageConfig.Values { option := imageConfig.Values[i] @@ -93,12 +107,16 @@ func buildImageConfigValues(imageConfig kubefloworgv1beta1.ImageConfig) []ImageC Labels: buildOptionLabels(option.Spawner.Labels), Hidden: ptr.Deref(option.Spawner.Hidden, false), Redirect: buildOptionRedirect(option.Redirect), + // TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context + ClusterMetrics: clusterMetrics{ + Workspaces: statusImageConfigMap[option.Id], + }, } } return imageConfigValues } -func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig) []PodConfigValue { +func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig, statusPodConfigMap map[string]int32) []PodConfigValue { podConfigValues := make([]PodConfigValue, len(podConfig.Values)) for i := range podConfig.Values { option := podConfig.Values[i] @@ -109,6 +127,10 @@ func buildPodConfigValues(podConfig kubefloworgv1beta1.PodConfig) []PodConfigVal Labels: buildOptionLabels(option.Spawner.Labels), Hidden: ptr.Deref(option.Spawner.Hidden, false), Redirect: buildOptionRedirect(option.Redirect), + // TODO: in the future will need to support including exactly one of clusterMetrics or namespaceMetrics based on request context + ClusterMetrics: clusterMetrics{ + Workspaces: statusPodConfigMap[option.Id], + }, } } return podConfigValues diff --git a/workspaces/backend/internal/models/workspacekinds/types.go b/workspaces/backend/internal/models/workspacekinds/types.go index c7947ea8a..2996f4666 100644 --- a/workspaces/backend/internal/models/workspacekinds/types.go +++ b/workspaces/backend/internal/models/workspacekinds/types.go @@ -17,15 +17,20 @@ limitations under the License. package workspacekinds type WorkspaceKind struct { - Name string `json:"name"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - Deprecated bool `json:"deprecated"` - DeprecationMessage string `json:"deprecationMessage"` - Hidden bool `json:"hidden"` - Icon ImageRef `json:"icon"` - Logo ImageRef `json:"logo"` - PodTemplate PodTemplate `json:"podTemplate"` + Name string `json:"name"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Deprecated bool `json:"deprecated"` + DeprecationMessage string `json:"deprecationMessage"` + Hidden bool `json:"hidden"` + Icon ImageRef `json:"icon"` + Logo ImageRef `json:"logo"` + ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"` + PodTemplate PodTemplate `json:"podTemplate"` +} + +type clusterMetrics struct { + Workspaces int32 `json:"workspacesCount"` } type ImageRef struct { @@ -58,12 +63,13 @@ type ImageConfig struct { } type ImageConfigValue struct { - Id string `json:"id"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - Labels []OptionLabel `json:"labels"` - Hidden bool `json:"hidden"` - Redirect *OptionRedirect `json:"redirect,omitempty"` + Id string `json:"id"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Labels []OptionLabel `json:"labels"` + Hidden bool `json:"hidden"` + Redirect *OptionRedirect `json:"redirect,omitempty"` + ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"` } type PodConfig struct { @@ -72,12 +78,13 @@ type PodConfig struct { } type PodConfigValue struct { - Id string `json:"id"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - Labels []OptionLabel `json:"labels"` - Hidden bool `json:"hidden"` - Redirect *OptionRedirect `json:"redirect,omitempty"` + Id string `json:"id"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Labels []OptionLabel `json:"labels"` + Hidden bool `json:"hidden"` + Redirect *OptionRedirect `json:"redirect,omitempty"` + ClusterMetrics clusterMetrics `json:"clusterMetrics,omitempty"` } type OptionLabel struct { diff --git a/workspaces/backend/openapi/docs.go b/workspaces/backend/openapi/docs.go index ba09bcc6a..7fa3f97dd 100644 --- a/workspaces/backend/openapi/docs.go +++ b/workspaces/backend/openapi/docs.go @@ -778,6 +778,9 @@ const docTemplate = `{ "workspacekinds.ImageConfigValue": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "description": { "type": "string" }, @@ -848,6 +851,9 @@ const docTemplate = `{ "workspacekinds.PodConfigValue": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "description": { "type": "string" }, @@ -948,6 +954,9 @@ const docTemplate = `{ "workspacekinds.WorkspaceKind": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "deprecated": { "type": "boolean" }, @@ -977,6 +986,14 @@ const docTemplate = `{ } } }, + "workspacekinds.clusterMetrics": { + "type": "object", + "properties": { + "workspacesCount": { + "type": "integer" + } + } + }, "workspaces.Activity": { "type": "object", "properties": { diff --git a/workspaces/backend/openapi/swagger.json b/workspaces/backend/openapi/swagger.json index 31c2b5548..b5c0e0857 100644 --- a/workspaces/backend/openapi/swagger.json +++ b/workspaces/backend/openapi/swagger.json @@ -776,6 +776,9 @@ "workspacekinds.ImageConfigValue": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "description": { "type": "string" }, @@ -846,6 +849,9 @@ "workspacekinds.PodConfigValue": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "description": { "type": "string" }, @@ -946,6 +952,9 @@ "workspacekinds.WorkspaceKind": { "type": "object", "properties": { + "clusterMetrics": { + "$ref": "#/definitions/workspacekinds.clusterMetrics" + }, "deprecated": { "type": "boolean" }, @@ -975,6 +984,14 @@ } } }, + "workspacekinds.clusterMetrics": { + "type": "object", + "properties": { + "workspacesCount": { + "type": "integer" + } + } + }, "workspaces.Activity": { "type": "object", "properties": {