notebooks/workspaces/backend/internal/repositories/workspaces.go

260 lines
8.6 KiB
Go

/*
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package repositories
import (
"context"
"errors"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubeflow/notebooks/workspaces/backend/internal/models"
)
var ErrWorkspaceNotFound = errors.New("workspace not found")
type WorkspaceRepository struct {
client client.Client
}
func NewWorkspaceRepository(cl client.Client) *WorkspaceRepository {
return &WorkspaceRepository{
client: cl,
}
}
func (r *WorkspaceRepository) GetWorkspace(ctx context.Context, namespace string, workspaceName string) (models.WorkspaceModel, error) {
workspace := &kubefloworgv1beta1.Workspace{}
if err := r.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: workspaceName}, workspace); err != nil {
if apierrors.IsNotFound(err) {
return models.WorkspaceModel{}, ErrWorkspaceNotFound
}
return models.WorkspaceModel{}, err
}
kind := &kubefloworgv1beta1.WorkspaceKind{}
if err := r.client.Get(ctx, client.ObjectKey{Name: workspace.Spec.Kind}, kind); err != nil {
return models.WorkspaceModel{}, err
}
return NewWorkspaceModelFromWorkspace(workspace, kind), nil
}
func (r *WorkspaceRepository) GetWorkspaces(ctx context.Context, namespace string) ([]models.WorkspaceModel, error) {
workspaceList := &kubefloworgv1beta1.WorkspaceList{}
listOptions := []client.ListOption{
client.InNamespace(namespace),
}
err := r.client.List(ctx, workspaceList, listOptions...)
if err != nil {
return nil, err
}
workspacesModels := make([]models.WorkspaceModel, len(workspaceList.Items))
for i, item := range workspaceList.Items {
kind := &kubefloworgv1beta1.WorkspaceKind{}
if err := r.client.Get(ctx, client.ObjectKey{Name: item.Spec.Kind}, kind); err != nil {
return nil, err
}
workspacesModels[i] = NewWorkspaceModelFromWorkspace(&item, kind)
}
return workspacesModels, nil
}
func (r *WorkspaceRepository) GetAllWorkspaces(ctx context.Context) ([]models.WorkspaceModel, error) {
workspaceList := &kubefloworgv1beta1.WorkspaceList{}
if err := r.client.List(ctx, workspaceList); err != nil {
return nil, err
}
workspacesModels := make([]models.WorkspaceModel, len(workspaceList.Items))
for i, item := range workspaceList.Items {
kind := &kubefloworgv1beta1.WorkspaceKind{}
if err := r.client.Get(ctx, client.ObjectKey{Name: item.Spec.Kind}, kind); err != nil {
return nil, err
}
workspacesModels[i] = NewWorkspaceModelFromWorkspace(&item, kind)
}
return workspacesModels, nil
}
func (r *WorkspaceRepository) CreateWorkspace(ctx context.Context, workspaceModel *models.WorkspaceModel) (models.WorkspaceModel, error) {
// TODO: review all fields
workspace := &kubefloworgv1beta1.Workspace{
ObjectMeta: metav1.ObjectMeta{
Name: workspaceModel.Name,
Namespace: workspaceModel.Namespace,
// TODO: the pod and workspace labels should be separated
Labels: workspaceModel.PodTemplate.PodMetadata.Labels,
Annotations: workspaceModel.PodTemplate.PodMetadata.Annotations,
},
Spec: kubefloworgv1beta1.WorkspaceSpec{
Paused: &workspaceModel.Paused,
DeferUpdates: &workspaceModel.DeferUpdates,
// TODO: verify if workspace kind exists on validation
Kind: workspaceModel.WorkspaceKind.Name,
PodTemplate: kubefloworgv1beta1.WorkspacePodTemplate{
PodMetadata: &kubefloworgv1beta1.WorkspacePodMetadata{
Labels: workspaceModel.PodTemplate.PodMetadata.Labels,
Annotations: workspaceModel.PodTemplate.PodMetadata.Annotations,
},
Volumes: kubefloworgv1beta1.WorkspacePodVolumes{
Home: &workspaceModel.PodTemplate.Volumes.Home.PvcName,
Data: []kubefloworgv1beta1.PodVolumeMount{},
},
Options: kubefloworgv1beta1.WorkspacePodOptions{
ImageConfig: workspaceModel.PodTemplate.ImageConfig.Current,
PodConfig: workspaceModel.PodTemplate.PodConfig.Current,
},
},
},
}
// TODO: create data volumes if necessary
workspace.Spec.PodTemplate.Volumes.Data = make([]kubefloworgv1beta1.PodVolumeMount, len(workspaceModel.PodTemplate.Volumes.Data))
for i, dataVolume := range workspaceModel.PodTemplate.Volumes.Data {
// make a copy of readOnly because dataVolume is reassigned each loop
readOnly := dataVolume.ReadOnly
workspace.Spec.PodTemplate.Volumes.Data[i] = kubefloworgv1beta1.PodVolumeMount{
PVCName: dataVolume.PvcName,
MountPath: dataVolume.MountPath,
ReadOnly: &readOnly,
}
}
if err := r.client.Create(ctx, workspace); err != nil {
return models.WorkspaceModel{}, err
}
kind := &kubefloworgv1beta1.WorkspaceKind{}
if err := r.client.Get(ctx, client.ObjectKey{Name: workspace.Spec.Kind}, kind); err != nil {
return models.WorkspaceModel{}, err
}
return NewWorkspaceModelFromWorkspace(workspace, kind), nil
}
func (r *WorkspaceRepository) DeleteWorkspace(ctx context.Context, namespace, workspaceName string) error {
workspace := &kubefloworgv1beta1.Workspace{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: workspaceName,
},
}
if err := r.client.Delete(ctx, workspace); err != nil {
if apierrors.IsNotFound(err) {
return ErrWorkspaceNotFound
}
return err
}
return nil
}
func NewWorkspaceModelFromWorkspace(item *kubefloworgv1beta1.Workspace, wsk *kubefloworgv1beta1.WorkspaceKind) models.WorkspaceModel {
dataVolumes := make([]models.DataVolumeModel, len(item.Spec.PodTemplate.Volumes.Data))
for i, volume := range item.Spec.PodTemplate.Volumes.Data {
dataVolumes[i] = models.DataVolumeModel{
PvcName: volume.PVCName,
MountPath: volume.MountPath,
ReadOnly: *volume.ReadOnly,
}
}
imageConfigRedirectChain := make([]*models.RedirectChain, len(item.Status.PodTemplateOptions.ImageConfig.RedirectChain))
for i, chain := range item.Status.PodTemplateOptions.ImageConfig.RedirectChain {
imageConfigRedirectChain[i] = &models.RedirectChain{
Source: chain.Source,
Target: chain.Target,
}
}
podConfigRedirectChain := make([]*models.RedirectChain, len(item.Status.PodTemplateOptions.PodConfig.RedirectChain))
for i, chain := range item.Status.PodTemplateOptions.PodConfig.RedirectChain {
podConfigRedirectChain[i] = &models.RedirectChain{
Source: chain.Source,
Target: chain.Target,
}
}
podMetadataLabels := item.Spec.PodTemplate.PodMetadata.Labels
if podMetadataLabels == nil {
podMetadataLabels = map[string]string{}
}
podMetadataAnnotations := item.Spec.PodTemplate.PodMetadata.Annotations
if podMetadataAnnotations == nil {
podMetadataAnnotations = map[string]string{}
}
workspaceModel := models.WorkspaceModel{
Name: item.ObjectMeta.Name,
Namespace: item.Namespace,
WorkspaceKind: models.WorkspaceKind{
Name: item.Spec.Kind,
Type: "POD_TEMPLATE",
},
DeferUpdates: *item.Spec.DeferUpdates,
Paused: *item.Spec.Paused,
PausedTime: item.Status.PauseTime,
State: string(item.Status.State),
StateMessage: item.Status.StateMessage,
PodTemplate: models.PodTemplate{
PodMetadata: &models.PodMetadata{
Labels: podMetadataLabels,
Annotations: podMetadataAnnotations,
},
Volumes: &models.Volumes{
Home: &models.DataVolumeModel{
PvcName: *item.Spec.PodTemplate.Volumes.Home,
MountPath: wsk.Spec.PodTemplate.VolumeMounts.Home,
ReadOnly: false, // From where to get this value?
},
Data: dataVolumes,
},
ImageConfig: &models.ImageConfig{
Current: item.Spec.PodTemplate.Options.ImageConfig,
Desired: item.Status.PodTemplateOptions.ImageConfig.Desired,
RedirectChain: imageConfigRedirectChain,
},
PodConfig: &models.PodConfig{
Current: item.Spec.PodTemplate.Options.PodConfig,
Desired: item.Spec.PodTemplate.Options.PodConfig,
RedirectChain: podConfigRedirectChain,
},
},
Activity: models.Activity{
LastActivity: item.Status.Activity.LastActivity,
LastUpdate: item.Status.Activity.LastUpdate,
// TODO: update these fields when the last probe is implemented
LastProbe: &models.Probe{
StartTimeMs: 0,
EndTimeMs: 0,
Result: "default_result",
Message: "default_message",
},
},
}
return workspaceModel
}