Skip to content

Commit db6e1ee

Browse files
authored
feat: add restart option for DaemonSets & StatefulSets (#9866)
* make daemon set restartable * make stateful set restartable * addressed PR comments * fix failing UTs * add valid license header * fix PR build * fix PR build
1 parent 2af4949 commit db6e1ee

File tree

6 files changed

+147
-6
lines changed

6 files changed

+147
-6
lines changed

modules/api/pkg/handler/apihandler.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,15 @@ func CreateHTTPAPIHandler(iManager integration.Manager) (*restful.Container, err
454454
Param(apiV1Ws.PathParameter("daemonSet", "name of the DaemonSet")).
455455
Writes(common.EventList{}).
456456
Returns(http.StatusOK, "OK", common.EventList{}))
457+
apiV1Ws.Route(
458+
apiV1Ws.PUT("/daemonset/{namespace}/{daemonSet}/restart").To(apiHandler.handleDaemonSetRestart).
459+
// docs
460+
Doc("rollout restart of the Daemon Set").
461+
Param(apiV1Ws.PathParameter("namespace", "namespace of the Daemon Set")).
462+
Param(apiV1Ws.PathParameter("daemonSet", "name of the Daemon Set")).
463+
Writes(deployment.RolloutSpec{}).
464+
Returns(http.StatusOK, "OK", daemonset.DaemonSetDetail{}),
465+
)
457466

458467
// HorizontalPodAutoscaler
459468
apiV1Ws.Route(
@@ -846,6 +855,15 @@ func CreateHTTPAPIHandler(iManager integration.Manager) (*restful.Container, err
846855
Param(apiV1Ws.PathParameter("statefulset", "name of the StatefulSet")).
847856
Writes(common.EventList{}).
848857
Returns(http.StatusOK, "OK", common.EventList{}))
858+
apiV1Ws.Route(
859+
apiV1Ws.PUT("/statefulset/{namespace}/{statefulset}/restart").To(apiHandler.handleStatefulSetRestart).
860+
// docs
861+
Doc("rollout restart of the Daemon Set").
862+
Param(apiV1Ws.PathParameter("namespace", "namespace of the StatefulSet")).
863+
Param(apiV1Ws.PathParameter("statefulset", "name of the StatefulSet")).
864+
Writes(deployment.RolloutSpec{}).
865+
Returns(http.StatusOK, "OK", statefulset.StatefulSetDetail{}),
866+
)
849867

850868
// Node
851869
apiV1Ws.Route(
@@ -2801,6 +2819,40 @@ func (apiHandler *APIHandler) handleGetDaemonSetEvents(request *restful.Request,
28012819
_ = response.WriteHeaderAndEntity(http.StatusOK, result)
28022820
}
28032821

2822+
func (apiHandle *APIHandler) handleDaemonSetRestart(request *restful.Request, response *restful.Response) {
2823+
k8sClient, err := client.Client(request.Request)
2824+
if err != nil {
2825+
errors.HandleInternalError(response, err)
2826+
return
2827+
}
2828+
2829+
namespace := request.PathParameter("namespace")
2830+
name := request.PathParameter("daemonSet")
2831+
result, err := daemonset.RestartDaemonSet(k8sClient, namespace, name)
2832+
if err != nil {
2833+
errors.HandleInternalError(response, err)
2834+
return
2835+
}
2836+
_ = response.WriteHeaderAndEntity(http.StatusOK, result)
2837+
}
2838+
2839+
func (apiHandle *APIHandler) handleStatefulSetRestart(request *restful.Request, response *restful.Response) {
2840+
k8sClient, err := client.Client(request.Request)
2841+
if err != nil {
2842+
errors.HandleInternalError(response, err)
2843+
return
2844+
}
2845+
2846+
namespace := request.PathParameter("namespace")
2847+
name := request.PathParameter("daemonSet")
2848+
result, err := statefulset.RestartStatefulSet(k8sClient, namespace, name)
2849+
if err != nil {
2850+
errors.HandleInternalError(response, err)
2851+
return
2852+
}
2853+
_ = response.WriteHeaderAndEntity(http.StatusOK, result)
2854+
}
2855+
28042856
func (apiHandler *APIHandler) handleGetHorizontalPodAutoscalerList(request *restful.Request,
28052857
response *restful.Response) {
28062858
k8sClient, err := client.Client(request.Request)

modules/api/pkg/resource/daemonset/list_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func TestGetDaemonSetListFromChannels(t *testing.T) {
106106
Labels: map[string]string{"key": "value"},
107107
CreationTimestamp: metaV1.Unix(111, 222),
108108
},
109-
TypeMeta: types.TypeMeta{Kind: types.ResourceKindDaemonSet},
109+
TypeMeta: types.TypeMeta{Kind: types.ResourceKindDaemonSet, Restartable: true},
110110
Pods: common.PodInfo{
111111
Current: 0,
112112
Failed: 0,
@@ -351,7 +351,7 @@ func TestToDaemonSetList(t *testing.T) {
351351
Namespace: "namespace-1",
352352
UID: "uid-1",
353353
},
354-
TypeMeta: types.TypeMeta{Kind: types.ResourceKindDaemonSet},
354+
TypeMeta: types.TypeMeta{Kind: types.ResourceKindDaemonSet, Restartable: true},
355355
ContainerImages: []string{"my-container-image-1"},
356356
InitContainerImages: []string{"my-init-container-image-1"},
357357
Pods: common.PodInfo{
@@ -367,7 +367,7 @@ func TestToDaemonSetList(t *testing.T) {
367367
Name: "my-app-2",
368368
Namespace: "namespace-2",
369369
},
370-
TypeMeta: types.TypeMeta{Kind: types.ResourceKindDaemonSet},
370+
TypeMeta: types.TypeMeta{Kind: types.ResourceKindDaemonSet, Restartable: true},
371371
ContainerImages: []string{"my-container-image-2"},
372372
InitContainerImages: []string{"my-init-container-image-2"},
373373
Pods: common.PodInfo{
@@ -379,7 +379,7 @@ func TestToDaemonSetList(t *testing.T) {
379379
Name: "my-app-3",
380380
Namespace: "namespace-3",
381381
},
382-
TypeMeta: types.TypeMeta{Kind: types.ResourceKindDaemonSet},
382+
TypeMeta: types.TypeMeta{Kind: types.ResourceKindDaemonSet, Restartable: true},
383383
ContainerImages: []string{"my-container-image-3"},
384384
InitContainerImages: []string{"my-init-container-image-3"},
385385
Pods: common.PodInfo{
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2017 The Kubernetes Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package daemonset
16+
17+
import (
18+
"context"
19+
"time"
20+
21+
v1 "k8s.io/api/apps/v1"
22+
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
client "k8s.io/client-go/kubernetes"
24+
)
25+
26+
const (
27+
// RestartedAtAnnotationKey is an annotation key for rollout restart
28+
RestartedAtAnnotationKey = "kubectl.kubernetes.io/restartedAt"
29+
)
30+
31+
// RestartDaemonSet restarts a daemon set in the manner of `kubectl rollout restart`.
32+
func RestartDaemonSet(client client.Interface, namespace, name string) (*v1.DaemonSet, error) {
33+
daemonSet, err := client.AppsV1().DaemonSets(namespace).Get(context.TODO(), name, metaV1.GetOptions{})
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
if daemonSet.Spec.Template.ObjectMeta.Annotations == nil {
39+
daemonSet.Spec.Template.ObjectMeta.Annotations = map[string]string{}
40+
}
41+
daemonSet.Spec.Template.ObjectMeta.Annotations[RestartedAtAnnotationKey] = time.Now().Format(time.RFC3339)
42+
return client.AppsV1().DaemonSets(namespace).Update(context.TODO(), daemonSet, metaV1.UpdateOptions{})
43+
}

modules/api/pkg/resource/statefulset/list_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ func TestGetStatefulSetListFromChannels(t *testing.T) {
148148
CreationTimestamp: metaV1.Unix(111, 222),
149149
},
150150
TypeMeta: types.TypeMeta{
151-
Kind: types.ResourceKindStatefulSet,
152-
Scalable: true,
151+
Kind: types.ResourceKindStatefulSet,
152+
Scalable: true,
153+
Restartable: true,
153154
},
154155
Pods: common.PodInfo{
155156
Current: 7,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2017 The Kubernetes Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package statefulset
16+
17+
import (
18+
"context"
19+
"time"
20+
21+
v1 "k8s.io/api/apps/v1"
22+
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
client "k8s.io/client-go/kubernetes"
24+
)
25+
26+
const (
27+
// RestartedAtAnnotationKey is an annotation key for rollout restart
28+
RestartedAtAnnotationKey = "kubectl.kubernetes.io/restartedAt"
29+
)
30+
31+
// RestartStatefulSet restarts a daemon set in the manner of `kubectl rollout restart`.
32+
func RestartStatefulSet(client client.Interface, namespace, name string) (*v1.StatefulSet, error) {
33+
statefulSet, err := client.AppsV1().StatefulSets(namespace).Get(context.TODO(), name, metaV1.GetOptions{})
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
if statefulSet.Spec.Template.ObjectMeta.Annotations == nil {
39+
statefulSet.Spec.Template.ObjectMeta.Annotations = map[string]string{}
40+
}
41+
statefulSet.Spec.Template.ObjectMeta.Annotations[RestartedAtAnnotationKey] = time.Now().Format(time.RFC3339)
42+
return client.AppsV1().StatefulSets(namespace).Update(context.TODO(), statefulSet, metaV1.UpdateOptions{})
43+
}

modules/common/types/resourcekind.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ func (k ResourceKind) Scalable() bool {
7575
func (k ResourceKind) Restartable() bool {
7676
restartable := []ResourceKind{
7777
ResourceKindDeployment,
78+
ResourceKindDaemonSet,
79+
ResourceKindStatefulSet,
7880
}
7981

8082
for _, kind := range restartable {

0 commit comments

Comments
 (0)