Skip to content

Commit

Permalink
Merge pull request #46144 from janetkuo/kubectl-rollout-ds
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue (batch tested with PRs 45871, 46498, 46729, 46144, 46804)

Implement kubectl rollout undo and history for DaemonSet

~Depends on #45924, only the 2nd commit needs review~ (merged)

Ref kubernetes/community#527

TODOs:
- [x] kubectl rollout history
  - [x] sort controller history, print overview (with revision number and change cause)
  - [x] print detail view (content of a history) 
    - [x] print template 
    - [x] ~(do we need to?) print labels and annotations~
- [x] kubectl rollout undo: 
  - [x] list controller history, figure out which revision to rollback to
    - if toRevision == 0, rollback to the latest revision, otherwise choose the history with matching revision
  - [x] update the ds using the history to rollback to 
    - [x] replace the ds template with history's
    - [x] ~(do we need to?) replace the ds labels and annotations with history's~
- [x] test-cmd.sh 

@kubernetes/sig-apps-pr-reviews @erictune @kow3ns @lukaszo @Kargakis @kubernetes/sig-cli-maintainers 

--- 

**Release note**:

```release-note
```
  • Loading branch information
Kubernetes Submit Queue committed Jun 5, 2017
2 parents 2fcadae + edabdac commit bdf9dc1
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 33 deletions.
40 changes: 40 additions & 0 deletions hack/make-rules/test-cmd-util.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ IMAGE_NGINX="gcr.io/google-containers/nginx:1.7.9"
IMAGE_DEPLOYMENT_R1="gcr.io/google-containers/nginx:test-cmd" # deployment-revision1.yaml
IMAGE_DEPLOYMENT_R2="$IMAGE_NGINX" # deployment-revision2.yaml
IMAGE_PERL="gcr.io/google-containers/perl"
IMAGE_DAEMONSET_R1="gcr.io/google-containers/pause:2.0"
IMAGE_DAEMONSET_R2="gcr.io/google-containers/pause:latest"

# Expose kubectl directly for readability
PATH="${KUBE_OUTPUT_HOSTBIN}":$PATH
Expand Down Expand Up @@ -71,6 +73,7 @@ subjectaccessreviews="subjectaccessreviews"
thirdpartyresources="thirdpartyresources"
customresourcedefinitions="customresourcedefinitions"
daemonsets="daemonsets"
controllerrevisions="controllerrevisions"


# Stops the running kubectl proxy, if there is one.
Expand Down Expand Up @@ -2868,6 +2871,39 @@ run_daemonset_tests() {
kubectl apply -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
# Template Generation should stay 1
kube::test::get_object_assert 'daemonsets bind' "{{${template_generation_field}}}" '1'
# Clean up
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
}

run_daemonset_history_tests() {
kube::log::status "Testing kubectl(v1:daemonsets, v1:controllerrevisions)"

### Test rolling back a DaemonSet
# Pre-condition: no DaemonSet or its pods exists
kube::test::get_object_assert daemonsets "{{range.items}}{{$id_field}}:{{end}}" ''
# Command
# Create a DaemonSet (revision 1)
kubectl apply -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
# Rollback to revision 1 - should be no-op
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]}"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
# Update the DaemonSet (revision 2)
kubectl apply -f hack/testdata/rollingupdate-daemonset-rv2.yaml "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
# Rollback to revision 1 with dry-run - should be no-op
kubectl rollout undo daemonset --dry-run=true "${kube_flags[@]}"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
# Rollback to revision 1
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
# Rollback to revision 1000000 - should fail
output_message=$(! kubectl rollout undo daemonset --to-revision=1000000 "${kube_flags[@]}" 2>&1)
kube::test::if_has_string "${output_message}" "unable to find specified revision"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
# Rollback to last revision
kubectl rollout undo daemonset "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
# Clean up
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
}

