Skip to content

Commit

Permalink
Add update strategy to the Stack (#179)
Browse files Browse the repository at this point in the history
Signed-off-by: Arjun Naik <arjun@arjunnaik.in>
  • Loading branch information
arjunrn authored Dec 16, 2019
1 parent 52e6818 commit b046043
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 6 deletions.
50 changes: 45 additions & 5 deletions cmd/e2e/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/stretchr/testify/require"
zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1"
apps "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
Expand All @@ -25,6 +26,8 @@ type TestStacksetSpecFactory struct {
hpaMaxReplicas int32
hpaMinReplicas int32
autoscaler bool
maxSurge int
maxUnavailable int
metrics []zv1.AutoscalerMetrics
}

Expand Down Expand Up @@ -132,9 +135,36 @@ func (f *TestStacksetSpecFactory) Create(stackVersion string) zv1.StackSetSpec {
BackendPort: intstr.FromInt(80),
}
}
if f.maxSurge != 0 || f.maxUnavailable != 0 {
strategy := &apps.DeploymentStrategy{
Type: apps.RollingUpdateDeploymentStrategyType,
RollingUpdate: &apps.RollingUpdateDeployment{},
}
if f.maxSurge != 0 {
strategy.RollingUpdate.MaxSurge = intstrptr(f.maxSurge)
}
if f.maxUnavailable != 0 {
strategy.RollingUpdate.MaxUnavailable = intstrptr(f.maxUnavailable)
}
}
return result
}

func intstrptr(value int) *intstr.IntOrString {
v := intstr.FromInt(value)
return &v
}

func (f *TestStacksetSpecFactory) UpdateMaxSurge(maxSurge int) *TestStacksetSpecFactory {
f.maxSurge = maxSurge
return f
}

func (f *TestStacksetSpecFactory) UpdateMaxUnavailable(maxUnavailable int) *TestStacksetSpecFactory {
f.maxUnavailable = maxUnavailable
return f
}

