func applyNodeStatusPatch(originalNode *v1.Node, patch []byte) (*v1.Node, error) { original, err := json.Marshal(originalNode) if err != nil { return nil, fmt.Errorf("failed to marshal original node %#v: %v", originalNode, err) } updated, err := strategicpatch.StrategicMergePatch(original, patch, v1.Node{}) if err != nil { return nil, fmt.Errorf("failed to apply strategic merge patch %q on node %#v: %v", patch, originalNode, err) } updatedNode := &v1.Node{} if err := json.Unmarshal(updated, updatedNode); err != nil { return nil, fmt.Errorf("failed to unmarshal updated node %q: %v", updated, err) } return updatedNode, nil }
func patchClusterSpec(cs *cluster.ClusterSpec, p []byte) (*cluster.ClusterSpec, error) { csj, err := json.Marshal(cs) if err != nil { return nil, fmt.Errorf("failed to marshal cluster spec: %v", err) } newcsj, err := strategicpatch.StrategicMergePatch(csj, p, &cluster.ClusterSpec{}) if err != nil { return nil, fmt.Errorf("failed to merge patch cluster spec: %v", err) } var newcs *cluster.ClusterSpec if err := json.Unmarshal(newcsj, &newcs); err != nil { return nil, fmt.Errorf("failed to unmarshal patched cluster spec: %v", err) } return newcs, nil }
func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) { switch patchType { case types.JSONPatchType: patchObj, err := jsonpatch.DecodePatch(patchJS) if err != nil { return nil, err } return patchObj.Apply(originalJS) case types.MergePatchType: return jsonpatch.MergePatch(originalJS, patchJS) case types.StrategicMergePatchType: return strategicpatch.StrategicMergePatch(originalJS, patchJS, obj) default: // only here as a safety net - go-restful filters content-type return nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType) } }
// patchObjectJSON patches the <originalObject> with <patchJS> and stores // the result in <objToUpdate>. // Currently it also returns the original and patched objects serialized to // JSONs (this may not be needed once we can apply patches at the // map[string]interface{} level). func patchObjectJSON( patchType types.PatchType, codec runtime.Codec, originalObject runtime.Object, patchJS []byte, objToUpdate runtime.Object, versionedObj runtime.Object, ) (originalObjJS []byte, patchedObjJS []byte, retErr error) { js, err := runtime.Encode(codec, originalObject) if err != nil { return nil, nil, err } originalObjJS = js switch patchType { case types.JSONPatchType: patchObj, err := jsonpatch.DecodePatch(patchJS) if err != nil { return nil, nil, err } if patchedObjJS, err = patchObj.Apply(originalObjJS); err != nil { return nil, nil, err } case types.MergePatchType: if patchedObjJS, err = jsonpatch.MergePatch(originalObjJS, patchJS); err != nil { return nil, nil, err } case types.StrategicMergePatchType: if patchedObjJS, err = strategicpatch.StrategicMergePatch(originalObjJS, patchJS, versionedObj); err != nil { return nil, nil, err } default: // only here as a safety net - go-restful filters content-type return nil, nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType) } if err := runtime.DecodeInto(codec, patchedObjJS, objToUpdate); err != nil { return nil, nil, err } return }
func OnPatchFactory(testCache map[string]*api.Event, patchEvent chan<- *api.Event) OnPatchFunc { return func(event *api.Event, patch []byte) (*api.Event, error) { cachedEvent, found := testCache[getEventKey(event)] if !found { return nil, fmt.Errorf("unexpected error: couldn't find Event in testCache.") } originalData, err := json.Marshal(cachedEvent) if err != nil { return nil, fmt.Errorf("unexpected error: %v", err) } patched, err := strategicpatch.StrategicMergePatch(originalData, patch, event) if err != nil { return nil, fmt.Errorf("unexpected error: %v", err) } patchedObj := &api.Event{} err = json.Unmarshal(patched, patchedObj) if err != nil { return nil, fmt.Errorf("unexpected error: %v", err) } patchEvent <- patchedObj return patchedObj, nil } }
func TestTaint(t *testing.T) { tests := []struct { description string oldTaints []api.Taint newTaints []api.Taint args []string expectFatal bool expectTaint bool }{ // success cases { description: "taints a node with effect NoSchedule", newTaints: []api.Taint{{ Key: "foo", Value: "bar", Effect: "NoSchedule", }}, args: []string{"node", "node-name", "foo=bar:NoSchedule"}, expectFatal: false, expectTaint: true, }, { description: "taints a node with effect PreferNoSchedule", newTaints: []api.Taint{{ Key: "foo", Value: "bar", Effect: "PreferNoSchedule", }}, args: []string{"node", "node-name", "foo=bar:PreferNoSchedule"}, expectFatal: false, expectTaint: true, }, { description: "update an existing taint on the node, change the value from bar to barz", oldTaints: []api.Taint{{ Key: "foo", Value: "bar", Effect: "NoSchedule", }}, newTaints: []api.Taint{{ Key: "foo", Value: "barz", Effect: "NoSchedule", }}, args: []string{"node", "node-name", "foo=barz:NoSchedule", "--overwrite"}, expectFatal: false, expectTaint: true, }, { description: "taints a node with two taints", newTaints: []api.Taint{{ Key: "dedicated", Value: "namespaceA", Effect: "NoSchedule", }, { Key: "foo", Value: "bar", Effect: "PreferNoSchedule", }}, args: []string{"node", "node-name", "dedicated=namespaceA:NoSchedule", "foo=bar:PreferNoSchedule"}, expectFatal: false, expectTaint: true, }, { description: "node has two taints with the same key but different effect, remove one of them by indicating exact key and effect", oldTaints: []api.Taint{{ Key: "dedicated", Value: "namespaceA", Effect: "NoSchedule", }, { Key: "dedicated", Value: "namespaceA", Effect: "PreferNoSchedule", }}, newTaints: []api.Taint{{ Key: "dedicated", Value: "namespaceA", Effect: "PreferNoSchedule", }}, args: []string{"node", "node-name", "dedicated:NoSchedule-"}, expectFatal: false, expectTaint: true, }, { description: "node has two taints with the same key but different effect, remove all of them with wildcard", oldTaints: []api.Taint{{ Key: "dedicated", Value: "namespaceA", Effect: "NoSchedule", }, { Key: "dedicated", Value: "namespaceA", Effect: "PreferNoSchedule", }}, newTaints: []api.Taint{}, args: []string{"node", "node-name", "dedicated-"}, expectFatal: false, expectTaint: true, }, { description: "node has two taints, update one of them and remove the other", oldTaints: []api.Taint{{ Key: "dedicated", Value: "namespaceA", Effect: "NoSchedule", }, { Key: "foo", Value: "bar", Effect: "PreferNoSchedule", }}, newTaints: []api.Taint{{ Key: "foo", Value: "barz", Effect: "PreferNoSchedule", }}, args: []string{"node", "node-name", "dedicated:NoSchedule-", "foo=barz:PreferNoSchedule", "--overwrite"}, expectFatal: false, expectTaint: true, }, // error cases { description: "invalid taint key", args: []string{"node", "node-name", "nospecialchars^@=banana:NoSchedule"}, expectFatal: true, expectTaint: false, }, { description: "invalid taint effect", args: []string{"node", "node-name", "foo=bar:NoExcute"}, expectFatal: true, expectTaint: false, }, { description: "duplicated taints with the same key and effect should be rejected", args: []string{"node", "node-name", "foo=bar:NoExcute", "foo=barz:NoExcute"}, expectFatal: true, expectTaint: false, }, { description: "can't update existing taint on the node, since 'overwrite' flag is not set", oldTaints: []api.Taint{{ Key: "foo", Value: "bar", Effect: "NoSchedule", }}, newTaints: []api.Taint{{ Key: "foo", Value: "bar", Effect: "NoSchedule", }}, args: []string{"node", "node-name", "foo=bar:NoSchedule"}, expectFatal: true, expectTaint: false, }, } for _, test := range tests { oldNode, expectNewNode := generateNodeAndTaintedNode(test.oldTaints, test.newTaints) new_node := &api.Node{} tainted := false f, tf, codec, ns := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ NegotiatedSerializer: ns, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { m := &MyReq{req} switch { case m.isFor("GET", "/nodes/node-name"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, oldNode)}, nil case m.isFor("PATCH", "/nodes/node-name"): tainted = true data, err := ioutil.ReadAll(req.Body) if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } defer req.Body.Close() // apply the patch oldJSON, err := runtime.Encode(codec, oldNode) if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } appliedPatch, err := strategicpatch.StrategicMergePatch(oldJSON, data, &v1.Node{}) if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } // decode the patch if err := runtime.DecodeInto(codec, appliedPatch, new_node); err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } if !AnnotationsHaveEqualTaints(expectNewNode.Annotations, new_node.Annotations) { t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, expectNewNode.Annotations, new_node.Annotations) } return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, new_node)}, nil case m.isFor("PUT", "/nodes/node-name"): tainted = true data, err := ioutil.ReadAll(req.Body) if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } defer req.Body.Close() if err := runtime.DecodeInto(codec, data, new_node); err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } if !AnnotationsHaveEqualTaints(expectNewNode.Annotations, new_node.Annotations) { t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, expectNewNode.Annotations, new_node.Annotations) } return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, new_node)}, nil default: t.Fatalf("%s: unexpected request: %v %#v\n%#v", test.description, req.Method, req.URL, req) return nil, nil } }), } tf.ClientConfig = defaultClientConfig() buf := bytes.NewBuffer([]byte{}) cmd := NewCmdTaint(f, buf) saw_fatal := false func() { defer func() { // Recover from the panic below. _ = recover() // Restore cmdutil behavior cmdutil.DefaultBehaviorOnFatal() }() cmdutil.BehaviorOnFatal(func(e string, code int) { saw_fatal = true; panic(e) }) cmd.SetArgs(test.args) cmd.Execute() }() if test.expectFatal { if !saw_fatal { t.Fatalf("%s: unexpected non-error", test.description) } } if test.expectTaint { if !tainted { t.Fatalf("%s: node not tainted", test.description) } } if !test.expectTaint { if tainted { t.Fatalf("%s: unexpected taint", test.description) } } } }