Unverified Commit ea92879f authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #62306 from freehan/pod-status-patch2

Automatic merge from submit-queue (batch tested with PRs 58920, 58327, 60577, 49388, 62306). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Use Patch instead of Put to sync pod status ref: https://github.com/kubernetes/community/blob/master/keps/sig-network/0007-pod-ready%2B%2B.md ```release-note Use Patch instead of Put to sync pod status ```
parents 6b2fc7cb 85e0d05a
...@@ -496,7 +496,6 @@ func TestStatefulSetControllerGetStatefulSetsForPod(t *testing.T) { ...@@ -496,7 +496,6 @@ func TestStatefulSetControllerGetStatefulSetsForPod(t *testing.T) {
func TestGetPodsForStatefulSetAdopt(t *testing.T) { func TestGetPodsForStatefulSetAdopt(t *testing.T) {
set := newStatefulSet(5) set := newStatefulSet(5)
ssc, spc := newFakeStatefulSetController(set)
pod1 := newStatefulSetPod(set, 1) pod1 := newStatefulSetPod(set, 1)
// pod2 is an orphan with matching labels and name. // pod2 is an orphan with matching labels and name.
pod2 := newStatefulSetPod(set, 2) pod2 := newStatefulSetPod(set, 2)
...@@ -510,6 +509,8 @@ func TestGetPodsForStatefulSetAdopt(t *testing.T) { ...@@ -510,6 +509,8 @@ func TestGetPodsForStatefulSetAdopt(t *testing.T) {
pod4.OwnerReferences = nil pod4.OwnerReferences = nil
pod4.Name = "x" + pod4.Name pod4.Name = "x" + pod4.Name
ssc, spc := newFakeStatefulSetController(set, pod1, pod2, pod3, pod4)
spc.podsIndexer.Add(pod1) spc.podsIndexer.Add(pod1)
spc.podsIndexer.Add(pod2) spc.podsIndexer.Add(pod2)
spc.podsIndexer.Add(pod3) spc.podsIndexer.Add(pod3)
......
...@@ -879,7 +879,7 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { ...@@ -879,7 +879,7 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) {
require.True(t, actions[1].Matches("patch", "nodes")) require.True(t, actions[1].Matches("patch", "nodes"))
require.Equal(t, actions[1].GetSubresource(), "status") require.Equal(t, actions[1].GetSubresource(), "status")
updatedNode, err := applyNodeStatusPatch(&existingNode, actions[1].(core.PatchActionImpl).GetPatch()) updatedNode, err := kubeClient.CoreV1().Nodes().Get(testKubeletHostname, metav1.GetOptions{})
require.NoError(t, err, "can't apply node status patch") require.NoError(t, err, "can't apply node status patch")
for i, cond := range updatedNode.Status.Conditions { for i, cond := range updatedNode.Status.Conditions {
...@@ -891,7 +891,6 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { ...@@ -891,7 +891,6 @@ func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) {
// Version skew workaround. See: https://github.com/kubernetes/kubernetes/issues/16961 // Version skew workaround. See: https://github.com/kubernetes/kubernetes/issues/16961
lastIndex := len(updatedNode.Status.Conditions) - 1 lastIndex := len(updatedNode.Status.Conditions) - 1
assert.Equal(t, v1.NodeReady, updatedNode.Status.Conditions[lastIndex].Type, "NodeReady should be the last condition") assert.Equal(t, v1.NodeReady, updatedNode.Status.Conditions[lastIndex].Type, "NodeReady should be the last condition")
assert.NotEmpty(t, updatedNode.Status.Conditions[lastIndex].Message) assert.NotEmpty(t, updatedNode.Status.Conditions[lastIndex].Message)
......
...@@ -19,6 +19,7 @@ go_library( ...@@ -19,6 +19,7 @@ go_library(
"//pkg/kubelet/pod:go_default_library", "//pkg/kubelet/pod:go_default_library",
"//pkg/kubelet/types:go_default_library", "//pkg/kubelet/types:go_default_library",
"//pkg/kubelet/util/format:go_default_library", "//pkg/kubelet/util/format:go_default_library",
"//pkg/util/pod:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
......
...@@ -37,6 +37,7 @@ import ( ...@@ -37,6 +37,7 @@ import (
kubepod "k8s.io/kubernetes/pkg/kubelet/pod" kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types" kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/kubelet/util/format" "k8s.io/kubernetes/pkg/kubelet/util/format"
statusutil "k8s.io/kubernetes/pkg/util/pod"
) )
// A wrapper around v1.PodStatus that includes a version to enforce that stale pod statuses are // A wrapper around v1.PodStatus that includes a version to enforce that stale pod statuses are
...@@ -121,11 +122,22 @@ func NewManager(kubeClient clientset.Interface, podManager kubepod.Manager, podD ...@@ -121,11 +122,22 @@ func NewManager(kubeClient clientset.Interface, podManager kubepod.Manager, podD
} }
} }
// isStatusEqual returns true if the given pod statuses are equal, false otherwise. // isPodStatusByKubeletEqual returns true if the given pod statuses are equal when non-kubelet-owned
// pod conditions are excluded.
// This method normalizes the status before comparing so as to make sure that meaningless // This method normalizes the status before comparing so as to make sure that meaningless
// changes will be ignored. // changes will be ignored.
func isStatusEqual(oldStatus, status *v1.PodStatus) bool { func isPodStatusByKubeletEqual(oldStatus, status *v1.PodStatus) bool {
return apiequality.Semantic.DeepEqual(status, oldStatus) oldCopy := oldStatus.DeepCopy()
for _, c := range status.Conditions {
if kubetypes.PodConditionByKubelet(c.Type) {
_, oc := podutil.GetPodCondition(oldCopy, c.Type)
if oc == nil || oc.Status != c.Status {
return false
}
}
}
oldCopy.Conditions = status.Conditions
return apiequality.Semantic.DeepEqual(oldCopy, status)
} }
func (m *manager) Start() { func (m *manager) Start() {
...@@ -162,6 +174,13 @@ func (m *manager) GetPodStatus(uid types.UID) (v1.PodStatus, bool) { ...@@ -162,6 +174,13 @@ func (m *manager) GetPodStatus(uid types.UID) (v1.PodStatus, bool) {
func (m *manager) SetPodStatus(pod *v1.Pod, status v1.PodStatus) { func (m *manager) SetPodStatus(pod *v1.Pod, status v1.PodStatus) {
m.podStatusesLock.Lock() m.podStatusesLock.Lock()
defer m.podStatusesLock.Unlock() defer m.podStatusesLock.Unlock()
for _, c := range pod.Status.Conditions {
if !kubetypes.PodConditionByKubelet(c.Type) {
glog.Errorf("Kubelet is trying to update pod condition %q for pod %q. "+
"But it is not owned by kubelet.", string(c.Type), format.Pod(pod))
}
}
// Make sure we're caching a deep copy. // Make sure we're caching a deep copy.
status = *status.DeepCopy() status = *status.DeepCopy()
...@@ -336,7 +355,7 @@ func (m *manager) updateStatusInternal(pod *v1.Pod, status v1.PodStatus, forceUp ...@@ -336,7 +355,7 @@ func (m *manager) updateStatusInternal(pod *v1.Pod, status v1.PodStatus, forceUp
normalizeStatus(pod, &status) normalizeStatus(pod, &status)
// The intent here is to prevent concurrent updates to a pod's status from // The intent here is to prevent concurrent updates to a pod's status from
// clobbering each other so the phase of a pod progresses monotonically. // clobbering each other so the phase of a pod progresses monotonically.
if isCached && isStatusEqual(&cachedStatus.status, &status) && !forceUpdate { if isCached && isPodStatusByKubeletEqual(&cachedStatus.status, &status) && !forceUpdate {
glog.V(3).Infof("Ignoring same status for pod %q, status: %+v", format.Pod(pod), status) glog.V(3).Infof("Ignoring same status for pod %q, status: %+v", format.Pod(pod), status)
return false // No new status. return false // No new status.
} }
...@@ -469,9 +488,10 @@ func (m *manager) syncPod(uid types.UID, status versionedPodStatus) { ...@@ -469,9 +488,10 @@ func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
m.deletePodStatus(uid) m.deletePodStatus(uid)
return return
} }
pod.Status = status.status
// TODO: handle conflict as a retry, make that easier too. oldStatus := pod.Status.DeepCopy()
newPod, err := m.kubeClient.CoreV1().Pods(pod.Namespace).UpdateStatus(pod) newPod, patchBytes, err := statusutil.PatchPodStatus(m.kubeClient, pod.Namespace, pod.Name, *oldStatus, mergePodStatus(*oldStatus, status.status))
glog.V(3).Infof("Patch status for pod %q with %q", format.Pod(pod), patchBytes)
if err != nil { if err != nil {
glog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err) glog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err)
return return
...@@ -546,7 +566,7 @@ func (m *manager) needsReconcile(uid types.UID, status v1.PodStatus) bool { ...@@ -546,7 +566,7 @@ func (m *manager) needsReconcile(uid types.UID, status v1.PodStatus) bool {
podStatus := pod.Status.DeepCopy() podStatus := pod.Status.DeepCopy()
normalizeStatus(pod, podStatus) normalizeStatus(pod, podStatus)
if isStatusEqual(podStatus, &status) { if isPodStatusByKubeletEqual(podStatus, &status) {
// If the status from the source is the same with the cached status, // If the status from the source is the same with the cached status,
// reconcile is not needed. Just return. // reconcile is not needed. Just return.
return false return false
...@@ -559,7 +579,7 @@ func (m *manager) needsReconcile(uid types.UID, status v1.PodStatus) bool { ...@@ -559,7 +579,7 @@ func (m *manager) needsReconcile(uid types.UID, status v1.PodStatus) bool {
// We add this function, because apiserver only supports *RFC3339* now, which means that the timestamp returned by // We add this function, because apiserver only supports *RFC3339* now, which means that the timestamp returned by
// apiserver has no nanosecond information. However, the timestamp returned by metav1.Now() contains nanosecond, // apiserver has no nanosecond information. However, the timestamp returned by metav1.Now() contains nanosecond,
// so when we do comparison between status from apiserver and cached status, isStatusEqual() will always return false. // so when we do comparison between status from apiserver and cached status, isPodStatusByKubeletEqual() will always return false.
// There is related issue #15262 and PR #15263 about this. // There is related issue #15262 and PR #15263 about this.
// In fact, the best way to solve this is to do it on api side. However, for now, we normalize the status locally in // In fact, the best way to solve this is to do it on api side. However, for now, we normalize the status locally in
// kubelet temporarily. // kubelet temporarily.
...@@ -613,3 +633,22 @@ func normalizeStatus(pod *v1.Pod, status *v1.PodStatus) *v1.PodStatus { ...@@ -613,3 +633,22 @@ func normalizeStatus(pod *v1.Pod, status *v1.PodStatus) *v1.PodStatus {
kubetypes.SortInitContainerStatuses(pod, status.InitContainerStatuses) kubetypes.SortInitContainerStatuses(pod, status.InitContainerStatuses)
return status return status
} }
// mergePodStatus merges oldPodStatus and newPodStatus where pod conditions
// not owned by kubelet is preserved from oldPodStatus
func mergePodStatus(oldPodStatus, newPodStatus v1.PodStatus) v1.PodStatus {
podConditions := []v1.PodCondition{}
for _, c := range oldPodStatus.Conditions {
if !kubetypes.PodConditionByKubelet(c.Type) {
podConditions = append(podConditions, c)
}
}
for _, c := range newPodStatus.Conditions {
if kubetypes.PodConditionByKubelet(c.Type) {
podConditions = append(podConditions, c)
}
}
newPodStatus.Conditions = podConditions
return newPodStatus
}
...@@ -12,6 +12,7 @@ go_library( ...@@ -12,6 +12,7 @@ go_library(
"constants.go", "constants.go",
"doc.go", "doc.go",
"labels.go", "labels.go",
"pod_status.go",
"pod_update.go", "pod_update.go",
"types.go", "types.go",
], ],
...@@ -29,6 +30,7 @@ go_test( ...@@ -29,6 +30,7 @@ go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"labels_test.go", "labels_test.go",
"pod_status_test.go",
"pod_update_test.go", "pod_update_test.go",
"types_test.go", "types_test.go",
], ],
......
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
import (
"k8s.io/api/core/v1"
)
// PodConditionsByKubelet is the list of pod conditions owned by kubelet
var PodConditionsByKubelet = []v1.PodConditionType{
v1.PodScheduled,
v1.PodReady,
v1.PodInitialized,
v1.PodReasonUnschedulable,
}
// PodConditionByKubelet returns if the pod condition type is owned by kubelet
func PodConditionByKubelet(conditionType v1.PodConditionType) bool {
for _, c := range PodConditionsByKubelet {
if c == conditionType {
return true
}
}
return false
}
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
import (
"k8s.io/api/core/v1"
"testing"
)
func TestPodConditionByKubelet(t *testing.T) {
trueCases := []v1.PodConditionType{
v1.PodScheduled,
v1.PodReady,
v1.PodInitialized,
v1.PodReasonUnschedulable,
}
for _, tc := range trueCases {
if !PodConditionByKubelet(tc) {
t.Errorf("Expect %q to be condition owned by kubelet.", tc)
}
}
falseCases := []v1.PodConditionType{
v1.PodConditionType("abcd"),
}
for _, tc := range falseCases {
if PodConditionByKubelet(tc) {
t.Errorf("Expect %q NOT to be condition owned by kubelet.", tc)
}
}
}
...@@ -45,6 +45,7 @@ filegroup( ...@@ -45,6 +45,7 @@ filegroup(
"//pkg/util/nsenter:all-srcs", "//pkg/util/nsenter:all-srcs",
"//pkg/util/oom:all-srcs", "//pkg/util/oom:all-srcs",
"//pkg/util/parsers:all-srcs", "//pkg/util/parsers:all-srcs",
"//pkg/util/pod:all-srcs",
"//pkg/util/pointer:all-srcs", "//pkg/util/pointer:all-srcs",
"//pkg/util/procfs:all-srcs", "//pkg/util/procfs:all-srcs",
"//pkg/util/reflector/prometheus:all-srcs", "//pkg/util/reflector/prometheus:all-srcs",
......
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["pod.go"],
importpath = "k8s.io/kubernetes/pkg/util/pod",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["pod_test.go"],
embed = [":go_default_library"],
deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pod
import (
"encoding/json"
"fmt"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
clientset "k8s.io/client-go/kubernetes"
)
// PatchPodStatus patches pod status.
func PatchPodStatus(c clientset.Interface, namespace, name string, oldPodStatus, newPodStatus v1.PodStatus) (*v1.Pod, []byte, error) {
patchBytes, err := preparePatchBytesforPodStatus(namespace, name, oldPodStatus, newPodStatus)
if err != nil {
return nil, nil, err
}
updatedPod, err := c.CoreV1().Pods(namespace).Patch(name, types.StrategicMergePatchType, patchBytes, "status")
if err != nil {
return nil, nil, fmt.Errorf("failed to patch status %q for pod %q/%q: %v", patchBytes, namespace, name, err)
}
return updatedPod, patchBytes, nil
}
func preparePatchBytesforPodStatus(namespace, name string, oldPodStatus, newPodStatus v1.PodStatus) ([]byte, error) {
oldData, err := json.Marshal(v1.Pod{
Status: oldPodStatus,
})
if err != nil {
return nil, fmt.Errorf("failed to Marshal oldData for pod %q/%q: %v", namespace, name, err)
}
newData, err := json.Marshal(v1.Pod{
Status: newPodStatus,
})
if err != nil {
return nil, fmt.Errorf("failed to Marshal newData for pod %q/%q: %v", namespace, name, err)
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Pod{})
if err != nil {
return nil, fmt.Errorf("failed to CreateTwoWayMergePatch for pod %q/%q: %v", namespace, name, err)
}
return patchBytes, nil
}
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pod
import (
"testing"
"fmt"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"reflect"
)
func TestPatchPodStatus(t *testing.T) {
ns := "ns"
name := "name"
client := &fake.Clientset{}
client.CoreV1().Pods(ns).Create(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
})
testCases := []struct {
description string
mutate func(input v1.PodStatus) v1.PodStatus
expectedPatchBytes []byte
}{
{
"no change",
func(input v1.PodStatus) v1.PodStatus { return input },
[]byte(fmt.Sprintf(`{}`)),
},
{
"message change",
func(input v1.PodStatus) v1.PodStatus {
input.Message = "random message"
return input
},
[]byte(fmt.Sprintf(`{"status":{"message":"random message"}}`)),
},
{
"pod condition change",
func(input v1.PodStatus) v1.PodStatus {
input.Conditions[0].Status = v1.ConditionFalse
return input
},
[]byte(fmt.Sprintf(`{"status":{"$setElementOrder/conditions":[{"type":"Ready"},{"type":"PodScheduled"}],"conditions":[{"status":"False","type":"Ready"}]}}`)),
},
{
"additional init container condition",
func(input v1.PodStatus) v1.PodStatus {
input.InitContainerStatuses = []v1.ContainerStatus{
{
Name: "init-container",
Ready: true,
},
}
return input
},
[]byte(fmt.Sprintf(`{"status":{"initContainerStatuses":[{"image":"","imageID":"","lastState":{},"name":"init-container","ready":true,"restartCount":0,"state":{}}]}}`)),
},
}
for _, tc := range testCases {
_, patchBytes, err := PatchPodStatus(client, ns, name, getPodStatus(), tc.mutate(getPodStatus()))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(patchBytes, tc.expectedPatchBytes) {
t.Errorf("for test case %q, expect patchBytes: %q, got: %q\n", tc.description, tc.expectedPatchBytes, patchBytes)
}
}
}
func getPodStatus() v1.PodStatus {
return v1.PodStatus{
Phase: v1.PodRunning,
Conditions: []v1.PodCondition{
{
Type: v1.PodReady,
Status: v1.ConditionTrue,
},
{
Type: v1.PodScheduled,
Status: v1.ConditionTrue,
},
},
ContainerStatuses: []v1.ContainerStatus{
{
Name: "container1",
Ready: true,
},
{
Name: "container2",
Ready: true,
},
},
Message: "Message",
}
}
...@@ -119,7 +119,7 @@ func NodeRules() []rbacv1.PolicyRule { ...@@ -119,7 +119,7 @@ func NodeRules() []rbacv1.PolicyRule {
rbacv1helpers.NewRule("create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(), rbacv1helpers.NewRule("create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// Needed for the node to report status of pods it is running. // Needed for the node to report status of pods it is running.
// Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself. // Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself.
rbacv1helpers.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(), rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
// Needed for the node to create pod evictions. // Needed for the node to create pod evictions.
// Use the NodeRestriction admission plugin to limit a node to creating evictions for pods bound to itself. // Use the NodeRestriction admission plugin to limit a node to creating evictions for pods bound to itself.
rbacv1helpers.NewRule("create").Groups(legacyGroup).Resources("pods/eviction").RuleOrDie(), rbacv1helpers.NewRule("create").Groups(legacyGroup).Resources("pods/eviction").RuleOrDie(),
......
...@@ -1098,6 +1098,7 @@ items: ...@@ -1098,6 +1098,7 @@ items:
resources: resources:
- pods/status - pods/status
verbs: verbs:
- patch
- update - update
- apiGroups: - apiGroups:
- "" - ""
......
...@@ -22,6 +22,8 @@ go_library( ...@@ -22,6 +22,8 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
], ],
......
...@@ -25,6 +25,8 @@ import ( ...@@ -25,6 +25,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
) )
...@@ -72,7 +74,6 @@ func ObjectReaction(tracker ObjectTracker) ReactionFunc { ...@@ -72,7 +74,6 @@ func ObjectReaction(tracker ObjectTracker) ReactionFunc {
return func(action Action) (bool, runtime.Object, error) { return func(action Action) (bool, runtime.Object, error) {
ns := action.GetNamespace() ns := action.GetNamespace()
gvr := action.GetResource() gvr := action.GetResource()
// Here and below we need to switch on implementation types, // Here and below we need to switch on implementation types,
// not on interfaces, as some interfaces are identical // not on interfaces, as some interfaces are identical
// (e.g. UpdateAction and CreateAction), so if we use them, // (e.g. UpdateAction and CreateAction), so if we use them,
...@@ -125,6 +126,34 @@ func ObjectReaction(tracker ObjectTracker) ReactionFunc { ...@@ -125,6 +126,34 @@ func ObjectReaction(tracker ObjectTracker) ReactionFunc {
} }
return true, nil, nil return true, nil, nil
case PatchActionImpl:
obj, err := tracker.Get(gvr, ns, action.GetName())
if err != nil {
// object is not registered
return false, nil, err
}
old, err := json.Marshal(obj)
if err != nil {
return true, nil, err
}
// Only supports strategic merge patch
// TODO: Add support for other Patch types
mergedByte, err := strategicpatch.StrategicMergePatch(old, action.GetPatch(), obj)
if err != nil {
return true, nil, err
}
if err = json.Unmarshal(mergedByte, obj); err != nil {
return true, nil, err
}
if err = tracker.Update(gvr, obj, ns); err != nil {
return true, nil, err
}
return true, obj, nil
default: default:
return false, nil, fmt.Errorf("no reaction implemented for %s", action) return false, nil, fmt.Errorf("no reaction implemented for %s", action)
} }
......
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
], ],
"Deps": [ "Deps": [
{ {
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
},
{
"ImportPath": "github.com/ghodss/yaml", "ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
}, },
...@@ -363,6 +367,10 @@ ...@@ -363,6 +367,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/net", "ImportPath": "k8s.io/apimachinery/pkg/util/net",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -375,6 +383,10 @@ ...@@ -375,6 +383,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation", "ImportPath": "k8s.io/apimachinery/pkg/util/validation",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -399,6 +411,10 @@ ...@@ -399,6 +411,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect", "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -465,6 +481,10 @@ ...@@ -465,6 +481,10 @@
{ {
"ImportPath": "k8s.io/client-go/util/integer", "ImportPath": "k8s.io/client-go/util/integer",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "86e28c192d2743f0232b9bc5f0a531568ef9f2a5"
} }
] ]
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment