Skip to content

Commit

Permalink
feat: Support configuring workspace deployment strategy in DWOC
Browse files Browse the repository at this point in the history
This commit adds a new DWOC field `config.workspace.deploymentStrategy` which allows
for specifying the workspace deployment strategy in the DWOC.
The possible choices are `Recreate` (Default) and `RollingUpdate`.

Fix devfile#1045

Signed-off-by: Andrew Obuchowicz <[email protected]>
  • Loading branch information
AObuchow committed Mar 29, 2023
1 parent 4f8fe70 commit 78d4be3
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 5 deletions.
9 changes: 9 additions & 0 deletions apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package v1alpha1

import (
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -100,6 +101,14 @@ type WorkspaceConfig struct {
// not specified, the default value of "Always" is used.
// +kubebuilder:validation:Enum=IfNotPresent;Always;Never
ImagePullPolicy string `json:"imagePullPolicy,omitempty"`
// DeploymentStrategy defines the deployment strategy to use to replace existing DevWorkspace pods
// with new ones. The available deployment stragies are "Recreate" and "RollingUpdate".
// With the "Recreate" deployment strategy, the existing workspace pod is killed before the new one is created.
// With the "RollingUpdate" deployment strategy, a new workspace pod is created and the existing workspace pod is deleted
// only when the new workspace pod is in a ready state.
// If not specified, the default "Recreate" deployment strategy is used.
// +kubebuilder:validation:Enum=Recreate;RollingUpdate
DeploymentStrategy appsv1.DeploymentStrategyType `json:"deploymentStrategy,omitempty"`
// PVCName defines the name used for the persistent volume claim created
// to support workspace storage when the 'common' storage class is used.
// If not specified, the default value of `claim-devworkspace` is used.
Expand Down
6 changes: 4 additions & 2 deletions pkg/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/pointer"
Expand All @@ -32,8 +33,9 @@ var defaultConfig = &v1alpha1.OperatorConfiguration{
ClusterHostSuffix: "", // is auto discovered when running on OpenShift. Must be defined by CR on Kubernetes.
},
Workspace: &v1alpha1.WorkspaceConfig{
ImagePullPolicy: "Always",
PVCName: "claim-devworkspace",
ImagePullPolicy: "Always",
DeploymentStrategy: appsv1.RecreateDeploymentStrategyType,
PVCName: "claim-devworkspace",
ServiceAccount: &v1alpha1.ServiceAccountConfig{
DisableCreation: pointer.Bool(false),
},
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ func mergeConfig(from, to *controller.OperatorConfiguration) {
if from.Workspace.ImagePullPolicy != "" {
to.Workspace.ImagePullPolicy = from.Workspace.ImagePullPolicy
}
if from.Workspace.DeploymentStrategy != "" {
to.Workspace.DeploymentStrategy = from.Workspace.DeploymentStrategy
}
if from.Workspace.IdleTimeout != "" {
to.Workspace.IdleTimeout = from.Workspace.IdleTimeout
}
Expand Down Expand Up @@ -399,6 +402,9 @@ func GetCurrentConfigString(currConfig *controller.OperatorConfiguration) string
if workspace.ImagePullPolicy != defaultConfig.Workspace.ImagePullPolicy {
config = append(config, fmt.Sprintf("workspace.imagePullPolicy=%s", workspace.ImagePullPolicy))
}
if workspace.DeploymentStrategy != defaultConfig.Workspace.DeploymentStrategy {
config = append(config, fmt.Sprintf("workspace.deploymentStrategy=%s", workspace.DeploymentStrategy))
}
if workspace.PVCName != defaultConfig.Workspace.PVCName {
config = append(config, fmt.Sprintf("workspace.pvcName=%s", workspace.PVCName))
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/config/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
fuzz "github.com/google/gofuzz"
routev1 "github.com/openshift/api/route/v1"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -97,6 +98,15 @@ func TestMergesAllFieldsFromClusterConfig(t *testing.T) {
func(_ *dw.DevWorkspaceTemplateSpecContent, c fuzz.Continue) {},
// Ensure no empty strings are generated as they cause default values to be used
func(s *string, c fuzz.Continue) { *s = "a" + c.RandString() },
// The only valid deployment strategies are Recreate and RollingUpdate
func(deploymentStrategy *appsv1.DeploymentStrategyType, c fuzz.Continue) {
switch c.Int() % 2 {
case 0:
*deploymentStrategy = appsv1.RollingUpdateDeploymentStrategyType
default:
*deploymentStrategy = appsv1.RecreateDeploymentStrategyType
}
},
)
for i := 0; i < 100; i++ {
fuzzedConfig := &v1alpha1.OperatorConfiguration{}
Expand Down
9 changes: 9 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// Package constants defines constant values used throughout the DevWorkspace Operator
package constants

import "k8s.io/apimachinery/pkg/util/intstr"

// Labels which should be used for controller related objects
var ControllerAppLabels = func() map[string]string {
return map[string]string{
Expand All @@ -24,6 +26,13 @@ var ControllerAppLabels = func() map[string]string {
}
}

var (
// Maximum number of unavailable workspace pods when using the RollingUpdate deployment strategy
RollingUpdateMaxUnavailable = intstr.FromInt(0)
// Maximum number of excesss workspace pods when using the RollingUpdate deployment strategy
RollingUpdateMaximumSurge = intstr.FromInt(1)
)

// Internal constants
const (
DefaultProjectsSourcesRoot = "/projects"
Expand Down
14 changes: 11 additions & 3 deletions pkg/provision/workspace/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,16 @@ func getSpecDeployment(
return nil, err
}

deploymentStrategy := appsv1.DeploymentStrategy{
Type: workspace.Config.Workspace.DeploymentStrategy,
}
if workspace.Config.Workspace.DeploymentStrategy == appsv1.RollingUpdateDeploymentStrategyType {
deploymentStrategy.RollingUpdate = &appsv1.RollingUpdateDeployment{
MaxUnavailable: &constants.RollingUpdateMaxUnavailable,
MaxSurge: &constants.RollingUpdateMaximumSurge,
}
}

deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: common.DeploymentName(workspace.Status.DevWorkspaceId),
Expand All @@ -213,9 +223,7 @@ func getSpecDeployment(
constants.DevWorkspaceIDLabel: workspace.Status.DevWorkspaceId,
},
},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RecreateDeploymentStrategyType,
},
Strategy: deploymentStrategy,
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: workspace.Status.DevWorkspaceId,
Expand Down

0 comments on commit 78d4be3

Please sign in to comment.