Expand Down Expand Up @@ -3103,6 +3139,7 @@ runTests() {
pdb_min_available=".spec.minAvailable"
pdb_max_unavailable=".spec.maxUnavailable"
template_generation_field=".spec.templateGeneration"
daemonset_image_field="(index .spec.template.spec.containers 0).image"

# Make sure "default" namespace exists.
if kube::test::if_supports_resource "${namespaces}" ; then
Expand Down Expand Up @@ -3555,6 +3592,9 @@ runTests() {

if kube::test::if_supports_resource "${daemonsets}" ; then
run_daemonset_tests
if kube::test::if_supports_resource "${controllerrevisions}"; then
run_daemonset_history_tests
fi
fi

###########################
Expand Down
27 changes: 27 additions & 0 deletions hack/testdata/rollingupdate-daemonset-rv2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: bind
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 10%
template:
metadata:
labels:
service: bind
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "service"
operator: "In"
values: ["bind"]
topologyKey: "kubernetes.io/hostname"
namespaces: []
containers:
- name: kubernetes-pause
image: gcr.io/google-containers/pause:latest
1 change: 1 addition & 0 deletions pkg/controller/daemon/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
Expand Down
16 changes: 5 additions & 11 deletions pkg/controller/daemon/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ limitations under the License.
package daemon

import (
"bytes"
"encoding/json"
"fmt"
"sort"

Expand All @@ -29,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
Expand Down Expand Up @@ -297,23 +296,18 @@ func (dsc *DaemonSetsController) controlledHistories(ds *extensions.DaemonSet) (

// Match check if ds template is semantically equal to the template stored in history
func Match(template *v1.PodTemplateSpec, history *apps.ControllerRevision) (bool, error) {
t, err := decodeHistory(history)
t, err := DecodeHistory(history)
return apiequality.Semantic.DeepEqual(template, t), err
}

func decodeHistory(history *apps.ControllerRevision) (*v1.PodTemplateSpec, error) {
raw := history.Data.Raw
decoder := json.NewDecoder(bytes.NewBuffer(raw))
func DecodeHistory(history *apps.ControllerRevision) (*v1.PodTemplateSpec, error) {
template := v1.PodTemplateSpec{}
err := decoder.Decode(&template)
err := json.Unmarshal(history.Data.Raw, &template)
return &template, err
}

func encodeTemplate(template *v1.PodTemplateSpec) ([]byte, error) {
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
err := encoder.Encode(template)
return buffer.Bytes(), err
return json.Marshal(template)
}

func (dsc *DaemonSetsController) snapshot(ds *extensions.DaemonSet, revision int64) (*apps.ControllerRevision, error) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/kubectl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ go_library(
"//pkg/apis/policy:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/apps/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/extensions/v1beta1:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
Expand All @@ -76,6 +77,7 @@ go_library(
"//pkg/client/retry:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/daemon:go_default_library",
"//pkg/controller/deployment/util:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//pkg/kubectl/resource:go_default_library",
Expand All @@ -88,6 +90,7 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
Expand Down
12 changes: 8 additions & 4 deletions pkg/kubectl/cmd/rollout/rollout.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,28 @@ import (

var (
rollout_long = templates.LongDesc(`
Manage a deployment using subcommands like "kubectl rollout undo deployment/abc"`)
Manage the rollout of a resource.` + rollout_valid_resources)

rollout_example = templates.Examples(`
# Rollback to the previous deployment
kubectl rollout undo deployment/abc`)
kubectl rollout undo deployment/abc
# Check the rollout status of a daemonset
kubectl rollout status daemonset/foo`)

rollout_valid_resources = dedent.Dedent(`
Valid resource types include:
* deployments
* daemonsets
`)
)

func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {

cmd := &cobra.Command{
Use: "rollout SUBCOMMAND",
Short: i18n.T("Manage a deployment rollout"),
Short: i18n.T("Manage the rollout of a resource"),
Long: rollout_long,
Example: rollout_example,
Run: cmdutil.DefaultSubCommandRun(errOut),
Expand All @@ -54,7 +59,6 @@ func NewCmdRollout(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdRolloutPause(f, out))
cmd.AddCommand(NewCmdRolloutResume(f, out))
cmd.AddCommand(NewCmdRolloutUndo(f, out))

cmd.AddCommand(NewCmdRolloutStatus(f, out))

return cmd
Expand Down
6 changes: 3 additions & 3 deletions pkg/kubectl/cmd/rollout/rollout_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ var (
# View the rollout history of a deployment
kubectl rollout history deployment/abc
# View the details of deployment revision 3
kubectl rollout history deployment/abc --revision=3`)
# View the details of daemonset revision 3
kubectl rollout history daemonset/abc --revision=3`)
)

func NewCmdRolloutHistory(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}

validArgs := []string{"deployment"}
validArgs := []string{"deployment", "daemonset"}
argAliases := kubectl.ResourceAliases(validArgs)

cmd := &cobra.Command{
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/rollout/rollout_pause.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var (
Mark the provided resource as paused
Paused resources will not be reconciled by a controller.
Use \"kubectl rollout resume\" to resume a paused resource.
Use "kubectl rollout resume" to resume a paused resource.
Currently only deployments support being paused.`)

pause_example = templates.Examples(`
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/rollout/rollout_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var (
func NewCmdRolloutStatus(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}

validArgs := []string{"deployment"}
validArgs := []string{"deployment", "daemonset"}
argAliases := kubectl.ResourceAliases(validArgs)

cmd := &cobra.Command{
Expand Down
6 changes: 3 additions & 3 deletions pkg/kubectl/cmd/rollout/rollout_undo.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ var (
# Rollback to the previous deployment
kubectl rollout undo deployment/abc
# Rollback to deployment revision 3
kubectl rollout undo deployment/abc --to-revision=3
# Rollback to daemonset revision 3
kubectl rollout undo daemonset/abc --to-revision=3
# Rollback to the previous deployment with dry-run
kubectl rollout undo --dry-run=true deployment/abc`)
Expand All @@ -64,7 +64,7 @@ var (
func NewCmdRolloutUndo(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &UndoOptions{}

validArgs := []string{"deployment"}
validArgs := []string{"deployment", "daemonset"}
argAliases := kubectl.ResourceAliases(validArgs)

cmd := &cobra.Command{
Expand Down
Loading

0 comments on commit bdf9dc1

Please sign in to comment.