// LoadPodFromFile will read, decode, and return a Pod from a file. func LoadPodFromFile(filePath string) (*api.Pod, error) { if filePath == "" { return nil, fmt.Errorf("file path not specified") } podDef, err := ioutil.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read file path %s: %+v", filePath, err) } if len(podDef) == 0 { return nil, fmt.Errorf("file was empty: %s", filePath) } pod := &api.Pod{} codec := api.Codecs.LegacyCodec(registered.GroupOrDie(api.GroupName).GroupVersion) if err := runtime.DecodeInto(codec, podDef, pod); err != nil { return nil, fmt.Errorf("failed decoding file: %v", err) } return pod, nil }
func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { printer := spew.ConfigState{DisableMethods: true} original := item copied, err := api.Scheme.DeepCopy(item) if err != nil { panic(fmt.Sprintf("unable to copy: %v", err)) } item = copied.(runtime.Object) name := reflect.TypeOf(item).Elem().Name() data, err := runtime.Encode(codec, item) if err != nil { t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item)) return } if !api.Semantic.DeepEqual(original, item) { t.Errorf("0: %v: encode altered the object, diff: %v", name, diff.ObjectReflectDiff(original, item)) return } obj2, err := runtime.Decode(codec, data) if err != nil { t.Errorf("0: %v: %v\nCodec: %#v\nData: %s\nSource: %#v", name, err, codec, dataAsString(data), printer.Sprintf("%#v", item)) panic("failed") } if !api.Semantic.DeepEqual(original, obj2) { t.Errorf("\n1: %v: diff: %v\nCodec: %#v\nSource:\n\n%#v\n\nEncoded:\n\n%s\n\nFinal:\n\n%#v", name, diff.ObjectReflectDiff(item, obj2), codec, printer.Sprintf("%#v", item), dataAsString(data), printer.Sprintf("%#v", obj2)) return } obj3 := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Errorf("2: %v: %v", name, err) return } if !api.Semantic.DeepEqual(item, obj3) { t.Errorf("3: %v: diff: %v\nCodec: %#v", name, diff.ObjectReflectDiff(item, obj3), codec) return } }
func (o *DrainOptions) getPodCreator(pod api.Pod) (*api.SerializedReference, error) { creatorRef, found := pod.ObjectMeta.Annotations[controller.CreatedByAnnotation] if !found { return nil, nil } // Now verify that the specified creator actually exists. sr := &api.SerializedReference{} if err := runtime.DecodeInto(o.factory.Decoder(true), []byte(creatorRef), sr); err != nil { return nil, err } // We assume the only reason for an error is because the controller is // gone/missing, not for any other cause. TODO(mml): something more // sophisticated than this _, err := o.getController(sr) if err != nil { return nil, err } return sr, nil }
func BenchmarkDecodeIntoInternalCodec(b *testing.B) { codec := testapi.Default.Codec() items := benchmarkItems() width := len(items) encoded := make([][]byte, width) for i := range items { data, err := runtime.Encode(codec, &items[i]) if err != nil { b.Fatal(err) } encoded[i] = data } b.ResetTimer() for i := 0; i < b.N; i++ { obj := api.Pod{} if err := runtime.DecodeInto(codec, encoded[i%width], &obj); err != nil { b.Fatal(err) } } b.StopTimer() }
// The label kubernetesPodLabel is added a long time ago (#7421), it serialized the whole api.Pod to a docker label. // We want to remove this label because it serialized too much useless information. However kubelet may still work // with old containers which only have this label for a long time until we completely deprecate the old label. // Before that to ensure correctness we have to supply information with the old labels when newly added labels // are not available. // TODO(random-liu): Remove this function when we can completely remove label kubernetesPodLabel, probably after // dropping support for v1.1. func supplyContainerInfoWithOldLabel(labels map[string]string, containerInfo *labelledContainerInfo) { // Get api.Pod from old label var pod *api.Pod data, found := labels[kubernetesPodLabel] if !found { // Don't report any error here, because it's normal that a container has no pod label, especially // when we gradually deprecate the old label return } pod = &api.Pod{} if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(data), pod); err != nil { // If the pod label can't be parsed, we should report an error logError(containerInfo, kubernetesPodLabel, err) return } if containerInfo.PodDeletionGracePeriod == nil { containerInfo.PodDeletionGracePeriod = pod.DeletionGracePeriodSeconds } if containerInfo.PodTerminationGracePeriod == nil { containerInfo.PodTerminationGracePeriod = pod.Spec.TerminationGracePeriodSeconds } // Get api.Container from api.Pod var container *api.Container for i := range pod.Spec.Containers { if pod.Spec.Containers[i].Name == containerInfo.Name { container = &pod.Spec.Containers[i] break } } if container == nil { glog.Errorf("Unable to find container %q in pod %q", containerInfo.Name, format.Pod(pod)) return } if containerInfo.PreStopHandler == nil && container.Lifecycle != nil { containerInfo.PreStopHandler = container.Lifecycle.PreStop } }
func TestSavePodToFile(t *testing.T) { pod := volume.NewPersistentVolumeRecyclerPodTemplate() // sets all default values on a pod for equality comparison after decoding from file codec := api.Codecs.LegacyCodec(registered.GroupOrDie(api.GroupName).GroupVersion) encoded, err := runtime.Encode(codec, pod) runtime.DecodeInto(codec, encoded, pod) tmpDir := utiltesting.MkTmpdirOrDie("kube-io-test") defer os.RemoveAll(tmpDir) path := fmt.Sprintf("/%s/kube-io-test-%s", tmpDir, uuid.New()) if err := io.SavePodToFile(pod, path, 777); err != nil { t.Fatalf("failed to save pod to file: %v", err) } podFromFile, err := io.LoadPodFromFile(path) if err != nil { t.Fatalf("failed to load pod from file: %v", err) } if !api.Semantic.DeepEqual(pod, podFromFile) { t.Errorf("\nexpected %#v\ngot %#v\n", pod, podFromFile) } }
func TestGenerateService(t *testing.T) { tests := []struct { port string args []string serviceGenerator string params map[string]interface{} expectErr bool name string service api.Service expectPOST bool }{ { port: "80", args: []string{"foo"}, serviceGenerator: "service/v2", params: map[string]interface{}{ "name": "foo", }, expectErr: false, name: "basic", service: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "foo", }, Spec: api.ServiceSpec{ Ports: []api.ServicePort{ { Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(80), }, }, Selector: map[string]string{ "run": "foo", }, Type: api.ServiceTypeClusterIP, SessionAffinity: api.ServiceAffinityNone, }, }, expectPOST: true, }, { port: "80", args: []string{"foo"}, serviceGenerator: "service/v2", params: map[string]interface{}{ "name": "foo", "labels": "app=bar", }, expectErr: false, name: "custom labels", service: api.Service{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{"app": "bar"}, }, Spec: api.ServiceSpec{ Ports: []api.ServicePort{ { Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(80), }, }, Selector: map[string]string{ "app": "bar", }, Type: api.ServiceTypeClusterIP, SessionAffinity: api.ServiceAffinityNone, }, }, expectPOST: true, }, { expectErr: true, name: "missing port", expectPOST: false, }, { port: "80", args: []string{"foo"}, serviceGenerator: "service/v2", params: map[string]interface{}{ "name": "foo", }, expectErr: false, name: "dry-run", expectPOST: false, }, } for _, test := range tests { sawPOST := false f, tf, codec := NewAPIFactory() tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} tf.Client = &fake.RESTClient{ Codec: codec, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case test.expectPOST && m == "POST" && p == "/namespaces/namespace/services": sawPOST = true body := objBody(codec, &test.service) data, err := ioutil.ReadAll(req.Body) if err != nil { t.Errorf("unexpected error: %v", err) t.FailNow() } defer req.Body.Close() svc := &api.Service{} if err := runtime.DecodeInto(codec, data, svc); err != nil { t.Errorf("unexpected error: %v", err) t.FailNow() } // Copy things that are defaulted by the system test.service.Annotations = svc.Annotations if !reflect.DeepEqual(&test.service, svc) { t.Errorf("expected:\n%v\nsaw:\n%v\n", &test.service, svc) } return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil default: // Ensures no GET is performed when deleting by name t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req) return nil, fmt.Errorf("unexpected request") } }), } cmd := &cobra.Command{} cmd.Flags().String("output", "", "") cmd.Flags().Bool(cmdutil.ApplyAnnotationsFlag, false, "") cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation.") cmdutil.AddInclude3rdPartyFlags(cmd) addRunFlags(cmd) if !test.expectPOST { cmd.Flags().Set("dry-run", "true") } if len(test.port) > 0 { cmd.Flags().Set("port", test.port) test.params["port"] = test.port } buff := &bytes.Buffer{} err := generateService(f, cmd, test.args, test.serviceGenerator, test.params, "namespace", buff) if test.expectErr { if err == nil { t.Error("unexpected non-error") } continue } if err != nil { t.Errorf("unexpected error: %v", err) } if test.expectPOST != sawPOST { t.Errorf("expectPost: %v, sawPost: %v", test.expectPOST, sawPOST) } } }
func TestCodec(t *testing.T) { tests := []struct { into runtime.Object obj *Foo expectErr bool name string }{ { into: &runtime.VersionedObjects{}, obj: &Foo{ ObjectMeta: api.ObjectMeta{Name: "bar"}, TypeMeta: unversioned.TypeMeta{APIVersion: "company.com/v1", Kind: "Foo"}, }, expectErr: false, name: "versioned objects list", }, { obj: &Foo{ObjectMeta: api.ObjectMeta{Name: "bar"}}, expectErr: true, name: "missing kind", }, { obj: &Foo{ ObjectMeta: api.ObjectMeta{Name: "bar"}, TypeMeta: unversioned.TypeMeta{APIVersion: "company.com/v1", Kind: "Foo"}, }, name: "basic", }, { obj: &Foo{ ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "baz"}, TypeMeta: unversioned.TypeMeta{APIVersion: "company.com/v1", Kind: "Foo"}, }, name: "resource version", }, { obj: &Foo{ ObjectMeta: api.ObjectMeta{ Name: "bar", CreationTimestamp: unversioned.Time{Time: time.Unix(100, 0)}, }, TypeMeta: unversioned.TypeMeta{ APIVersion: "company.com/v1", Kind: "Foo", }, }, name: "creation time", }, { obj: &Foo{ ObjectMeta: api.ObjectMeta{ Name: "bar", ResourceVersion: "baz", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, TypeMeta: unversioned.TypeMeta{APIVersion: "company.com/v1", Kind: "Foo"}, }, name: "labels", }, } registered.AddThirdPartyAPIGroupVersions(unversioned.GroupVersion{Group: "company.com", Version: "v1"}) for _, test := range tests { d := &thirdPartyResourceDataDecoder{kind: "Foo", delegate: testapi.Extensions.Codec()} e := &thirdPartyResourceDataEncoder{gvk: unversioned.GroupVersionKind{ Group: "company.com", Version: "v1", Kind: "Foo", }, delegate: testapi.Extensions.Codec()} data, err := json.Marshal(test.obj) if err != nil { t.Errorf("[%s] unexpected error: %v", test.name, err) continue } var obj runtime.Object if test.into != nil { err = runtime.DecodeInto(d, data, test.into) obj = test.into } else { obj, err = runtime.Decode(d, data) } if err != nil && !test.expectErr { t.Errorf("[%s] unexpected error: %v", test.name, err) continue } if test.expectErr { if err == nil { t.Errorf("[%s] unexpected non-error", test.name) } continue } var rsrcObj *extensions.ThirdPartyResourceData switch o := obj.(type) { case *extensions.ThirdPartyResourceData: rsrcObj = o case *runtime.VersionedObjects: rsrcObj = o.First().(*extensions.ThirdPartyResourceData) default: t.Errorf("[%s] unexpected object: %v", test.name, obj) continue } if !reflect.DeepEqual(rsrcObj.ObjectMeta, test.obj.ObjectMeta) { t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, rsrcObj.ObjectMeta, test.obj.ObjectMeta) } var output Foo if err := json.Unmarshal(rsrcObj.Data, &output); err != nil { t.Errorf("[%s] unexpected error: %v", test.name, err) continue } if !reflect.DeepEqual(&output, test.obj) { t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output) } data, err = runtime.Encode(e, rsrcObj) if err != nil { t.Errorf("[%s] unexpected error: %v", test.name, err) } var output2 Foo if err := json.Unmarshal(data, &output2); err != nil { t.Errorf("[%s] unexpected error: %v", test.name, err) continue } if !reflect.DeepEqual(&output2, test.obj) { t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output2) } } }
func TestScheme(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{}) scheme.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{}) // If set, would clear TypeMeta during conversion. //scheme.AddIgnoredConversionType(&TypeMeta{}, &TypeMeta{}) // test that scheme is an ObjectTyper var _ runtime.ObjectTyper = scheme internalToExternalCalls := 0 externalToInternalCalls := 0 // Register functions to verify that scope.Meta() gets set correctly. err := scheme.AddConversionFuncs( func(in *InternalSimple, out *ExternalSimple, scope conversion.Scope) error { scope.Convert(&in.TypeMeta, &out.TypeMeta, 0) scope.Convert(&in.TestString, &out.TestString, 0) internalToExternalCalls++ return nil }, func(in *ExternalSimple, out *InternalSimple, scope conversion.Scope) error { scope.Convert(&in.TypeMeta, &out.TypeMeta, 0) scope.Convert(&in.TestString, &out.TestString, 0) externalToInternalCalls++ return nil }, ) if err != nil { t.Fatalf("unexpected error: %v", err) } codecs := serializer.NewCodecFactory(scheme) codec := codecs.LegacyCodec(externalGV) jsonserializer, _ := codecs.SerializerForFileExtension("json") simple := &InternalSimple{ TestString: "foo", } // Test Encode, Decode, DecodeInto, and DecodeToVersion obj := runtime.Object(simple) data, err := runtime.Encode(codec, obj) if err != nil { t.Fatal(err) } obj2, err := runtime.Decode(codec, data) if err != nil { t.Fatal(err) } if _, ok := obj2.(*InternalSimple); !ok { t.Fatalf("Got wrong type") } if e, a := simple, obj2; !reflect.DeepEqual(e, a) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) } obj3 := &InternalSimple{} if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Fatal(err) } // clearing TypeMeta is a function of the scheme, which we do not test here (ConvertToVersion // does not automatically clear TypeMeta anymore). simple.TypeMeta = runtime.TypeMeta{Kind: "Simple", APIVersion: externalGV.String()} if e, a := simple, obj3; !reflect.DeepEqual(e, a) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) } obj4, err := runtime.Decode(jsonserializer, data) if err != nil { t.Fatal(err) } if _, ok := obj4.(*ExternalSimple); !ok { t.Fatalf("Got wrong type") } // Test Convert external := &ExternalSimple{} err = scheme.Convert(simple, external) if err != nil { t.Fatalf("Unexpected error: %v", err) } if e, a := simple.TestString, external.TestString; e != a { t.Errorf("Expected %v, got %v", e, a) } // Encode and Convert should each have caused an increment. if e, a := 2, internalToExternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } // DecodeInto and Decode should each have caused an increment because of a conversion if e, a := 2, externalToInternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } }
func TestDecode(t *testing.T) { wire1 := []byte{ 0x6b, 0x38, 0x73, 0x00, // prefix 0x0a, 0x04, 0x0a, 0x00, // apiversion 0x12, 0x00, // kind 0x12, 0x00, // data 0x1a, 0x00, // content-type 0x22, 0x00, // content-encoding } wire2 := []byte{ 0x6b, 0x38, 0x73, 0x00, // prefix 0x0a, 0x15, 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // apiversion 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, // kind 0x12, 0x03, 0x01, 0x02, 0x03, // data 0x1a, 0x00, // content-type 0x22, 0x00, // content-encoding } //err1 := fmt.Errorf("a test error") testCases := []struct { obj runtime.Object data []byte errFn func(error) bool }{ { obj: &runtime.Unknown{}, errFn: func(err error) bool { return err.Error() == "empty data" }, }, { data: []byte{0x6b}, errFn: func(err error) bool { return strings.Contains(err.Error(), "does not appear to be a protobuf message") }, }, { obj: &runtime.Unknown{ ContentType: "application/protobuf", Raw: []byte{}, }, data: wire1, }, { obj: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{ APIVersion: "other/version", Kind: "test", }, ContentType: "application/protobuf", Raw: []byte{0x01, 0x02, 0x03}, }, data: wire2, }, } for i, test := range testCases { s := protobuf.NewSerializer(nil, nil, "application/protobuf") unk := &runtime.Unknown{} err := runtime.DecodeInto(s, test.data, unk) switch { case err == nil && test.errFn != nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil && test.errFn == nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil: if !test.errFn(err) { t.Errorf("%d: failed: %v", i, err) } continue } if !reflect.DeepEqual(unk, test.obj) { t.Errorf("%d: unexpected object:\n%#v", i, unk) continue } } }
// patchResource divides PatchResource for easier unit testing func patchResource( ctx api.Context, admit updateAdmissionFunc, timeout time.Duration, versionedObj runtime.Object, patcher rest.Patcher, name string, patchType api.PatchType, patchJS []byte, namer ScopeNamer, copier runtime.ObjectCopier, resource unversioned.GroupVersionResource, codec runtime.Codec, ) (runtime.Object, error) { namespace := api.NamespaceValue(ctx) var ( originalObjJS []byte originalPatchedObjJS []byte lastConflictErr error ) // applyPatch is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object as input. applyPatch := func(_ api.Context, _, currentObject runtime.Object) (runtime.Object, error) { // Make sure we actually have a persisted currentObject if hasUID, err := hasUID(currentObject); err != nil { return nil, err } else if !hasUID { return nil, errors.NewNotFound(resource.GroupResource(), name) } switch { case len(originalObjJS) == 0 || len(originalPatchedObjJS) == 0: // first time through, // 1. apply the patch // 2. save the originalJS and patchedJS to detect whether there were conflicting changes on retries if js, err := runtime.Encode(codec, currentObject); err != nil { return nil, err } else { originalObjJS = js } if js, err := getPatchedJS(patchType, originalObjJS, patchJS, versionedObj); err != nil { return nil, err } else { originalPatchedObjJS = js } objToUpdate := patcher.New() if err := runtime.DecodeInto(codec, originalPatchedObjJS, objToUpdate); err != nil { return nil, err } if err := checkName(objToUpdate, name, namespace, namer); err != nil { return nil, err } return objToUpdate, nil default: // on a conflict, // 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can // be specified, but a strategic merge patch should be expressive enough handle them. Build the // patch with this type to handle those cases. // 2. build a strategic merge patch from originalJS and the currentJS // 3. ensure no conflicts between the two patches // 4. apply the #1 patch to the currentJS object currentObjectJS, err := runtime.Encode(codec, currentObject) if err != nil { return nil, err } currentPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, currentObjectJS, versionedObj) if err != nil { return nil, err } originalPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, originalPatchedObjJS, versionedObj) if err != nil { return nil, err } diff1 := make(map[string]interface{}) if err := json.Unmarshal(originalPatch, &diff1); err != nil { return nil, err } diff2 := make(map[string]interface{}) if err := json.Unmarshal(currentPatch, &diff2); err != nil { return nil, err } hasConflicts, err := strategicpatch.HasConflicts(diff1, diff2) if err != nil { return nil, err } if hasConflicts { glog.V(4).Infof("patchResource failed for resource %s, because there is a meaningful conflict.\n diff1=%v\n, diff2=%v\n", name, diff1, diff2) // Return the last conflict error we got if we have one if lastConflictErr != nil { return nil, lastConflictErr } // Otherwise manufacture one of our own return nil, errors.NewConflict(resource.GroupResource(), name, nil) } newlyPatchedObjJS, err := getPatchedJS(api.StrategicMergePatchType, currentObjectJS, originalPatch, versionedObj) if err != nil { return nil, err } objToUpdate := patcher.New() if err := runtime.DecodeInto(codec, newlyPatchedObjJS, objToUpdate); err != nil { return nil, err } return objToUpdate, nil } } // applyAdmission is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object and the patched object as input. applyAdmission := func(ctx api.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) { return patchedObject, admit(patchedObject, currentObject) } updatedObjectInfo := rest.DefaultUpdatedObjectInfo(nil, copier, applyPatch, applyAdmission) return finishRequest(timeout, func() (runtime.Object, error) { updateObject, _, updateErr := patcher.Update(ctx, name, updatedObjectInfo) for i := 0; i < MaxPatchConflicts && (errors.IsConflict(updateErr)); i++ { lastConflictErr = updateErr updateObject, _, updateErr = patcher.Update(ctx, name, updatedObjectInfo) } return updateObject, updateErr }) }
func TestCordon(t *testing.T) { tests := []struct { description string node *api.Node expected *api.Node cmd func(*cmdutil.Factory, io.Writer) *cobra.Command arg string expectFatal bool }{ { description: "node/node syntax", node: cordoned_node, expected: node, cmd: NewCmdUncordon, arg: "node/node", expectFatal: false, }, { description: "uncordon for real", node: cordoned_node, expected: node, cmd: NewCmdUncordon, arg: "node", expectFatal: false, }, { description: "uncordon does nothing", node: node, expected: node, cmd: NewCmdUncordon, arg: "node", expectFatal: false, }, { description: "cordon does nothing", node: cordoned_node, expected: cordoned_node, cmd: NewCmdCordon, arg: "node", expectFatal: false, }, { description: "cordon for real", node: node, expected: cordoned_node, cmd: NewCmdCordon, arg: "node", expectFatal: false, }, { description: "cordon missing node", node: node, expected: node, cmd: NewCmdCordon, arg: "bar", expectFatal: true, }, { description: "uncordon missing node", node: node, expected: node, cmd: NewCmdUncordon, arg: "bar", expectFatal: true, }, } for _, test := range tests { f, tf, codec := NewAPIFactory() new_node := &api.Node{} updated := false tf.Client = &fake.RESTClient{ Codec: codec, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { m := &MyReq{req} switch { case m.isFor("GET", "/nodes/node"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.node)}, nil case m.isFor("GET", "/nodes/bar"): return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("nope")}, nil case m.isFor("PUT", "/nodes/node"): 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 !reflect.DeepEqual(test.expected.Spec, new_node.Spec) { t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, test.expected.Spec, new_node.Spec) } updated = true 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 := test.cmd(f, buf) saw_fatal := false func() { defer func() { // Recover from the panic below. _ = recover() // Restore cmdutil behavior cmdutil.DefaultBehaviorOnFatal() }() cmdutil.BehaviorOnFatal(func(e string) { saw_fatal = true; panic(e) }) cmd.SetArgs([]string{test.arg}) cmd.Execute() }() if test.expectFatal { if !saw_fatal { t.Fatalf("%s: unexpected non-error", test.description) } if updated { t.Fatalf("%s: unexpcted update", test.description) } } if !test.expectFatal && saw_fatal { t.Fatalf("%s: unexpected error", test.description) } if !reflect.DeepEqual(test.expected.Spec, test.node.Spec) && !updated { t.Fatalf("%s: node never updated", test.description) } } }
func TestDrain(t *testing.T) { labels := make(map[string]string) labels["my_key"] = "my_value" rc := api.ReplicationController{ ObjectMeta: api.ObjectMeta{ Name: "rc", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, SelfLink: testapi.Default.SelfLink("replicationcontrollers", "rc"), }, Spec: api.ReplicationControllerSpec{ Selector: labels, }, } rc_anno := make(map[string]string) rc_anno[controller.CreatedByAnnotation] = refJson(t, &rc) rc_pod := api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, Annotations: rc_anno, }, Spec: api.PodSpec{ NodeName: "node", }, } ds := extensions.DaemonSet{ ObjectMeta: api.ObjectMeta{ Name: "ds", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, SelfLink: "/apis/extensions/v1beta1/namespaces/default/daemonsets/ds", }, Spec: extensions.DaemonSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: labels}, }, } ds_anno := make(map[string]string) ds_anno[controller.CreatedByAnnotation] = refJson(t, &ds) ds_pod := api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, Annotations: ds_anno, }, Spec: api.PodSpec{ NodeName: "node", }, } job := batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "job", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, SelfLink: "/apis/extensions/v1beta1/namespaces/default/jobs/job", }, Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{MatchLabels: labels}, }, } job_pod := api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, Annotations: map[string]string{controller.CreatedByAnnotation: refJson(t, &job)}, }, } rs := extensions.ReplicaSet{ ObjectMeta: api.ObjectMeta{ Name: "rs", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, SelfLink: testapi.Default.SelfLink("replicasets", "rs"), }, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: labels}, }, } rs_anno := make(map[string]string) rs_anno[controller.CreatedByAnnotation] = refJson(t, &rs) rs_pod := api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, Annotations: rs_anno, }, Spec: api.PodSpec{ NodeName: "node", }, } naked_pod := api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, }, Spec: api.PodSpec{ NodeName: "node", }, } emptydir_pod := api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, }, Spec: api.PodSpec{ NodeName: "node", Volumes: []api.Volume{ { Name: "scratch", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: ""}}, }, }, }, } tests := []struct { description string node *api.Node expected *api.Node pods []api.Pod rcs []api.ReplicationController replicaSets []extensions.ReplicaSet args []string expectFatal bool expectDelete bool }{ { description: "RC-managed pod", node: node, expected: cordoned_node, pods: []api.Pod{rc_pod}, rcs: []api.ReplicationController{rc}, args: []string{"node"}, expectFatal: false, expectDelete: true, }, { description: "DS-managed pod", node: node, expected: cordoned_node, pods: []api.Pod{ds_pod}, rcs: []api.ReplicationController{rc}, args: []string{"node"}, expectFatal: true, expectDelete: false, }, { description: "DS-managed pod with --ignore-daemonsets", node: node, expected: cordoned_node, pods: []api.Pod{ds_pod}, rcs: []api.ReplicationController{rc}, args: []string{"node", "--ignore-daemonsets"}, expectFatal: false, expectDelete: false, }, { description: "Job-managed pod", node: node, expected: cordoned_node, pods: []api.Pod{job_pod}, rcs: []api.ReplicationController{rc}, args: []string{"node"}, expectFatal: false, expectDelete: true, }, { description: "RS-managed pod", node: node, expected: cordoned_node, pods: []api.Pod{rs_pod}, replicaSets: []extensions.ReplicaSet{rs}, args: []string{"node"}, expectFatal: false, expectDelete: true, }, { description: "naked pod", node: node, expected: cordoned_node, pods: []api.Pod{naked_pod}, rcs: []api.ReplicationController{}, args: []string{"node"}, expectFatal: true, expectDelete: false, }, { description: "naked pod with --force", node: node, expected: cordoned_node, pods: []api.Pod{naked_pod}, rcs: []api.ReplicationController{}, args: []string{"node", "--force"}, expectFatal: false, expectDelete: true, }, { description: "pod with EmptyDir", node: node, expected: cordoned_node, pods: []api.Pod{emptydir_pod}, args: []string{"node", "--force"}, expectFatal: true, expectDelete: false, }, { description: "pod with EmptyDir and --delete-local-data", node: node, expected: cordoned_node, pods: []api.Pod{emptydir_pod}, args: []string{"node", "--force", "--delete-local-data=true"}, expectFatal: false, expectDelete: true, }, { description: "empty node", node: node, expected: cordoned_node, pods: []api.Pod{}, rcs: []api.ReplicationController{rc}, args: []string{"node"}, expectFatal: false, expectDelete: false, }, } for _, test := range tests { new_node := &api.Node{} deleted := false f, tf, codec := NewAPIFactory() tf.Client = &fake.RESTClient{ Codec: codec, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { m := &MyReq{req} switch { case m.isFor("GET", "/nodes/node"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.node)}, nil case m.isFor("GET", "/namespaces/default/replicationcontrollers/rc"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &test.rcs[0])}, nil case m.isFor("GET", "/namespaces/default/daemonsets/ds"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(testapi.Extensions.Codec(), &ds)}, nil case m.isFor("GET", "/namespaces/default/jobs/job"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(testapi.Extensions.Codec(), &job)}, nil case m.isFor("GET", "/namespaces/default/replicasets/rs"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(testapi.Extensions.Codec(), &test.replicaSets[0])}, nil case m.isFor("GET", "/pods"): values, err := url.ParseQuery(req.URL.RawQuery) if err != nil { t.Fatalf("%s: unexpected error: %v", test.description, err) } get_params := make(url.Values) get_params["fieldSelector"] = []string{"spec.nodeName=node"} if !reflect.DeepEqual(get_params, values) { t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, get_params, values) } return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.PodList{Items: test.pods})}, nil case m.isFor("GET", "/replicationcontrollers"): return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationControllerList{Items: test.rcs})}, nil case m.isFor("PUT", "/nodes/node"): 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 !reflect.DeepEqual(test.expected.Spec, new_node.Spec) { t.Fatalf("%s: expected:\n%v\nsaw:\n%v\n", test.description, test.expected.Spec, new_node.Spec) } return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, new_node)}, nil case m.isFor("DELETE", "/namespaces/default/pods/bar"): deleted = true return &http.Response{StatusCode: 204, Header: defaultHeader(), Body: objBody(codec, &test.pods[0])}, 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 := NewCmdDrain(f, buf) saw_fatal := false func() { defer func() { // Recover from the panic below. _ = recover() // Restore cmdutil behavior cmdutil.DefaultBehaviorOnFatal() }() cmdutil.BehaviorOnFatal(func(e string) { 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.expectDelete { if !deleted { t.Fatalf("%s: pod never deleted", test.description) } } if !test.expectDelete { if deleted { t.Fatalf("%s: unexpected delete", test.description) } } } }