func (f *TestStacksetSpecFactory) Autoscaler(minReplicas, maxReplicas int32, metrics []zv1.AutoscalerMetrics) *TestStacksetSpecFactory {
f.autoscaler = true
f.hpaMinReplicas = minReplicas
Expand Down Expand Up @@ -165,6 +195,9 @@ func verifyStack(t *testing.T, stacksetName, currentVersion string, stacksetSpec
require.EqualValues(t, stackResourceLabels, deployment.Labels)
require.EqualValues(t, replicas(deployment.Spec.Replicas), replicas(stack.Spec.Replicas))
require.EqualValues(t, stackResourceLabels, deployment.Spec.Template.Labels)
if stacksetSpec.StackTemplate.Spec.Strategy != nil {
require.EqualValues(t, *stacksetSpec.StackTemplate.Spec.Strategy, deployment.Spec.Strategy)
}

// Verify service
service, err := waitForService(t, stack.Name)
Expand Down Expand Up @@ -287,7 +320,7 @@ func verifyStacksetIngress(t *testing.T, stacksetName string, stacksetSpec zv1.S
require.NoError(t, err)
}

func testStacksetCreate(t *testing.T, testName string, hpa, ingress, externalIngress bool) {
func testStacksetCreate(t *testing.T, testName string, hpa, ingress, externalIngress bool, updateStrategy bool) {
t.Parallel()

stacksetName := fmt.Sprintf("stackset-create-%s", testName)
Expand All @@ -302,6 +335,9 @@ func testStacksetCreate(t *testing.T, testName string, hpa, ingress, externalIng
if externalIngress {
stacksetSpecFactory.ExternalIngress()
}
if updateStrategy {
stacksetSpecFactory.UpdateMaxSurge(10).UpdateMaxUnavailable(100)
}
stacksetSpec := stacksetSpecFactory.Create(stackVersion)
err := createStackSet(stacksetName, 0, stacksetSpec)
require.NoError(t, err)
Expand Down Expand Up @@ -416,19 +452,23 @@ func testStacksetUpdate(t *testing.T, testName string, oldHpa, newHpa, oldIngres
}

func TestStacksetCreateBasic(t *testing.T) {
testStacksetCreate(t, "basic", false, false, false)
testStacksetCreate(t, "basic", false, false, false, false)
}

func TestStacksetCreateHPA(t *testing.T) {
testStacksetCreate(t, "hpa", true, false, false)
testStacksetCreate(t, "hpa", true, false, false, false)
}

func TestStacksetCreateIngress(t *testing.T) {
testStacksetCreate(t, "ingress", false, true, false)
testStacksetCreate(t, "ingress", false, true, false, false)
}

func TestStacksetCreateExternalIngress(t *testing.T) {
testStacksetCreate(t, "externalingress", false, false, true)
testStacksetCreate(t, "externalingress", false, false, true, false)
}

func TestStacksetCreateUpdateStrategy(t *testing.T) {
testStacksetCreate(t, "updatestrategy", false, false, false, true)
}

func TestStacksetUpdateBasic(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/zalando.org/v1/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v1

import (
appsv1 "k8s.io/api/apps/v1"
autoscaling "k8s.io/api/autoscaling/v2beta1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
Expand Down Expand Up @@ -230,6 +231,9 @@ type StackSpec struct {
PodTemplate v1.PodTemplateSpec `json:"podTemplate"`

Autoscaler *Autoscaler `json:"autoscaler,omitempty"`

// Strategy describe the rollout strategy for the underlying deployment
Strategy *appsv1.DeploymentStrategy `json:"strategy,omitempty"`
}

// StackServiceSpec makes it possible to customize the service generated for
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/zalando.org/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion pkg/core/stack_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,12 @@ func (sc *StackContainer) GenerateDeployment() *appsv1.Deployment {
updatedReplicas = wrapReplicas(sc.deploymentReplicas)
}

return &appsv1.Deployment{
var strategy *appsv1.DeploymentStrategy
if stack.Spec.Strategy != nil {
strategy = stack.Spec.Strategy.DeepCopy()
}

deployment := &appsv1.Deployment{
ObjectMeta: sc.resourceMeta(),
Spec: appsv1.DeploymentSpec{
Replicas: updatedReplicas,
Expand All @@ -176,6 +181,10 @@ func (sc *StackContainer) GenerateDeployment() *appsv1.Deployment {
Template: *templateInjectLabels(stack.Spec.PodTemplate.DeepCopy(), stack.Labels),
},
}
if strategy != nil {
deployment.Spec.Strategy = *strategy
}
return deployment
}

func (sc *StackContainer) GenerateHPA() (*autoscaling.HorizontalPodAutoscaler, error) {
Expand Down
43 changes: 43 additions & 0 deletions pkg/core/stack_resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ func TestStackGenerateDeployment(t *testing.T) {
deploymentReplicas int32
noTrafficSince time.Time
expectedReplicas int32
maxUnavailable int
maxSurge int
}{
{
name: "stack scaled down to zero, deployment still running",
Expand Down Expand Up @@ -497,12 +499,50 @@ func TestStackGenerateDeployment(t *testing.T) {
deploymentReplicas: 5,
expectedReplicas: 5,
},
{
name: "max surge is specified",
stackReplicas: 3,
deploymentReplicas: 3,
expectedReplicas: 3,
maxSurge: 10,
},
{
name: "max unavailable is specified",
stackReplicas: 3,
deploymentReplicas: 3,
expectedReplicas: 3,
maxUnavailable: 10,
},
{
name: "max surge and max unavailable are specified",
stackReplicas: 3,
deploymentReplicas: 3,
expectedReplicas: 3,
maxSurge: 1,
maxUnavailable: 10,
},
} {
t.Run(tc.name, func(t *testing.T) {
var strategy *apps.DeploymentStrategy
if tc.maxUnavailable != 0 || tc.maxSurge != 0 {
strategy = &apps.DeploymentStrategy{
Type: apps.RollingUpdateDeploymentStrategyType,
RollingUpdate: &apps.RollingUpdateDeployment{},
}
if tc.maxUnavailable != 0 {
value := intstr.FromInt(tc.maxUnavailable)
strategy.RollingUpdate.MaxUnavailable = &value
}
if tc.maxSurge != 0 {
value := intstr.FromInt(tc.maxSurge)
strategy.RollingUpdate.MaxSurge = &value
}
}
c := &StackContainer{
Stack: &zv1.Stack{
ObjectMeta: testStackMeta,
Spec: zv1.StackSpec{
Strategy: strategy,
PodTemplate: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
Expand Down Expand Up @@ -561,6 +601,9 @@ func TestStackGenerateDeployment(t *testing.T) {
},
},
}
if strategy != nil {
expected.Spec.Strategy = *strategy
}
require.Equal(t, expected, deployment)
})
}
Expand Down
1 change: 1 addition & 0 deletions pkg/core/stackset.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func (ssc *StackSetContainer) NewStack() (*StackContainer, string) {
Service: service,
PodTemplate: stackset.Spec.StackTemplate.Spec.PodTemplate,
Autoscaler: stackset.Spec.StackTemplate.Spec.Autoscaler,
Strategy: stackset.Spec.StackTemplate.Spec.Strategy,
},
},
}, stackVersion
Expand Down
68 changes: 68 additions & 0 deletions pkg/core/stackset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,69 @@ func TestStackSetNewStack(t *testing.T) {
},
expectedStackName: "v1",
},
{
name: "stack needs to have the same update strategy",
stackset: &zv1.StackSet{
TypeMeta: metav1.TypeMeta{
APIVersion: APIVersion,
Kind: KindStackSet,
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
UID: "1234-abc-2134",
Labels: map[string]string{"custom": "label"},
},
Spec: zv1.StackSetSpec{
StackTemplate: zv1.StackTemplate{
Spec: zv1.StackSpecTemplate{
Version: "v1",
StackSpec: zv1.StackSpec{
Strategy: &apps.DeploymentStrategy{
Type: apps.RollingUpdateDeploymentStrategyType,
RollingUpdate: &apps.RollingUpdateDeployment{
MaxUnavailable: intstrptr("10%"),
MaxSurge: intstrptr("100%"),
},
},
},
},
},
},
},
stacks: map[types.UID]*StackContainer{},
expectedStack: &StackContainer{
Stack: &zv1.Stack{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-v1",
Namespace: "bar",
Labels: map[string]string{
StacksetHeritageLabelKey: "foo",
"custom": "label",
StackVersionLabelKey: "v1",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: APIVersion,
Kind: KindStackSet,
Name: "foo",
UID: "1234-abc-2134",
},
},
},
Spec: zv1.StackSpec{
Strategy: &apps.DeploymentStrategy{
Type: apps.RollingUpdateDeploymentStrategyType,
RollingUpdate: &apps.RollingUpdateDeployment{
MaxUnavailable: intstrptr("10%"),
MaxSurge: intstrptr("100%"),
},
},
},
},
},
expectedStackName: "v1",
},
} {
t.Run(tc.name, func(t *testing.T) {
stackset := &StackSetContainer{
Expand All @@ -240,6 +303,11 @@ func TestStackSetNewStack(t *testing.T) {
}
}

func intstrptr(value string) *intstr.IntOrString {
v := intstr.FromString(value)
return &v
}

func dummyStacksetContainer() *StackSetContainer {
return &StackSetContainer{
StackSet: &zv1.StackSet{
Expand Down

0 comments on commit b046043

Please sign in to comment.