Implement a rolling deployment strategy which piggybacks on the upstream
RollingUpdater functionality.
| ... | ... |
@@ -88,7 +88,7 @@ describe('', function() {
|
| 88 | 88 |
expect(element(by.cssContainingText("h2.service","frontend")).isPresent()).toBe(true);
|
| 89 | 89 |
expect(element(by.cssContainingText(".pod-template-image","Build: ruby-sample-build")).isPresent()).toBe(true);
|
| 90 | 90 |
expect(element(by.cssContainingText(".deployment-trigger","new image for test/origin-ruby-sample:latest")).isPresent()).toBe(true);
|
| 91 |
- expect(element.all(by.css(".pod-running")).count()).toEqual(2);
|
|
| 91 |
+ expect(element.all(by.css(".pod-running")).count()).toEqual(3);
|
|
| 92 | 92 |
}); |
| 93 | 93 |
}); |
| 94 | 94 |
|
| ... | ... |
@@ -158,42 +158,10 @@ |
| 158 | 158 |
"replicaSelector": {
|
| 159 | 159 |
"name": "frontend" |
| 160 | 160 |
}, |
| 161 |
- "replicas": 1 |
|
| 161 |
+ "replicas": 2 |
|
| 162 | 162 |
}, |
| 163 | 163 |
"strategy": {
|
| 164 |
- "type": "Recreate", |
|
| 165 |
- "recreateParams": {
|
|
| 166 |
- "pre": {
|
|
| 167 |
- "failurePolicy": "Abort", |
|
| 168 |
- "execNewPod": {
|
|
| 169 |
- "containerName": "ruby-helloworld", |
|
| 170 |
- "command": [ |
|
| 171 |
- "/bin/true" |
|
| 172 |
- ], |
|
| 173 |
- "env": [ |
|
| 174 |
- {
|
|
| 175 |
- "name": "CUSTOM_VAR1", |
|
| 176 |
- "value": "custom_value1" |
|
| 177 |
- } |
|
| 178 |
- ] |
|
| 179 |
- } |
|
| 180 |
- }, |
|
| 181 |
- "post": {
|
|
| 182 |
- "failurePolicy": "Ignore", |
|
| 183 |
- "execNewPod": {
|
|
| 184 |
- "containerName": "ruby-helloworld", |
|
| 185 |
- "command": [ |
|
| 186 |
- "/bin/false" |
|
| 187 |
- ], |
|
| 188 |
- "env": [ |
|
| 189 |
- {
|
|
| 190 |
- "name": "CUSTOM_VAR2", |
|
| 191 |
- "value": "custom_value2" |
|
| 192 |
- } |
|
| 193 |
- ] |
|
| 194 |
- } |
|
| 195 |
- } |
|
| 196 |
- } |
|
| 164 |
+ "type": "Rolling" |
|
| 197 | 165 |
} |
| 198 | 166 |
}, |
| 199 | 167 |
"triggers": [ |
| ... | ... |
@@ -105,9 +105,26 @@ func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, se |
| 105 | 105 |
}, |
| 106 | 106 |
func(j *deploy.DeploymentStrategy, c fuzz.Continue) {
|
| 107 | 107 |
c.FuzzNoCustom(j) |
| 108 |
- // TODO: we should not have to set defaults, instead we should be able to detect defaults were applied. |
|
| 109 |
- if len(j.Type) == 0 {
|
|
| 108 |
+ mkintp := func(i int) *int64 {
|
|
| 109 |
+ v := int64(i) |
|
| 110 |
+ return &v |
|
| 111 |
+ } |
|
| 112 |
+ switch c.Intn(3) {
|
|
| 113 |
+ case 0: |
|
| 114 |
+ // TODO: we should not have to set defaults, instead we should be able |
|
| 115 |
+ // to detect defaults were applied. |
|
| 116 |
+ j.Type = deploy.DeploymentStrategyTypeRolling |
|
| 117 |
+ j.RollingParams = &deploy.RollingDeploymentStrategyParams{
|
|
| 118 |
+ IntervalSeconds: mkintp(1), |
|
| 119 |
+ UpdatePeriodSeconds: mkintp(1), |
|
| 120 |
+ TimeoutSeconds: mkintp(120), |
|
| 121 |
+ } |
|
| 122 |
+ case 1: |
|
| 110 | 123 |
j.Type = deploy.DeploymentStrategyTypeRecreate |
| 124 |
+ j.RollingParams = nil |
|
| 125 |
+ case 2: |
|
| 126 |
+ j.Type = deploy.DeploymentStrategyTypeCustom |
|
| 127 |
+ j.RollingParams = nil |
|
| 111 | 128 |
} |
| 112 | 129 |
}, |
| 113 | 130 |
func(j *deploy.DeploymentCauseImageTrigger, c fuzz.Continue) {
|
| ... | ... |
@@ -14,7 +14,9 @@ import ( |
| 14 | 14 |
"github.com/openshift/origin/pkg/cmd/util" |
| 15 | 15 |
"github.com/openshift/origin/pkg/cmd/util/clientcmd" |
| 16 | 16 |
deployapi "github.com/openshift/origin/pkg/deploy/api" |
| 17 |
- strategy "github.com/openshift/origin/pkg/deploy/strategy/recreate" |
|
| 17 |
+ "github.com/openshift/origin/pkg/deploy/strategy" |
|
| 18 |
+ "github.com/openshift/origin/pkg/deploy/strategy/recreate" |
|
| 19 |
+ "github.com/openshift/origin/pkg/deploy/strategy/rolling" |
|
| 18 | 20 |
deployutil "github.com/openshift/origin/pkg/deploy/util" |
| 19 | 21 |
"github.com/openshift/origin/pkg/version" |
| 20 | 22 |
) |
| ... | ... |
@@ -78,21 +80,35 @@ func NewCommandDeployer(name string) *cobra.Command {
|
| 78 | 78 |
|
| 79 | 79 |
// deploy executes a deployment strategy. |
| 80 | 80 |
func deploy(kClient kclient.Interface, namespace, deploymentName string) error {
|
| 81 |
- newDeployment, oldDeployments, err := getDeployerContext(&realReplicationControllerGetter{kClient}, namespace, deploymentName)
|
|
| 82 |
- |
|
| 81 |
+ deployment, oldDeployments, err := getDeployerContext(&realReplicationControllerGetter{kClient}, namespace, deploymentName)
|
|
| 83 | 82 |
if err != nil {
|
| 84 | 83 |
return err |
| 85 | 84 |
} |
| 86 | 85 |
|
| 87 |
- // TODO: Choose a strategy based on some input |
|
| 88 |
- strategy := strategy.NewRecreateDeploymentStrategy(kClient, latest.Codec) |
|
| 89 |
- return strategy.Deploy(newDeployment, oldDeployments) |
|
| 86 |
+ config, err := deployutil.DecodeDeploymentConfig(deployment, latest.Codec) |
|
| 87 |
+ if err != nil {
|
|
| 88 |
+ return fmt.Errorf("couldn't decode DeploymentConfig from deployment %s/%s: %v", deployment.Namespace, deployment.Name, err)
|
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ var strategy strategy.DeploymentStrategy |
|
| 92 |
+ |
|
| 93 |
+ switch config.Template.Strategy.Type {
|
|
| 94 |
+ case deployapi.DeploymentStrategyTypeRecreate: |
|
| 95 |
+ strategy = recreate.NewRecreateDeploymentStrategy(kClient, latest.Codec) |
|
| 96 |
+ case deployapi.DeploymentStrategyTypeRolling: |
|
| 97 |
+ recreate := recreate.NewRecreateDeploymentStrategy(kClient, latest.Codec) |
|
| 98 |
+ strategy = rolling.NewRollingDeploymentStrategy(deployment.Namespace, kClient, latest.Codec, recreate) |
|
| 99 |
+ default: |
|
| 100 |
+ return fmt.Errorf("unsupported strategy type: %s", config.Template.Strategy.Type)
|
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ return strategy.Deploy(deployment, oldDeployments) |
|
| 90 | 104 |
} |
| 91 | 105 |
|
| 92 | 106 |
// getDeployerContext finds the target deployment and any deployments it considers to be prior to the |
| 93 | 107 |
// target deployment. Only deployments whose LatestVersion is less than the target deployment are |
| 94 | 108 |
// considered to be prior. |
| 95 |
-func getDeployerContext(controllerGetter replicationControllerGetter, namespace, deploymentName string) (*kapi.ReplicationController, []kapi.ObjectReference, error) {
|
|
| 109 |
+func getDeployerContext(controllerGetter replicationControllerGetter, namespace, deploymentName string) (*kapi.ReplicationController, []*kapi.ReplicationController, error) {
|
|
| 96 | 110 |
var err error |
| 97 | 111 |
var newDeployment *kapi.ReplicationController |
| 98 | 112 |
var newConfig *deployapi.DeploymentConfig |
| ... | ... |
@@ -112,14 +128,14 @@ func getDeployerContext(controllerGetter replicationControllerGetter, namespace, |
| 112 | 112 |
// encoded DeploymentConfigs to the new one by LatestVersion. Treat a failure to interpret a given |
| 113 | 113 |
// old deployment as a fatal error to prevent overlapping deployments. |
| 114 | 114 |
var allControllers *kapi.ReplicationControllerList |
| 115 |
- oldDeployments := []kapi.ObjectReference{}
|
|
| 115 |
+ oldDeployments := []*kapi.ReplicationController{}
|
|
| 116 | 116 |
|
| 117 | 117 |
if allControllers, err = controllerGetter.List(newDeployment.Namespace, labels.Everything()); err != nil {
|
| 118 | 118 |
return nil, nil, fmt.Errorf("Unable to get list replication controllers in deployment namespace %s: %v", newDeployment.Namespace, err)
|
| 119 | 119 |
} |
| 120 | 120 |
|
| 121 | 121 |
glog.Infof("Inspecting %d potential prior deployments", len(allControllers.Items))
|
| 122 |
- for _, controller := range allControllers.Items {
|
|
| 122 |
+ for i, controller := range allControllers.Items {
|
|
| 123 | 123 |
if configName, hasConfigName := controller.Annotations[deployapi.DeploymentConfigAnnotation]; !hasConfigName {
|
| 124 | 124 |
glog.Infof("Disregarding replicationController %s (not a deployment)", controller.Name)
|
| 125 | 125 |
continue |
| ... | ... |
@@ -135,10 +151,7 @@ func getDeployerContext(controllerGetter replicationControllerGetter, namespace, |
| 135 | 135 |
|
| 136 | 136 |
if oldVersion < newConfig.LatestVersion {
|
| 137 | 137 |
glog.Infof("Marking deployment %s as a prior deployment", controller.Name)
|
| 138 |
- oldDeployments = append(oldDeployments, kapi.ObjectReference{
|
|
| 139 |
- Namespace: controller.Namespace, |
|
| 140 |
- Name: controller.Name, |
|
| 141 |
- }) |
|
| 138 |
+ oldDeployments = append(oldDeployments, &allControllers.Items[i]) |
|
| 142 | 139 |
} else {
|
| 143 | 140 |
glog.Infof("Disregarding deployment %s (same as or newer than target)", controller.Name)
|
| 144 | 141 |
} |
| ... | ... |
@@ -96,13 +96,13 @@ func TestGetDeploymentContextNoPriorDeployments(t *testing.T) {
|
| 96 | 96 |
func TestGetDeploymentContextWithPriorDeployments(t *testing.T) {
|
| 97 | 97 |
getter := &testReplicationControllerGetter{
|
| 98 | 98 |
getFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
|
| 99 |
- deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(2), kapi.Codec) |
|
| 99 |
+ deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(3), kapi.Codec) |
|
| 100 | 100 |
return deployment, nil |
| 101 | 101 |
}, |
| 102 | 102 |
listFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
|
| 103 | 103 |
deployment1, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) |
| 104 | 104 |
deployment2, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(2), kapi.Codec) |
| 105 |
- deployment3, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(3), kapi.Codec) |
|
| 105 |
+ deployment3, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(4), kapi.Codec) |
|
| 106 | 106 |
deployment4, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) |
| 107 | 107 |
deployment4.Annotations[deployapi.DeploymentConfigAnnotation] = "another-config" |
| 108 | 108 |
return &kapi.ReplicationControllerList{
|
| ... | ... |
@@ -131,8 +131,30 @@ func TestGetDeploymentContextWithPriorDeployments(t *testing.T) {
|
| 131 | 131 |
t.Fatal("expected non-nil oldDeployments")
|
| 132 | 132 |
} |
| 133 | 133 |
|
| 134 |
- if e, a := 1, len(oldDeployments); e != a {
|
|
| 135 |
- t.Fatalf("expected oldDeployments with size %d, got %d: %#v", e, a, oldDeployments)
|
|
| 134 |
+ expected := []string{"config-1", "config-2"}
|
|
| 135 |
+ for _, e := range expected {
|
|
| 136 |
+ found := false |
|
| 137 |
+ for _, d := range oldDeployments {
|
|
| 138 |
+ if d.Name == e {
|
|
| 139 |
+ found = true |
|
| 140 |
+ break |
|
| 141 |
+ } |
|
| 142 |
+ } |
|
| 143 |
+ if !found {
|
|
| 144 |
+ t.Errorf("expected to find old deployment %s", e)
|
|
| 145 |
+ } |
|
| 146 |
+ } |
|
| 147 |
+ for _, d := range oldDeployments {
|
|
| 148 |
+ ok := false |
|
| 149 |
+ for _, e := range expected {
|
|
| 150 |
+ if d.Name == e {
|
|
| 151 |
+ ok = true |
|
| 152 |
+ break |
|
| 153 |
+ } |
|
| 154 |
+ } |
|
| 155 |
+ if !ok {
|
|
| 156 |
+ t.Errorf("unexpected old deployment %s", d.Name)
|
|
| 157 |
+ } |
|
| 136 | 158 |
} |
| 137 | 159 |
} |
| 138 | 160 |
|
| ... | ... |
@@ -837,10 +837,10 @@ func (c *MasterConfig) RunDeploymentController() error {
|
| 837 | 837 |
env = append(env, clientcmd.EnvVarsFromConfig(c.DeployerClientConfig())...) |
| 838 | 838 |
|
| 839 | 839 |
factory := deploycontroller.DeploymentControllerFactory{
|
| 840 |
- KubeClient: kclient, |
|
| 841 |
- Codec: latest.Codec, |
|
| 842 |
- Environment: env, |
|
| 843 |
- RecreateStrategyImage: c.ImageFor("deployer"),
|
|
| 840 |
+ KubeClient: kclient, |
|
| 841 |
+ Codec: latest.Codec, |
|
| 842 |
+ Environment: env, |
|
| 843 |
+ DeployerImage: c.ImageFor("deployer"),
|
|
| 844 | 844 |
} |
| 845 | 845 |
|
| 846 | 846 |
controller := factory.Create() |
| ... | ... |
@@ -54,6 +54,8 @@ type DeploymentStrategy struct {
|
| 54 | 54 |
CustomParams *CustomDeploymentStrategyParams `json:"customParams,omitempty"` |
| 55 | 55 |
// RecreateParams are the input to the Recreate deployment strategy. |
| 56 | 56 |
RecreateParams *RecreateDeploymentStrategyParams `json:"recreateParams,omitempty"` |
| 57 |
+ // RollingParams are the input to the Rolling deployment strategy. |
|
| 58 |
+ RollingParams *RollingDeploymentStrategyParams `json:"rollingParams,omitempty"` |
|
| 57 | 59 |
// Compute resource requirements to execute the deployment |
| 58 | 60 |
Resources kapi.ResourceRequirements `json:"resources,omitempty"` |
| 59 | 61 |
} |
| ... | ... |
@@ -66,6 +68,8 @@ const ( |
| 66 | 66 |
DeploymentStrategyTypeRecreate DeploymentStrategyType = "Recreate" |
| 67 | 67 |
// DeploymentStrategyTypeCustom is a user defined strategy. |
| 68 | 68 |
DeploymentStrategyTypeCustom DeploymentStrategyType = "Custom" |
| 69 |
+ // DeploymentStrategyTypeRolling uses the Kubernetes RollingUpdater. |
|
| 70 |
+ DeploymentStrategyTypeRolling DeploymentStrategyType = "Rolling" |
|
| 69 | 71 |
) |
| 70 | 72 |
|
| 71 | 73 |
// CustomDeploymentStrategyParams are the input to the Custom deployment strategy. |
| ... | ... |
@@ -123,6 +127,20 @@ type ExecNewPodHook struct {
|
| 123 | 123 |
ContainerName string `json:"containerName"` |
| 124 | 124 |
} |
| 125 | 125 |
|
| 126 |
+// RollingDeploymentStrategyParams are the input to the Rolling deployment |
|
| 127 |
+// strategy. |
|
| 128 |
+type RollingDeploymentStrategyParams struct {
|
|
| 129 |
+ // UpdatePeriodSeconds is the time to wait between individual pod updates. |
|
| 130 |
+ // If the value is nil, a default will be used. |
|
| 131 |
+ UpdatePeriodSeconds *int64 `json:"updatePeriodSeconds,omitempty" description:"the time to wait between individual pod updates"` |
|
| 132 |
+ // IntervalSeconds is the time to wait between polling deployment status |
|
| 133 |
+ // after update. If the value is nil, a default will be used. |
|
| 134 |
+ IntervalSeconds *int64 `json:"intervalSeconds,omitempty" description:"the time to wait between polling deployment status after update"` |
|
| 135 |
+ // TimeoutSeconds is the time to wait for updates before giving up. If the |
|
| 136 |
+ // value is nil, a default will be used. |
|
| 137 |
+ TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" description:"the time to wait for updates before giving up"` |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 126 | 140 |
// DeploymentList is a collection of deployments. |
| 127 | 141 |
// DEPRECATED: Like Deployment, this is no longer used. |
| 128 | 142 |
type DeploymentList struct {
|
| ... | ... |
@@ -19,6 +19,9 @@ func init() {
|
| 19 | 19 |
if err := s.Convert(&in.RecreateParams, &out.RecreateParams, 0); err != nil {
|
| 20 | 20 |
return err |
| 21 | 21 |
} |
| 22 |
+ if err := s.Convert(&in.RollingParams, &out.RollingParams, 0); err != nil {
|
|
| 23 |
+ return err |
|
| 24 |
+ } |
|
| 22 | 25 |
if err := s.Convert(&in.Resources, &out.Resources, 0); err != nil {
|
| 23 | 26 |
return err |
| 24 | 27 |
} |
| ... | ... |
@@ -34,6 +37,9 @@ func init() {
|
| 34 | 34 |
if err := s.Convert(&in.RecreateParams, &out.RecreateParams, 0); err != nil {
|
| 35 | 35 |
return err |
| 36 | 36 |
} |
| 37 |
+ if err := s.Convert(&in.RollingParams, &out.RollingParams, 0); err != nil {
|
|
| 38 |
+ return err |
|
| 39 |
+ } |
|
| 37 | 40 |
if err := s.Convert(&in.Resources, &out.Resources, 0); err != nil {
|
| 38 | 41 |
return err |
| 39 | 42 |
} |
| 40 | 43 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,46 @@ |
| 0 |
+package v1beta1 |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 4 |
+ |
|
| 5 |
+ deployapi "github.com/openshift/origin/pkg/deploy/api" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+func init() {
|
|
| 9 |
+ mkintp := func(i int) *int64 {
|
|
| 10 |
+ v := int64(i) |
|
| 11 |
+ return &v |
|
| 12 |
+ } |
|
| 13 |
+ |
|
| 14 |
+ err := api.Scheme.AddDefaultingFuncs( |
|
| 15 |
+ func(obj *deployapi.DeploymentStrategy) {
|
|
| 16 |
+ if len(obj.Type) == 0 {
|
|
| 17 |
+ obj.Type = deployapi.DeploymentStrategyTypeRolling |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ if obj.Type == deployapi.DeploymentStrategyTypeRolling && obj.RollingParams == nil {
|
|
| 21 |
+ obj.RollingParams = &deployapi.RollingDeploymentStrategyParams{
|
|
| 22 |
+ IntervalSeconds: mkintp(1), |
|
| 23 |
+ UpdatePeriodSeconds: mkintp(1), |
|
| 24 |
+ TimeoutSeconds: mkintp(120), |
|
| 25 |
+ } |
|
| 26 |
+ } |
|
| 27 |
+ }, |
|
| 28 |
+ func(obj *deployapi.RollingDeploymentStrategyParams) {
|
|
| 29 |
+ if obj.IntervalSeconds == nil {
|
|
| 30 |
+ obj.IntervalSeconds = mkintp(1) |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ if obj.UpdatePeriodSeconds == nil {
|
|
| 34 |
+ obj.UpdatePeriodSeconds = mkintp(1) |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ if obj.TimeoutSeconds == nil {
|
|
| 38 |
+ obj.TimeoutSeconds = mkintp(120) |
|
| 39 |
+ } |
|
| 40 |
+ }, |
|
| 41 |
+ ) |
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ panic(err) |
|
| 44 |
+ } |
|
| 45 |
+} |
| 0 | 46 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,53 @@ |
| 0 |
+package v1beta1_test |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 7 |
+ |
|
| 8 |
+ newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 9 |
+ current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" |
|
| 10 |
+ deployapi "github.com/openshift/origin/pkg/deploy/api" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
|
|
| 14 |
+ data, err := current.Codec.Encode(obj) |
|
| 15 |
+ if err != nil {
|
|
| 16 |
+ t.Errorf("%v\n %#v", err, obj)
|
|
| 17 |
+ return nil |
|
| 18 |
+ } |
|
| 19 |
+ obj2, err := newer.Codec.Decode(data) |
|
| 20 |
+ if err != nil {
|
|
| 21 |
+ t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj)
|
|
| 22 |
+ return nil |
|
| 23 |
+ } |
|
| 24 |
+ obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) |
|
| 25 |
+ err = newer.Scheme.Convert(obj2, obj3) |
|
| 26 |
+ if err != nil {
|
|
| 27 |
+ t.Errorf("%v\nSource: %#v", err, obj2)
|
|
| 28 |
+ return nil |
|
| 29 |
+ } |
|
| 30 |
+ return obj3 |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func TestDefaults_rollingParams(t *testing.T) {
|
|
| 34 |
+ c := &deployapi.DeploymentConfig{
|
|
| 35 |
+ Template: deployapi.DeploymentTemplate{},
|
|
| 36 |
+ } |
|
| 37 |
+ o := roundTrip(t, runtime.Object(c)) |
|
| 38 |
+ config := o.(*deployapi.DeploymentConfig) |
|
| 39 |
+ strat := config.Template.Strategy |
|
| 40 |
+ if e, a := deployapi.DeploymentStrategyTypeRolling, strat.Type; e != a {
|
|
| 41 |
+ t.Errorf("expected strategy type %s, got %s", e, a)
|
|
| 42 |
+ } |
|
| 43 |
+ if e, a := int64(1), *strat.RollingParams.UpdatePeriodSeconds; e != a {
|
|
| 44 |
+ t.Errorf("expected UpdatePeriodSeconds %d, got %d", e, a)
|
|
| 45 |
+ } |
|
| 46 |
+ if e, a := int64(1), *strat.RollingParams.IntervalSeconds; e != a {
|
|
| 47 |
+ t.Errorf("expected IntervalSeconds %d, got %d", e, a)
|
|
| 48 |
+ } |
|
| 49 |
+ if e, a := int64(120), *strat.RollingParams.TimeoutSeconds; e != a {
|
|
| 50 |
+ t.Errorf("expected UpdatePeriodSeconds %d, got %d", e, a)
|
|
| 51 |
+ } |
|
| 52 |
+} |
| ... | ... |
@@ -55,6 +55,8 @@ type DeploymentStrategy struct {
|
| 55 | 55 |
CustomParams *CustomDeploymentStrategyParams `json:"customParams,omitempty"` |
| 56 | 56 |
// RecreateParams are the input to the Recreate deployment strategy. |
| 57 | 57 |
RecreateParams *RecreateDeploymentStrategyParams `json:"recreateParams,omitempty"` |
| 58 |
+ // RollingParams are the input to the Rolling deployment strategy. |
|
| 59 |
+ RollingParams *RollingDeploymentStrategyParams `json:"rollingParams,omitempty"` |
|
| 58 | 60 |
// Compute resource requirements to execute the deployment |
| 59 | 61 |
Resources kapi.ResourceRequirements `json:"resources,omitempty"` |
| 60 | 62 |
} |
| ... | ... |
@@ -67,6 +69,8 @@ const ( |
| 67 | 67 |
DeploymentStrategyTypeRecreate DeploymentStrategyType = "Recreate" |
| 68 | 68 |
// DeploymentStrategyTypeCustom is a user defined strategy. |
| 69 | 69 |
DeploymentStrategyTypeCustom DeploymentStrategyType = "Custom" |
| 70 |
+ // DeploymentStrategyTypeRolling uses the Kubernetes RollingUpdater. |
|
| 71 |
+ DeploymentStrategyTypeRolling DeploymentStrategyType = "Rolling" |
|
| 70 | 72 |
) |
| 71 | 73 |
|
| 72 | 74 |
// CustomParams are the input to the Custom deployment strategy. |
| ... | ... |
@@ -124,6 +128,20 @@ type ExecNewPodHook struct {
|
| 124 | 124 |
ContainerName string `json:"containerName"` |
| 125 | 125 |
} |
| 126 | 126 |
|
| 127 |
+// RollingDeploymentStrategyParams are the input to the Rolling deployment |
|
| 128 |
+// strategy. |
|
| 129 |
+type RollingDeploymentStrategyParams struct {
|
|
| 130 |
+ // UpdatePeriodSeconds is the time to wait between individual pod updates. |
|
| 131 |
+ // If the value is nil, a default will be used. |
|
| 132 |
+ UpdatePeriodSeconds *int64 `json:"updatePeriodSeconds,omitempty" description:"the time to wait between individual pod updates"` |
|
| 133 |
+ // IntervalSeconds is the time to wait between polling deployment status |
|
| 134 |
+ // after update. If the value is nil, a default will be used. |
|
| 135 |
+ IntervalSeconds *int64 `json:"intervalSeconds,omitempty" description:"the time to wait between polling deployment status after update"` |
|
| 136 |
+ // TimeoutSeconds is the time to wait for updates before giving up. If the |
|
| 137 |
+ // value is nil, a default will be used. |
|
| 138 |
+ TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" description:"the time to wait for updates before giving up"` |
|
| 139 |
+} |
|
| 140 |
+ |
|
| 127 | 141 |
// A DeploymentList is a collection of deployments. |
| 128 | 142 |
// DEPRECATED: Like Deployment, this is no longer used. |
| 129 | 143 |
type DeploymentList struct {
|
| ... | ... |
@@ -93,6 +93,9 @@ func init() {
|
| 93 | 93 |
if err := s.Convert(&in.RecreateParams, &out.RecreateParams, 0); err != nil {
|
| 94 | 94 |
return err |
| 95 | 95 |
} |
| 96 |
+ if err := s.Convert(&in.RollingParams, &out.RollingParams, 0); err != nil {
|
|
| 97 |
+ return err |
|
| 98 |
+ } |
|
| 96 | 99 |
if err := s.Convert(&in.Resources, &out.Resources, 0); err != nil {
|
| 97 | 100 |
return err |
| 98 | 101 |
} |
| ... | ... |
@@ -108,6 +111,9 @@ func init() {
|
| 108 | 108 |
if err := s.Convert(&in.RecreateParams, &out.RecreateParams, 0); err != nil {
|
| 109 | 109 |
return err |
| 110 | 110 |
} |
| 111 |
+ if err := s.Convert(&in.RollingParams, &out.RollingParams, 0); err != nil {
|
|
| 112 |
+ return err |
|
| 113 |
+ } |
|
| 111 | 114 |
if err := s.Convert(&in.Resources, &out.Resources, 0); err != nil {
|
| 112 | 115 |
return err |
| 113 | 116 |
} |
| 114 | 117 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,46 @@ |
| 0 |
+package v1beta3 |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 4 |
+ |
|
| 5 |
+ deployapi "github.com/openshift/origin/pkg/deploy/api" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+func init() {
|
|
| 9 |
+ mkintp := func(i int) *int64 {
|
|
| 10 |
+ v := int64(i) |
|
| 11 |
+ return &v |
|
| 12 |
+ } |
|
| 13 |
+ |
|
| 14 |
+ err := api.Scheme.AddDefaultingFuncs( |
|
| 15 |
+ func(obj *deployapi.DeploymentStrategy) {
|
|
| 16 |
+ if len(obj.Type) == 0 {
|
|
| 17 |
+ obj.Type = deployapi.DeploymentStrategyTypeRolling |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ if obj.Type == deployapi.DeploymentStrategyTypeRolling && obj.RollingParams == nil {
|
|
| 21 |
+ obj.RollingParams = &deployapi.RollingDeploymentStrategyParams{
|
|
| 22 |
+ IntervalSeconds: mkintp(1), |
|
| 23 |
+ UpdatePeriodSeconds: mkintp(1), |
|
| 24 |
+ TimeoutSeconds: mkintp(120), |
|
| 25 |
+ } |
|
| 26 |
+ } |
|
| 27 |
+ }, |
|
| 28 |
+ func(obj *deployapi.RollingDeploymentStrategyParams) {
|
|
| 29 |
+ if obj.IntervalSeconds == nil {
|
|
| 30 |
+ obj.IntervalSeconds = mkintp(1) |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ if obj.UpdatePeriodSeconds == nil {
|
|
| 34 |
+ obj.UpdatePeriodSeconds = mkintp(1) |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ if obj.TimeoutSeconds == nil {
|
|
| 38 |
+ obj.TimeoutSeconds = mkintp(120) |
|
| 39 |
+ } |
|
| 40 |
+ }, |
|
| 41 |
+ ) |
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ panic(err) |
|
| 44 |
+ } |
|
| 45 |
+} |
| 0 | 46 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,53 @@ |
| 0 |
+package v1beta3_test |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "reflect" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 7 |
+ |
|
| 8 |
+ newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 9 |
+ current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3" |
|
| 10 |
+ deployapi "github.com/openshift/origin/pkg/deploy/api" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
|
|
| 14 |
+ data, err := current.Codec.Encode(obj) |
|
| 15 |
+ if err != nil {
|
|
| 16 |
+ t.Errorf("%v\n %#v", err, obj)
|
|
| 17 |
+ return nil |
|
| 18 |
+ } |
|
| 19 |
+ obj2, err := newer.Codec.Decode(data) |
|
| 20 |
+ if err != nil {
|
|
| 21 |
+ t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj)
|
|
| 22 |
+ return nil |
|
| 23 |
+ } |
|
| 24 |
+ obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) |
|
| 25 |
+ err = newer.Scheme.Convert(obj2, obj3) |
|
| 26 |
+ if err != nil {
|
|
| 27 |
+ t.Errorf("%v\nSource: %#v", err, obj2)
|
|
| 28 |
+ return nil |
|
| 29 |
+ } |
|
| 30 |
+ return obj3 |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func TestDefaults_rollingParams(t *testing.T) {
|
|
| 34 |
+ c := &deployapi.DeploymentConfig{
|
|
| 35 |
+ Template: deployapi.DeploymentTemplate{},
|
|
| 36 |
+ } |
|
| 37 |
+ o := roundTrip(t, runtime.Object(c)) |
|
| 38 |
+ config := o.(*deployapi.DeploymentConfig) |
|
| 39 |
+ strat := config.Template.Strategy |
|
| 40 |
+ if e, a := deployapi.DeploymentStrategyTypeRolling, strat.Type; e != a {
|
|
| 41 |
+ t.Errorf("expected strategy type %s, got %s", e, a)
|
|
| 42 |
+ } |
|
| 43 |
+ if e, a := int64(1), *strat.RollingParams.UpdatePeriodSeconds; e != a {
|
|
| 44 |
+ t.Errorf("expected UpdatePeriodSeconds %d, got %d", e, a)
|
|
| 45 |
+ } |
|
| 46 |
+ if e, a := int64(1), *strat.RollingParams.IntervalSeconds; e != a {
|
|
| 47 |
+ t.Errorf("expected IntervalSeconds %d, got %d", e, a)
|
|
| 48 |
+ } |
|
| 49 |
+ if e, a := int64(120), *strat.RollingParams.TimeoutSeconds; e != a {
|
|
| 50 |
+ t.Errorf("expected UpdatePeriodSeconds %d, got %d", e, a)
|
|
| 51 |
+ } |
|
| 52 |
+} |
| ... | ... |
@@ -30,6 +30,8 @@ type DeploymentStrategy struct {
|
| 30 | 30 |
CustomParams *CustomDeploymentStrategyParams `json:"customParams,omitempty"` |
| 31 | 31 |
// RecreateParams are the input to the Recreate deployment strategy. |
| 32 | 32 |
RecreateParams *RecreateDeploymentStrategyParams `json:"recreateParams,omitempty"` |
| 33 |
+ // RollingParams are the input to the Rolling deployment strategy. |
|
| 34 |
+ RollingParams *RollingDeploymentStrategyParams `json:"rollingParams,omitempty"` |
|
| 33 | 35 |
// Compute resource requirements to execute the deployment |
| 34 | 36 |
Resources kapi.ResourceRequirements `json:"resources,omitempty"` |
| 35 | 37 |
} |
| ... | ... |
@@ -42,6 +44,8 @@ const ( |
| 42 | 42 |
DeploymentStrategyTypeRecreate DeploymentStrategyType = "Recreate" |
| 43 | 43 |
// DeploymentStrategyTypeCustom is a user defined strategy. |
| 44 | 44 |
DeploymentStrategyTypeCustom DeploymentStrategyType = "Custom" |
| 45 |
+ // DeploymentStrategyTypeRolling uses the Kubernetes RollingUpdater. |
|
| 46 |
+ DeploymentStrategyTypeRolling DeploymentStrategyType = "Rolling" |
|
| 45 | 47 |
) |
| 46 | 48 |
|
| 47 | 49 |
// CustomParams are the input to the Custom deployment strategy. |
| ... | ... |
@@ -99,6 +103,20 @@ type ExecNewPodHook struct {
|
| 99 | 99 |
ContainerName string `json:"containerName"` |
| 100 | 100 |
} |
| 101 | 101 |
|
| 102 |
+// RollingDeploymentStrategyParams are the input to the Rolling deployment |
|
| 103 |
+// strategy. |
|
| 104 |
+type RollingDeploymentStrategyParams struct {
|
|
| 105 |
+ // UpdatePeriodSeconds is the time to wait between individual pod updates. |
|
| 106 |
+ // If the value is nil, a default will be used. |
|
| 107 |
+ UpdatePeriodSeconds *int64 `json:"updatePeriodSeconds,omitempty" description:"the time to wait between individual pod updates"` |
|
| 108 |
+ // IntervalSeconds is the time to wait between polling deployment status |
|
| 109 |
+ // after update. If the value is nil, a default will be used. |
|
| 110 |
+ IntervalSeconds *int64 `json:"intervalSeconds,omitempty" description:"the time to wait between polling deployment status after update"` |
|
| 111 |
+ // TimeoutSeconds is the time to wait for updates before giving up. If the |
|
| 112 |
+ // value is nil, a default will be used. |
|
| 113 |
+ TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" description:"the time to wait for updates before giving up"` |
|
| 114 |
+} |
|
| 115 |
+ |
|
| 102 | 116 |
// These constants represent keys used for correlating objects related to deployments. |
| 103 | 117 |
const ( |
| 104 | 118 |
// DeploymentConfigAnnotation is an annotation name used to correlate a deployment with the |
| ... | ... |
@@ -82,6 +82,12 @@ func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy) fielderr |
| 82 | 82 |
if strategy.RecreateParams != nil {
|
| 83 | 83 |
errs = append(errs, validateRecreateParams(strategy.RecreateParams).Prefix("recreateParams")...)
|
| 84 | 84 |
} |
| 85 |
+ case deployapi.DeploymentStrategyTypeRolling: |
|
| 86 |
+ if strategy.RollingParams == nil {
|
|
| 87 |
+ errs = append(errs, fielderrors.NewFieldRequired("rollingParams"))
|
|
| 88 |
+ } else {
|
|
| 89 |
+ errs = append(errs, validateRollingParams(strategy.RollingParams).Prefix("rollingParams")...)
|
|
| 90 |
+ } |
|
| 85 | 91 |
case deployapi.DeploymentStrategyTypeCustom: |
| 86 | 92 |
if strategy.CustomParams == nil {
|
| 87 | 93 |
errs = append(errs, fielderrors.NewFieldRequired("customParams"))
|
| ... | ... |
@@ -168,6 +174,24 @@ func validateEnv(vars []kapi.EnvVar) fielderrors.ValidationErrorList {
|
| 168 | 168 |
return allErrs |
| 169 | 169 |
} |
| 170 | 170 |
|
| 171 |
+func validateRollingParams(params *deployapi.RollingDeploymentStrategyParams) fielderrors.ValidationErrorList {
|
|
| 172 |
+ errs := fielderrors.ValidationErrorList{}
|
|
| 173 |
+ |
|
| 174 |
+ if params.IntervalSeconds != nil && *params.IntervalSeconds < 1 {
|
|
| 175 |
+ errs = append(errs, fielderrors.NewFieldInvalid("intervalSeconds", *params.IntervalSeconds, "must be >0"))
|
|
| 176 |
+ } |
|
| 177 |
+ |
|
| 178 |
+ if params.UpdatePeriodSeconds != nil && *params.UpdatePeriodSeconds < 1 {
|
|
| 179 |
+ errs = append(errs, fielderrors.NewFieldInvalid("updatePeriodSeconds", *params.UpdatePeriodSeconds, "must be >0"))
|
|
| 180 |
+ } |
|
| 181 |
+ |
|
| 182 |
+ if params.TimeoutSeconds != nil && *params.TimeoutSeconds < 1 {
|
|
| 183 |
+ errs = append(errs, fielderrors.NewFieldInvalid("timeoutSeconds", *params.TimeoutSeconds, "must be >0"))
|
|
| 184 |
+ } |
|
| 185 |
+ |
|
| 186 |
+ return errs |
|
| 187 |
+} |
|
| 188 |
+ |
|
| 171 | 189 |
func validateTrigger(trigger *deployapi.DeploymentTriggerPolicy) fielderrors.ValidationErrorList {
|
| 172 | 190 |
errs := fielderrors.ValidationErrorList{}
|
| 173 | 191 |
|
| ... | ... |
@@ -328,6 +328,63 @@ func TestValidateDeploymentConfigMissingFields(t *testing.T) {
|
| 328 | 328 |
fielderrors.ValidationErrorTypeRequired, |
| 329 | 329 |
"template.strategy.recreateParams.pre.execNewPod.containerName", |
| 330 | 330 |
}, |
| 331 |
+ "invalid template.strategy.rollingParams.intervalSeconds": {
|
|
| 332 |
+ api.DeploymentConfig{
|
|
| 333 |
+ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"},
|
|
| 334 |
+ Triggers: manualTrigger(), |
|
| 335 |
+ Template: api.DeploymentTemplate{
|
|
| 336 |
+ Strategy: api.DeploymentStrategy{
|
|
| 337 |
+ Type: api.DeploymentStrategyTypeRolling, |
|
| 338 |
+ RollingParams: &api.RollingDeploymentStrategyParams{
|
|
| 339 |
+ IntervalSeconds: mkintp(-20), |
|
| 340 |
+ UpdatePeriodSeconds: mkintp(1), |
|
| 341 |
+ TimeoutSeconds: mkintp(1), |
|
| 342 |
+ }, |
|
| 343 |
+ }, |
|
| 344 |
+ ControllerTemplate: test.OkControllerTemplate(), |
|
| 345 |
+ }, |
|
| 346 |
+ }, |
|
| 347 |
+ fielderrors.ValidationErrorTypeInvalid, |
|
| 348 |
+ "template.strategy.rollingParams.intervalSeconds", |
|
| 349 |
+ }, |
|
| 350 |
+ "invalid template.strategy.rollingParams.updatePeriodSeconds": {
|
|
| 351 |
+ api.DeploymentConfig{
|
|
| 352 |
+ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"},
|
|
| 353 |
+ Triggers: manualTrigger(), |
|
| 354 |
+ Template: api.DeploymentTemplate{
|
|
| 355 |
+ Strategy: api.DeploymentStrategy{
|
|
| 356 |
+ Type: api.DeploymentStrategyTypeRolling, |
|
| 357 |
+ RollingParams: &api.RollingDeploymentStrategyParams{
|
|
| 358 |
+ IntervalSeconds: mkintp(1), |
|
| 359 |
+ UpdatePeriodSeconds: mkintp(-20), |
|
| 360 |
+ TimeoutSeconds: mkintp(1), |
|
| 361 |
+ }, |
|
| 362 |
+ }, |
|
| 363 |
+ ControllerTemplate: test.OkControllerTemplate(), |
|
| 364 |
+ }, |
|
| 365 |
+ }, |
|
| 366 |
+ fielderrors.ValidationErrorTypeInvalid, |
|
| 367 |
+ "template.strategy.rollingParams.updatePeriodSeconds", |
|
| 368 |
+ }, |
|
| 369 |
+ "invalid template.strategy.rollingParams.timeoutSeconds": {
|
|
| 370 |
+ api.DeploymentConfig{
|
|
| 371 |
+ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"},
|
|
| 372 |
+ Triggers: manualTrigger(), |
|
| 373 |
+ Template: api.DeploymentTemplate{
|
|
| 374 |
+ Strategy: api.DeploymentStrategy{
|
|
| 375 |
+ Type: api.DeploymentStrategyTypeRolling, |
|
| 376 |
+ RollingParams: &api.RollingDeploymentStrategyParams{
|
|
| 377 |
+ IntervalSeconds: mkintp(1), |
|
| 378 |
+ UpdatePeriodSeconds: mkintp(1), |
|
| 379 |
+ TimeoutSeconds: mkintp(-20), |
|
| 380 |
+ }, |
|
| 381 |
+ }, |
|
| 382 |
+ ControllerTemplate: test.OkControllerTemplate(), |
|
| 383 |
+ }, |
|
| 384 |
+ }, |
|
| 385 |
+ fielderrors.ValidationErrorTypeInvalid, |
|
| 386 |
+ "template.strategy.rollingParams.timeoutSeconds", |
|
| 387 |
+ }, |
|
| 331 | 388 |
} |
| 332 | 389 |
|
| 333 | 390 |
for k, v := range errorCases {
|
| ... | ... |
@@ -464,3 +521,8 @@ func TestValidateDeploymentConfigImageRepositorySupported(t *testing.T) {
|
| 464 | 464 |
t.Errorf("expected imageChangeParams.from.kind %s, got %s", e, a)
|
| 465 | 465 |
} |
| 466 | 466 |
} |
| 467 |
+ |
|
| 468 |
+func mkintp(i int) *int64 {
|
|
| 469 |
+ v := int64(i) |
|
| 470 |
+ return &v |
|
| 471 |
+} |
| ... | ... |
@@ -28,8 +28,8 @@ type DeploymentControllerFactory struct {
|
| 28 | 28 |
Codec runtime.Codec |
| 29 | 29 |
// Environment is a set of environment which should be injected into all deployer pod containers. |
| 30 | 30 |
Environment []kapi.EnvVar |
| 31 |
- // RecreateStrategyImage specifies which Docker image which should implement the Recreate strategy. |
|
| 32 |
- RecreateStrategyImage string |
|
| 31 |
+ // DeployerImage specifies which Docker image can support the default strategies. |
|
| 32 |
+ DeployerImage string |
|
| 33 | 33 |
} |
| 34 | 34 |
|
| 35 | 35 |
// Create creates a DeploymentController. |
| ... | ... |
@@ -100,9 +100,9 @@ func (factory *DeploymentControllerFactory) Create() controller.RunnableControll |
| 100 | 100 |
|
| 101 | 101 |
// makeContainer creates containers in the following way: |
| 102 | 102 |
// |
| 103 |
-// 1. For the Recreate strategy, use the factory's RecreateStrategyImage as |
|
| 104 |
-// the container image, and the factory's Environment as the container |
|
| 105 |
-// environment. |
|
| 103 |
+// 1. For the Recreate and Rolling strategies, strategy, use the factory's |
|
| 104 |
+// DeployerImage as the container image, and the factory's Environment |
|
| 105 |
+// as the container environment. |
|
| 106 | 106 |
// 2. For all Custom strategy, use the strategy's image for the container |
| 107 | 107 |
// image, and use the combination of the factory's Environment and the |
| 108 | 108 |
// strategy's environment as the container environment. |
| ... | ... |
@@ -117,10 +117,10 @@ func (factory *DeploymentControllerFactory) makeContainer(strategy *deployapi.De |
| 117 | 117 |
|
| 118 | 118 |
// Every strategy type should be handled here. |
| 119 | 119 |
switch strategy.Type {
|
| 120 |
- case deployapi.DeploymentStrategyTypeRecreate: |
|
| 120 |
+ case deployapi.DeploymentStrategyTypeRecreate, deployapi.DeploymentStrategyTypeRolling: |
|
| 121 | 121 |
// Use the factory-configured image. |
| 122 | 122 |
return &kapi.Container{
|
| 123 |
- Image: factory.RecreateStrategyImage, |
|
| 123 |
+ Image: factory.DeployerImage, |
|
| 124 | 124 |
Env: environment, |
| 125 | 125 |
}, nil |
| 126 | 126 |
case deployapi.DeploymentStrategyTypeCustom: |
| 127 | 127 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,11 @@ |
| 0 |
+package strategy |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// DeploymentStrategy knows how to make a deployment active. |
|
| 7 |
+type DeploymentStrategy interface {
|
|
| 8 |
+ // Deploy makes a deployment active. |
|
| 9 |
+ Deploy(deployment *kapi.ReplicationController, oldDeployments []*kapi.ReplicationController) error |
|
| 10 |
+} |
| ... | ... |
@@ -58,7 +58,7 @@ func NewRecreateDeploymentStrategy(client kclient.Interface, codec runtime.Codec |
| 58 | 58 |
} |
| 59 | 59 |
|
| 60 | 60 |
// Deploy makes deployment active and disables oldDeployments. |
| 61 |
-func (s *RecreateDeploymentStrategy) Deploy(deployment *kapi.ReplicationController, oldDeployments []kapi.ObjectReference) error {
|
|
| 61 |
+func (s *RecreateDeploymentStrategy) Deploy(deployment *kapi.ReplicationController, oldDeployments []*kapi.ReplicationController) error {
|
|
| 62 | 62 |
var err error |
| 63 | 63 |
var deploymentConfig *deployapi.DeploymentConfig |
| 64 | 64 |
|
| ... | ... |
@@ -40,7 +40,7 @@ func TestRecreate_initialDeployment(t *testing.T) {
|
| 40 | 40 |
// Deployment replicas should follow the config as there's no explicit |
| 41 | 41 |
// desired annotation. |
| 42 | 42 |
deployment, _ = deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) |
| 43 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 43 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 44 | 44 |
if err != nil {
|
| 45 | 45 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| 46 | 46 |
} |
| ... | ... |
@@ -51,7 +51,7 @@ func TestRecreate_initialDeployment(t *testing.T) {
|
| 51 | 51 |
// Deployment replicas should follow the explicit annotation. |
| 52 | 52 |
deployment, _ = deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) |
| 53 | 53 |
deployment.Annotations[deployapi.DesiredReplicasAnnotation] = "2" |
| 54 |
- err = strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 54 |
+ err = strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 55 | 55 |
if err != nil {
|
| 56 | 56 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| 57 | 57 |
} |
| ... | ... |
@@ -63,7 +63,7 @@ func TestRecreate_initialDeployment(t *testing.T) {
|
| 63 | 63 |
// invalid. |
| 64 | 64 |
deployment, _ = deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) |
| 65 | 65 |
deployment.Annotations[deployapi.DesiredReplicasAnnotation] = "invalid" |
| 66 |
- err = strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 66 |
+ err = strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 67 | 67 |
if err != nil {
|
| 68 | 68 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| 69 | 69 |
} |
| ... | ... |
@@ -112,12 +112,7 @@ func TestRecreate_secondDeploymentWithSuccessfulRetries(t *testing.T) {
|
| 112 | 112 |
}, |
| 113 | 113 |
} |
| 114 | 114 |
|
| 115 |
- err := strategy.Deploy(newDeployment, []kapi.ObjectReference{
|
|
| 116 |
- {
|
|
| 117 |
- Namespace: oldDeployment.Namespace, |
|
| 118 |
- Name: oldDeployment.Name, |
|
| 119 |
- }, |
|
| 120 |
- }) |
|
| 115 |
+ err := strategy.Deploy(newDeployment, []*kapi.ReplicationController{oldDeployment})
|
|
| 121 | 116 |
|
| 122 | 117 |
if err != nil {
|
| 123 | 118 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| ... | ... |
@@ -165,12 +160,7 @@ func TestRecreate_secondDeploymentScaleUpRetries(t *testing.T) {
|
| 165 | 165 |
}, |
| 166 | 166 |
} |
| 167 | 167 |
|
| 168 |
- err := strategy.Deploy(newDeployment, []kapi.ObjectReference{
|
|
| 169 |
- {
|
|
| 170 |
- Namespace: oldDeployment.Namespace, |
|
| 171 |
- Name: oldDeployment.Name, |
|
| 172 |
- }, |
|
| 173 |
- }) |
|
| 168 |
+ err := strategy.Deploy(newDeployment, []*kapi.ReplicationController{oldDeployment})
|
|
| 174 | 169 |
|
| 175 | 170 |
if err == nil {
|
| 176 | 171 |
t.Fatalf("expected a deploy error: %#v", err)
|
| ... | ... |
@@ -222,12 +212,7 @@ func TestRecreate_secondDeploymentScaleDownRetries(t *testing.T) {
|
| 222 | 222 |
}, |
| 223 | 223 |
} |
| 224 | 224 |
|
| 225 |
- err := strategy.Deploy(newDeployment, []kapi.ObjectReference{
|
|
| 226 |
- {
|
|
| 227 |
- Namespace: oldDeployment.Namespace, |
|
| 228 |
- Name: oldDeployment.Name, |
|
| 229 |
- }, |
|
| 230 |
- }) |
|
| 225 |
+ err := strategy.Deploy(newDeployment, []*kapi.ReplicationController{oldDeployment})
|
|
| 231 | 226 |
|
| 232 | 227 |
if err == nil {
|
| 233 | 228 |
t.Fatalf("expected a deploy error: %#v", err)
|
| ... | ... |
@@ -264,7 +249,7 @@ func TestRecreate_deploymentPreHookSuccess(t *testing.T) {
|
| 264 | 264 |
}, |
| 265 | 265 |
} |
| 266 | 266 |
|
| 267 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 267 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 268 | 268 |
|
| 269 | 269 |
if err != nil {
|
| 270 | 270 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| ... | ... |
@@ -305,7 +290,7 @@ func TestRecreate_deploymentPreHookFailAbort(t *testing.T) {
|
| 305 | 305 |
}, |
| 306 | 306 |
} |
| 307 | 307 |
|
| 308 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 308 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 309 | 309 |
if err == nil {
|
| 310 | 310 |
t.Fatalf("expected a deploy error")
|
| 311 | 311 |
} |
| ... | ... |
@@ -338,7 +323,7 @@ func TestRecreate_deploymentPreHookFailureIgnored(t *testing.T) {
|
| 338 | 338 |
}, |
| 339 | 339 |
} |
| 340 | 340 |
|
| 341 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 341 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 342 | 342 |
|
| 343 | 343 |
if err != nil {
|
| 344 | 344 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| ... | ... |
@@ -384,7 +369,7 @@ func TestRecreate_deploymentPreHookFailureRetried(t *testing.T) {
|
| 384 | 384 |
}, |
| 385 | 385 |
} |
| 386 | 386 |
|
| 387 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 387 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 388 | 388 |
|
| 389 | 389 |
if err != nil {
|
| 390 | 390 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| ... | ... |
@@ -425,7 +410,7 @@ func TestRecreate_deploymentPostHookSuccess(t *testing.T) {
|
| 425 | 425 |
}, |
| 426 | 426 |
} |
| 427 | 427 |
|
| 428 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 428 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 429 | 429 |
|
| 430 | 430 |
if err != nil {
|
| 431 | 431 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| ... | ... |
@@ -466,7 +451,7 @@ func TestRecreate_deploymentPostHookAbortUnsupported(t *testing.T) {
|
| 466 | 466 |
}, |
| 467 | 467 |
} |
| 468 | 468 |
|
| 469 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 469 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 470 | 470 |
|
| 471 | 471 |
if err != nil {
|
| 472 | 472 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| ... | ... |
@@ -507,7 +492,7 @@ func TestRecreate_deploymentPostHookFailIgnore(t *testing.T) {
|
| 507 | 507 |
}, |
| 508 | 508 |
} |
| 509 | 509 |
|
| 510 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 510 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 511 | 511 |
|
| 512 | 512 |
if err != nil {
|
| 513 | 513 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| ... | ... |
@@ -553,7 +538,7 @@ func TestRecreate_deploymentPostHookFailureRetried(t *testing.T) {
|
| 553 | 553 |
}, |
| 554 | 554 |
} |
| 555 | 555 |
|
| 556 |
- err := strategy.Deploy(deployment, []kapi.ObjectReference{})
|
|
| 556 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 557 | 557 |
|
| 558 | 558 |
if err != nil {
|
| 559 | 559 |
t.Fatalf("unexpected deploy error: %#v", err)
|
| 560 | 560 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,215 @@ |
| 0 |
+package rolling |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strconv" |
|
| 5 |
+ "time" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/golang/glog" |
|
| 8 |
+ |
|
| 9 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 10 |
+ kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" |
|
| 11 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" |
|
| 12 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 13 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait" |
|
| 14 |
+ |
|
| 15 |
+ deployapi "github.com/openshift/origin/pkg/deploy/api" |
|
| 16 |
+ "github.com/openshift/origin/pkg/deploy/strategy" |
|
| 17 |
+ deployutil "github.com/openshift/origin/pkg/deploy/util" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+// TODO: This should perhaps be made public upstream. See: |
|
| 21 |
+// https://github.com/GoogleCloudPlatform/kubernetes/issues/7851 |
|
| 22 |
+const sourceIdAnnotation = "kubectl.kubernetes.io/update-source-id" |
|
| 23 |
+ |
|
| 24 |
+// RollingDeploymentStrategy is a Strategy which implements rolling |
|
| 25 |
+// deployments using the upstream Kubernetes RollingUpdater. |
|
| 26 |
+// |
|
| 27 |
+// Currently, there are some caveats: |
|
| 28 |
+// |
|
| 29 |
+// 1. When there is no existing prior deployment, deployment delegates to |
|
| 30 |
+// another strategy. |
|
| 31 |
+// 2. The interface to the RollingUpdater is not very clean. |
|
| 32 |
+// |
|
| 33 |
+// These caveats can be resolved with future upstream refactorings to |
|
| 34 |
+// RollingUpdater[1][2]. |
|
| 35 |
+// |
|
| 36 |
+// [1] https://github.com/GoogleCloudPlatform/kubernetes/pull/7183 |
|
| 37 |
+// [2] https://github.com/GoogleCloudPlatform/kubernetes/issues/7851 |
|
| 38 |
+type RollingDeploymentStrategy struct {
|
|
| 39 |
+ // initialStrategy is used when there are no prior deployments. |
|
| 40 |
+ initialStrategy strategy.DeploymentStrategy |
|
| 41 |
+ // client is used to deal with ReplicationControllers. |
|
| 42 |
+ client kubectl.RollingUpdaterClient |
|
| 43 |
+ // rollingUpdate knows how to perform a rolling update. |
|
| 44 |
+ rollingUpdate func(config *kubectl.RollingUpdaterConfig) error |
|
| 45 |
+ // codec is used to access the encoded config on a deployment. |
|
| 46 |
+ codec runtime.Codec |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+// NewRollingDeploymentStrategy makes a new RollingDeploymentStrategy. |
|
| 50 |
+func NewRollingDeploymentStrategy(namespace string, client kclient.Interface, codec runtime.Codec, initialStrategy strategy.DeploymentStrategy) *RollingDeploymentStrategy {
|
|
| 51 |
+ updaterClient := &rollingUpdaterClient{
|
|
| 52 |
+ ControllerHasDesiredReplicasFn: func(rc *kapi.ReplicationController) wait.ConditionFunc {
|
|
| 53 |
+ return kclient.ControllerHasDesiredReplicas(client, rc) |
|
| 54 |
+ }, |
|
| 55 |
+ GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
|
|
| 56 |
+ return client.ReplicationControllers(namespace).Get(name) |
|
| 57 |
+ }, |
|
| 58 |
+ UpdateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
|
|
| 59 |
+ return client.ReplicationControllers(namespace).Update(rc) |
|
| 60 |
+ }, |
|
| 61 |
+ // This guards against the RollingUpdater's built-in behavior to create |
|
| 62 |
+ // RCs when the supplied old RC is nil. We won't pass nil, but it doesn't |
|
| 63 |
+ // hurt to further guard against it since we would have no way to identify |
|
| 64 |
+ // or clean up orphaned RCs RollingUpdater might inadvertently create. |
|
| 65 |
+ CreateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
|
|
| 66 |
+ return nil, fmt.Errorf("unexpected attempt to create deployment: %#v", rc)
|
|
| 67 |
+ }, |
|
| 68 |
+ // We give the RollingUpdater a policy which should prevent it from |
|
| 69 |
+ // deleting the source deployment after the transition, but it doesn't |
|
| 70 |
+ // hurt to guard by removing its ability to delete. |
|
| 71 |
+ DeleteReplicationControllerFn: func(namespace, name string) error {
|
|
| 72 |
+ return fmt.Errorf("unexpected attempt to delete %s/%s", namespace, name)
|
|
| 73 |
+ }, |
|
| 74 |
+ } |
|
| 75 |
+ return &RollingDeploymentStrategy{
|
|
| 76 |
+ codec: codec, |
|
| 77 |
+ initialStrategy: initialStrategy, |
|
| 78 |
+ client: updaterClient, |
|
| 79 |
+ rollingUpdate: func(config *kubectl.RollingUpdaterConfig) error {
|
|
| 80 |
+ updater := kubectl.NewRollingUpdater(namespace, updaterClient) |
|
| 81 |
+ return updater.Update(config) |
|
| 82 |
+ }, |
|
| 83 |
+ } |
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+func (s *RollingDeploymentStrategy) Deploy(deployment *kapi.ReplicationController, oldDeployments []*kapi.ReplicationController) error {
|
|
| 87 |
+ config, err := deployutil.DecodeDeploymentConfig(deployment, s.codec) |
|
| 88 |
+ if err != nil {
|
|
| 89 |
+ return fmt.Errorf("Couldn't decode DeploymentConfig from deployment %s: %v", deployment.Name, err)
|
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ // Find the latest deployment (if any). |
|
| 93 |
+ latest, err := s.findLatestDeployment(oldDeployments) |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ return fmt.Errorf("couldn't determine latest deployment: %v", err)
|
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ // If there's no prior deployment, delegate to another strategy since the |
|
| 99 |
+ // rolling updater only supports transitioning between two deployments. |
|
| 100 |
+ if latest == nil {
|
|
| 101 |
+ return s.initialStrategy.Deploy(deployment, oldDeployments) |
|
| 102 |
+ } |
|
| 103 |
+ |
|
| 104 |
+ // HACK: Assign the source ID annotation that the rolling updater expects, |
|
| 105 |
+ // unless it already exists on the deployment. |
|
| 106 |
+ // |
|
| 107 |
+ // Related upstream issue: |
|
| 108 |
+ // https://github.com/GoogleCloudPlatform/kubernetes/pull/7183 |
|
| 109 |
+ if _, hasSourceId := deployment.Annotations[sourceIdAnnotation]; !hasSourceId {
|
|
| 110 |
+ deployment.Annotations[sourceIdAnnotation] = fmt.Sprintf("%s:%s", latest.Name, latest.ObjectMeta.UID)
|
|
| 111 |
+ if updated, err := s.client.UpdateReplicationController(deployment.Namespace, deployment); err != nil {
|
|
| 112 |
+ return fmt.Errorf("couldn't assign source annotation to deployment %s/%s: %v", deployment.Namespace, deployment.Name, err)
|
|
| 113 |
+ } else {
|
|
| 114 |
+ deployment = updated |
|
| 115 |
+ } |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ // HACK: There's a validation in the rolling updater which assumes that when |
|
| 119 |
+ // an existing RC is supplied, it will have >0 replicas- a validation which |
|
| 120 |
+ // is then disregarded as the desired count is obtained from the annotation |
|
| 121 |
+ // on the RC. For now, fake it out by just setting replicas to 1. |
|
| 122 |
+ // |
|
| 123 |
+ // Related upstream issue: |
|
| 124 |
+ // https://github.com/GoogleCloudPlatform/kubernetes/pull/7183 |
|
| 125 |
+ deployment.Spec.Replicas = 1 |
|
| 126 |
+ |
|
| 127 |
+ glog.Infof("OldRc: %s, replicas=%d", latest.Name, latest.Spec.Replicas)
|
|
| 128 |
+ // Perform a rolling update. |
|
| 129 |
+ params := config.Template.Strategy.RollingParams |
|
| 130 |
+ rollingConfig := &kubectl.RollingUpdaterConfig{
|
|
| 131 |
+ Out: &rollingUpdaterWriter{},
|
|
| 132 |
+ OldRc: latest, |
|
| 133 |
+ NewRc: deployment, |
|
| 134 |
+ UpdatePeriod: time.Duration(*params.UpdatePeriodSeconds) * time.Second, |
|
| 135 |
+ Interval: time.Duration(*params.IntervalSeconds) * time.Second, |
|
| 136 |
+ Timeout: time.Duration(*params.TimeoutSeconds) * time.Second, |
|
| 137 |
+ CleanupPolicy: kubectl.PreserveRollingUpdateCleanupPolicy, |
|
| 138 |
+ } |
|
| 139 |
+ glog.Infof("Starting rolling update with config: %#v (UpdatePeriod %d, Interval %d, Timeout %d) (UpdatePeriodSeconds %d, IntervalSeconds %d, TimeoutSeconds %d)",
|
|
| 140 |
+ rollingConfig, |
|
| 141 |
+ rollingConfig.UpdatePeriod, |
|
| 142 |
+ rollingConfig.Interval, |
|
| 143 |
+ rollingConfig.Timeout, |
|
| 144 |
+ *params.UpdatePeriodSeconds, |
|
| 145 |
+ *params.IntervalSeconds, |
|
| 146 |
+ *params.TimeoutSeconds, |
|
| 147 |
+ ) |
|
| 148 |
+ return s.rollingUpdate(rollingConfig) |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+// findLatestDeployment retrieves deployments identified by oldDeployments and |
|
| 152 |
+// returns the latest one from the list, or nil if there are no old |
|
| 153 |
+// deployments. |
|
| 154 |
+func (s *RollingDeploymentStrategy) findLatestDeployment(oldDeployments []*kapi.ReplicationController) (*kapi.ReplicationController, error) {
|
|
| 155 |
+ // Find the latest deployment from the list of old deployments. |
|
| 156 |
+ var latest *kapi.ReplicationController |
|
| 157 |
+ latestVersion := 0 |
|
| 158 |
+ for _, deployment := range oldDeployments {
|
|
| 159 |
+ if val, hasVersion := deployment.Annotations[deployapi.DeploymentVersionAnnotation]; hasVersion {
|
|
| 160 |
+ version, err := strconv.Atoi(val) |
|
| 161 |
+ if err != nil {
|
|
| 162 |
+ return nil, fmt.Errorf("deployment %s/%s has invalid version annotation value '%s': %v", deployment.Namespace, deployment.Name, val, err)
|
|
| 163 |
+ } |
|
| 164 |
+ if version > latestVersion {
|
|
| 165 |
+ latest = deployment |
|
| 166 |
+ latestVersion = version |
|
| 167 |
+ } |
|
| 168 |
+ } else {
|
|
| 169 |
+ glog.Infof("Ignoring deployment with missing version annotation: %s/%s", deployment.Namespace, deployment.Name)
|
|
| 170 |
+ } |
|
| 171 |
+ } |
|
| 172 |
+ if latest != nil {
|
|
| 173 |
+ glog.Infof("Found latest deployment %s", latest.Name)
|
|
| 174 |
+ } else {
|
|
| 175 |
+ glog.Info("No latest deployment found")
|
|
| 176 |
+ } |
|
| 177 |
+ return latest, nil |
|
| 178 |
+} |
|
| 179 |
+ |
|
| 180 |
+type rollingUpdaterClient struct {
|
|
| 181 |
+ GetReplicationControllerFn func(namespace, name string) (*kapi.ReplicationController, error) |
|
| 182 |
+ UpdateReplicationControllerFn func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) |
|
| 183 |
+ CreateReplicationControllerFn func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) |
|
| 184 |
+ DeleteReplicationControllerFn func(namespace, name string) error |
|
| 185 |
+ ControllerHasDesiredReplicasFn func(rc *kapi.ReplicationController) wait.ConditionFunc |
|
| 186 |
+} |
|
| 187 |
+ |
|
| 188 |
+func (c *rollingUpdaterClient) GetReplicationController(namespace, name string) (*kapi.ReplicationController, error) {
|
|
| 189 |
+ return c.GetReplicationControllerFn(namespace, name) |
|
| 190 |
+} |
|
| 191 |
+ |
|
| 192 |
+func (c *rollingUpdaterClient) UpdateReplicationController(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
|
|
| 193 |
+ return c.UpdateReplicationControllerFn(namespace, rc) |
|
| 194 |
+} |
|
| 195 |
+ |
|
| 196 |
+func (c *rollingUpdaterClient) CreateReplicationController(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
|
|
| 197 |
+ return c.CreateReplicationControllerFn(namespace, rc) |
|
| 198 |
+} |
|
| 199 |
+ |
|
| 200 |
+func (c *rollingUpdaterClient) DeleteReplicationController(namespace, name string) error {
|
|
| 201 |
+ return c.DeleteReplicationControllerFn(namespace, name) |
|
| 202 |
+} |
|
| 203 |
+ |
|
| 204 |
+func (c *rollingUpdaterClient) ControllerHasDesiredReplicas(rc *kapi.ReplicationController) wait.ConditionFunc {
|
|
| 205 |
+ return c.ControllerHasDesiredReplicasFn(rc) |
|
| 206 |
+} |
|
| 207 |
+ |
|
| 208 |
+// rollingUpdaterWriter is an io.Writer that delegates to glog. |
|
| 209 |
+type rollingUpdaterWriter struct{}
|
|
| 210 |
+ |
|
| 211 |
+func (w *rollingUpdaterWriter) Write(p []byte) (n int, err error) {
|
|
| 212 |
+ glog.Info(fmt.Sprintf("RollingUpdater: %s", p))
|
|
| 213 |
+ return len(p), nil |
|
| 214 |
+} |
| 0 | 215 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,238 @@ |
| 0 |
+package rolling |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "testing" |
|
| 5 |
+ "time" |
|
| 6 |
+ |
|
| 7 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 8 |
+ kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" |
|
| 9 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" |
|
| 10 |
+ |
|
| 11 |
+ api "github.com/openshift/origin/pkg/api/latest" |
|
| 12 |
+ deployapi "github.com/openshift/origin/pkg/deploy/api" |
|
| 13 |
+ deploytest "github.com/openshift/origin/pkg/deploy/api/test" |
|
| 14 |
+ deployutil "github.com/openshift/origin/pkg/deploy/util" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+func TestRolling_deployInitial(t *testing.T) {
|
|
| 18 |
+ initialStrategyInvoked := false |
|
| 19 |
+ |
|
| 20 |
+ strategy := &RollingDeploymentStrategy{
|
|
| 21 |
+ codec: api.Codec, |
|
| 22 |
+ client: &rollingUpdaterClient{
|
|
| 23 |
+ GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
|
|
| 24 |
+ t.Fatalf("unexpected call to GetReplicationController")
|
|
| 25 |
+ return nil, nil |
|
| 26 |
+ }, |
|
| 27 |
+ }, |
|
| 28 |
+ initialStrategy: &testStrategy{
|
|
| 29 |
+ deployFn: func(deployment *kapi.ReplicationController, oldDeployments []*kapi.ReplicationController) error {
|
|
| 30 |
+ initialStrategyInvoked = true |
|
| 31 |
+ return nil |
|
| 32 |
+ }, |
|
| 33 |
+ }, |
|
| 34 |
+ rollingUpdate: func(config *kubectl.RollingUpdaterConfig) error {
|
|
| 35 |
+ t.Fatalf("unexpected call to rollingUpdate")
|
|
| 36 |
+ return nil |
|
| 37 |
+ }, |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) |
|
| 41 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{})
|
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 44 |
+ } |
|
| 45 |
+ if !initialStrategyInvoked {
|
|
| 46 |
+ t.Fatalf("expected initial strategy to be invoked")
|
|
| 47 |
+ } |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+func TestRolling_deployRolling(t *testing.T) {
|
|
| 51 |
+ latest, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) |
|
| 52 |
+ config := deploytest.OkDeploymentConfig(2) |
|
| 53 |
+ config.Template.Strategy = deployapi.DeploymentStrategy{
|
|
| 54 |
+ Type: deployapi.DeploymentStrategyTypeRolling, |
|
| 55 |
+ RollingParams: &deployapi.RollingDeploymentStrategyParams{
|
|
| 56 |
+ IntervalSeconds: mkintp(1), |
|
| 57 |
+ UpdatePeriodSeconds: mkintp(2), |
|
| 58 |
+ TimeoutSeconds: mkintp(3), |
|
| 59 |
+ }, |
|
| 60 |
+ } |
|
| 61 |
+ deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) |
|
| 62 |
+ |
|
| 63 |
+ var rollingConfig *kubectl.RollingUpdaterConfig |
|
| 64 |
+ deploymentUpdated := false |
|
| 65 |
+ strategy := &RollingDeploymentStrategy{
|
|
| 66 |
+ codec: api.Codec, |
|
| 67 |
+ client: &rollingUpdaterClient{
|
|
| 68 |
+ GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
|
|
| 69 |
+ if name != latest.Name {
|
|
| 70 |
+ t.Fatalf("unexpected call to GetReplicationController for %s", name)
|
|
| 71 |
+ } |
|
| 72 |
+ return latest, nil |
|
| 73 |
+ }, |
|
| 74 |
+ UpdateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) {
|
|
| 75 |
+ if rc.Name != deployment.Name {
|
|
| 76 |
+ t.Fatalf("unexpected call to UpdateReplicationController for %s", rc.Name)
|
|
| 77 |
+ } |
|
| 78 |
+ deploymentUpdated = true |
|
| 79 |
+ return rc, nil |
|
| 80 |
+ }, |
|
| 81 |
+ }, |
|
| 82 |
+ initialStrategy: &testStrategy{
|
|
| 83 |
+ deployFn: func(deployment *kapi.ReplicationController, oldDeployments []*kapi.ReplicationController) error {
|
|
| 84 |
+ t.Fatalf("unexpected call to initial strategy")
|
|
| 85 |
+ return nil |
|
| 86 |
+ }, |
|
| 87 |
+ }, |
|
| 88 |
+ rollingUpdate: func(config *kubectl.RollingUpdaterConfig) error {
|
|
| 89 |
+ rollingConfig = config |
|
| 90 |
+ return nil |
|
| 91 |
+ }, |
|
| 92 |
+ } |
|
| 93 |
+ |
|
| 94 |
+ err := strategy.Deploy(deployment, []*kapi.ReplicationController{latest})
|
|
| 95 |
+ if err != nil {
|
|
| 96 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 97 |
+ } |
|
| 98 |
+ if rollingConfig == nil {
|
|
| 99 |
+ t.Fatalf("expected rolling update to be invoked")
|
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ if e, a := latest, rollingConfig.OldRc; e != a {
|
|
| 103 |
+ t.Errorf("expected rollingConfig.OldRc %v, got %v", e, a)
|
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ if e, a := deployment, rollingConfig.NewRc; e != a {
|
|
| 107 |
+ t.Errorf("expected rollingConfig.NewRc %v, got %v", e, a)
|
|
| 108 |
+ } |
|
| 109 |
+ |
|
| 110 |
+ if e, a := 1*time.Second, rollingConfig.Interval; e != a {
|
|
| 111 |
+ t.Errorf("expected Interval %d, got %d", e, a)
|
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ if e, a := 2*time.Second, rollingConfig.UpdatePeriod; e != a {
|
|
| 115 |
+ t.Errorf("expected UpdatePeriod %d, got %d", e, a)
|
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ if e, a := 3*time.Second, rollingConfig.Timeout; e != a {
|
|
| 119 |
+ t.Errorf("expected Timeout %d, got %d", e, a)
|
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ // verify hack |
|
| 123 |
+ if e, a := 1, rollingConfig.NewRc.Spec.Replicas; e != a {
|
|
| 124 |
+ t.Errorf("expected rollingConfig.NewRc.Spec.Replicas %d, got %d", e, a)
|
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ // verify hack |
|
| 128 |
+ if !deploymentUpdated {
|
|
| 129 |
+ t.Errorf("expected deployment to be updated for source annotation")
|
|
| 130 |
+ } |
|
| 131 |
+ sid := fmt.Sprintf("%s:%s", latest.Name, latest.ObjectMeta.UID)
|
|
| 132 |
+ if e, a := sid, rollingConfig.NewRc.Annotations[sourceIdAnnotation]; e != a {
|
|
| 133 |
+ t.Errorf("expected sourceIdAnnotation %s, got %s", e, a)
|
|
| 134 |
+ } |
|
| 135 |
+} |
|
| 136 |
+ |
|
| 137 |
+func TestRolling_findLatestDeployment(t *testing.T) {
|
|
| 138 |
+ deployments := map[string]*kapi.ReplicationController{}
|
|
| 139 |
+ for i := 1; i <= 10; i++ {
|
|
| 140 |
+ deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(i), kapi.Codec) |
|
| 141 |
+ deployments[deployment.Name] = deployment |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ ignoredDeployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(12), kapi.Codec) |
|
| 145 |
+ delete(ignoredDeployment.Annotations, deployapi.DeploymentVersionAnnotation) |
|
| 146 |
+ deployments[ignoredDeployment.Name] = ignoredDeployment |
|
| 147 |
+ |
|
| 148 |
+ strategy := &RollingDeploymentStrategy{
|
|
| 149 |
+ codec: api.Codec, |
|
| 150 |
+ client: &rollingUpdaterClient{
|
|
| 151 |
+ GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
|
|
| 152 |
+ deployment, found := deployments[name] |
|
| 153 |
+ if !found {
|
|
| 154 |
+ return nil, kerrors.NewNotFound("ReplicationController", name)
|
|
| 155 |
+ } |
|
| 156 |
+ return deployment, nil |
|
| 157 |
+ }, |
|
| 158 |
+ }, |
|
| 159 |
+ } |
|
| 160 |
+ |
|
| 161 |
+ type scenario struct {
|
|
| 162 |
+ old []string |
|
| 163 |
+ latest string |
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ scenarios := []scenario{
|
|
| 167 |
+ {
|
|
| 168 |
+ old: []string{
|
|
| 169 |
+ "config-1", |
|
| 170 |
+ "config-2", |
|
| 171 |
+ "config-3", |
|
| 172 |
+ }, |
|
| 173 |
+ latest: "config-3", |
|
| 174 |
+ }, |
|
| 175 |
+ {
|
|
| 176 |
+ old: []string{
|
|
| 177 |
+ "config-3", |
|
| 178 |
+ "config-1", |
|
| 179 |
+ "config-7", |
|
| 180 |
+ ignoredDeployment.Name, |
|
| 181 |
+ }, |
|
| 182 |
+ latest: "config-7", |
|
| 183 |
+ }, |
|
| 184 |
+ } |
|
| 185 |
+ |
|
| 186 |
+ for _, scenario := range scenarios {
|
|
| 187 |
+ old := []*kapi.ReplicationController{}
|
|
| 188 |
+ for _, oldName := range scenario.old {
|
|
| 189 |
+ old = append(old, deployments[oldName]) |
|
| 190 |
+ } |
|
| 191 |
+ found, err := strategy.findLatestDeployment(old) |
|
| 192 |
+ if err != nil {
|
|
| 193 |
+ t.Errorf("unexpected error for scenario: %v: %v", scenario, err)
|
|
| 194 |
+ continue |
|
| 195 |
+ } |
|
| 196 |
+ |
|
| 197 |
+ if found == nil {
|
|
| 198 |
+ t.Errorf("expected to find a deployment for scenario: %v", scenario)
|
|
| 199 |
+ continue |
|
| 200 |
+ } |
|
| 201 |
+ |
|
| 202 |
+ if e, a := scenario.latest, found.Name; e != a {
|
|
| 203 |
+ t.Errorf("expected latest %s, got %s for scenario: %v", e, a, scenario)
|
|
| 204 |
+ } |
|
| 205 |
+ } |
|
| 206 |
+} |
|
| 207 |
+ |
|
| 208 |
+func TestRolling_findLatestDeploymentInvalidDeployment(t *testing.T) {
|
|
| 209 |
+ deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) |
|
| 210 |
+ deployment.Annotations[deployapi.DeploymentVersionAnnotation] = "" |
|
| 211 |
+ |
|
| 212 |
+ strategy := &RollingDeploymentStrategy{
|
|
| 213 |
+ codec: api.Codec, |
|
| 214 |
+ client: &rollingUpdaterClient{
|
|
| 215 |
+ GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) {
|
|
| 216 |
+ return deployment, nil |
|
| 217 |
+ }, |
|
| 218 |
+ }, |
|
| 219 |
+ } |
|
| 220 |
+ _, err := strategy.findLatestDeployment([]*kapi.ReplicationController{deployment})
|
|
| 221 |
+ if err == nil {
|
|
| 222 |
+ t.Errorf("expected an error")
|
|
| 223 |
+ } |
|
| 224 |
+} |
|
| 225 |
+ |
|
| 226 |
+type testStrategy struct {
|
|
| 227 |
+ deployFn func(deployment *kapi.ReplicationController, oldDeployments []*kapi.ReplicationController) error |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 230 |
+func (s *testStrategy) Deploy(deployment *kapi.ReplicationController, oldDeployments []*kapi.ReplicationController) error {
|
|
| 231 |
+ return s.deployFn(deployment, oldDeployments) |
|
| 232 |
+} |
|
| 233 |
+ |
|
| 234 |
+func mkintp(i int) *int64 {
|
|
| 235 |
+ v := int64(i) |
|
| 236 |
+ return &v |
|
| 237 |
+} |