// Visit implements Visitor func (r *Selector) Visit(fn VisitorFunc) error { list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector, r.Export) if err != nil { if errors.IsBadRequest(err) || errors.IsNotFound(err) { if se, ok := err.(*errors.StatusError); ok { // modify the message without hiding this is an API error if r.Selector.Empty() { se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message) } else { se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, se.ErrStatus.Message) } return se } if r.Selector.Empty() { return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err) } else { return fmt.Errorf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, err) } } return err } accessor := r.Mapping.MetadataAccessor resourceVersion, _ := accessor.ResourceVersion(list) info := &Info{ Client: r.Client, Mapping: r.Mapping, Namespace: r.Namespace, Object: list, ResourceVersion: resourceVersion, } return fn(info, nil) }
// Visit implements Visitor func (r *Selector) Visit(fn VisitorFunc) error { list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.ResourceMapping().APIVersion, r.Selector) if err != nil { if errors.IsBadRequest(err) || errors.IsNotFound(err) { if r.Selector.Empty() { glog.V(2).Infof("Unable to list %q: %v", r.Mapping.Resource, err) } else { glog.V(2).Infof("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, err) } return nil } return err } accessor := r.Mapping.MetadataAccessor resourceVersion, _ := accessor.ResourceVersion(list) info := &Info{ Client: r.Client, Mapping: r.Mapping, Namespace: r.Namespace, Object: list, ResourceVersion: resourceVersion, } return fn(info) }
func TestProjectStatusErrors(t *testing.T) { testCases := map[string]struct { Err error ErrFn func(error) bool }{ "project error is returned": { Err: errors.NewBadRequest("unavailable"), ErrFn: func(err error) bool { if aggr, ok := err.(utilerrors.Aggregate); ok { for _, e := range aggr.Errors() { if !errors.IsBadRequest(e) { return false } } return true } return false }, }, } for k, test := range testCases { oc, kc, oldK := testclient.NewErrorClients(test.Err) d := ProjectStatusDescriber{OldK: oldK, K: kc, C: oc, Server: "https://example.com:8443", Suggest: true, CommandBaseName: "oc", LogsCommandName: "oc logs -p", SecurityPolicyCommandFormat: "policycommand %s %s"} _, err := d.Describe("example", "") if !test.ErrFn(err) { t.Errorf("%s: unexpected error: %v", k, err) } } }
func TestEtcdControllerValidatesUpdate(t *testing.T) { ctx := api.NewDefaultContext() storage, _ := newStorage(t) updateController, err := createController(storage, validController, t) if err != nil { t.Errorf("Failed to create controller, cannot proceed with test.") } updaters := []func(rc api.ReplicationController) (runtime.Object, bool, error){ func(rc api.ReplicationController) (runtime.Object, bool, error) { rc.UID = "newUID" return storage.Update(ctx, &rc) }, func(rc api.ReplicationController) (runtime.Object, bool, error) { rc.Name = "" return storage.Update(ctx, &rc) }, func(rc api.ReplicationController) (runtime.Object, bool, error) { rc.Spec.Selector = map[string]string{} return storage.Update(ctx, &rc) }, } for _, u := range updaters { c, updated, err := u(updateController) if c != nil || updated { t.Errorf("Expected nil object and not created") } if !errors.IsInvalid(err) && !errors.IsBadRequest(err) { t.Errorf("Expected invalid or bad request error, got %v of type %T", err, err) } } }
func (c *BuildConfigController) HandleBuildConfig(bc *buildapi.BuildConfig) error { glog.V(4).Infof("Handling BuildConfig %s/%s", bc.Namespace, bc.Name) hasChangeTrigger := false for _, trigger := range bc.Spec.Triggers { if trigger.Type == buildapi.ConfigChangeBuildTriggerType { hasChangeTrigger = true break } } if !hasChangeTrigger { return nil } if bc.Status.LastVersion > 0 { return nil } glog.V(4).Infof("Running build for BuildConfig %s/%s", bc.Namespace, bc.Name) buildTriggerCauses := []buildapi.BuildTriggerCause{} // instantiate new build lastVersion := int64(0) request := &buildapi.BuildRequest{ TriggeredBy: append(buildTriggerCauses, buildapi.BuildTriggerCause{ Message: "Build configuration change", }), ObjectMeta: kapi.ObjectMeta{ Name: bc.Name, Namespace: bc.Namespace, }, LastVersion: &lastVersion, } if _, err := c.BuildConfigInstantiator.Instantiate(bc.Namespace, request); err != nil { var instantiateErr error if kerrors.IsConflict(err) { instantiateErr = fmt.Errorf("unable to instantiate Build for BuildConfig %s/%s due to a conflicting update: %v", bc.Namespace, bc.Name, err) utilruntime.HandleError(instantiateErr) } else if buildgenerator.IsFatal(err) || kerrors.IsNotFound(err) || kerrors.IsBadRequest(err) { return &ConfigControllerFatalError{err.Error()} } else { instantiateErr = fmt.Errorf("error instantiating Build from BuildConfig %s/%s: %v", bc.Namespace, bc.Name, err) c.Recorder.Event(bc, kapi.EventTypeWarning, "BuildConfigInstantiateFailed", instantiateErr.Error()) utilruntime.HandleError(instantiateErr) } return instantiateErr } return nil }
// deploy launches a new deployment unless there's already a deployment // process in progress for config. func (o DeployOptions) deploy(config *deployapi.DeploymentConfig) error { if config.Spec.Paused { return fmt.Errorf("cannot deploy a paused deployment config") } // TODO: This implies that deploymentconfig.status.latestVersion is always synced. Currently, // that's the case because clients (oc, trigger controllers) are updating the status directly. // Clients should be acting either on spec or on annotations and status updates should be a // responsibility of the main controller. We need to start by unplugging this assumption from // our client tools. deploymentName := deployutil.LatestDeploymentNameForConfig(config) deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName) if err == nil && !deployutil.IsTerminatedDeployment(deployment) { // Reject attempts to start a concurrent deployment. return fmt.Errorf("#%d is already in progress (%s).\nOptionally, you can cancel this deployment using the --cancel option.", config.Status.LatestVersion, deployutil.DeploymentStatusFor(deployment)) } if err != nil && !kerrors.IsNotFound(err) { return err } request := &deployapi.DeploymentRequest{ Name: config.Name, Latest: false, Force: true, } dc, err := o.osClient.DeploymentConfigs(config.Namespace).Instantiate(request) // Pre 1.4 servers don't support the instantiate endpoint. Fallback to incrementing // latestVersion on them. if kerrors.IsNotFound(err) || kerrors.IsForbidden(err) { config.Status.LatestVersion++ dc, err = o.osClient.DeploymentConfigs(config.Namespace).Update(config) } if err != nil { if kerrors.IsBadRequest(err) { err = fmt.Errorf("%v - try 'oc rollout latest dc/%s'", err, config.Name) } return err } fmt.Fprintf(o.out, "Started deployment #%d\n", dc.Status.LatestVersion) if o.follow { return o.getLogs(dc) } fmt.Fprintf(o.out, "Use '%s logs -f dc/%s' to track its progress.\n", o.baseCommandName, dc.Name) return nil }
func TestCreateControllerWithConflictingNamespace(t *testing.T) { storage, _ := newStorage(t) controller := &api.Daemon{ ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "not-default"}, } ctx := api.NewDefaultContext() channel, err := storage.Create(ctx, controller) if channel != nil { t.Error("Expected a nil channel, but we got a value") } errSubString := "namespace" if err == nil { t.Errorf("Expected an error, but we didn't get one") } else if !errors.IsBadRequest(err) || strings.Index(err.Error(), errSubString) == -1 { t.Errorf("Expected a Bad Request error with the sub string '%s', got %v", errSubString, err) } }
func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, setFn SetFunc, invalidUpdateFn ...UpdateFunc) { ctx := t.TestContext() foo := copyOrDie(obj) t.setObjectMeta(foo, "foo4") if err := setFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) } for _, update := range invalidUpdateFn { toUpdate := update(copyOrDie(foo)) got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdate) if got != nil || created { t.Errorf("expected nil object and no creation for object: %v", toUpdate) } if !errors.IsInvalid(err) && !errors.IsBadRequest(err) { t.Errorf("expected invalid or bad request error, got %v", err) } } }
func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, createFn CreateFunc, invalidUpdateFn ...UpdateFunc) { ctx := t.TestContext() foo := copyOrDie(obj) t.setObjectMeta(foo, t.namer(4)) if err := createFn(ctx, foo); err != nil { t.Errorf("unexpected error: %v", err) } for _, update := range invalidUpdateFn { toUpdate := update(copyOrDie(foo)) toUpdateMeta := t.getObjectMetaOrFail(toUpdate) got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdateMeta.Name, rest.DefaultUpdatedObjectInfo(toUpdate, api.Scheme)) if got != nil || created { t.Errorf("expected nil object and no creation for object: %v", toUpdate) } if !errors.IsInvalid(err) && !errors.IsBadRequest(err) { t.Errorf("expected invalid or bad request error, got %v", err) } } }
func TestPatchWithCreateOnUpdate(t *testing.T) { _, s := framework.RunAMaster(t) defer s.Close() framework.DeleteAllEtcdKeys() c := client.NewOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) endpointTemplate := &api.Endpoints{ ObjectMeta: api.ObjectMeta{Name: "patchendpoint"}, Subsets: []api.EndpointSubset{ { Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, Ports: []api.EndpointPort{{Port: 80, Protocol: api.ProtocolTCP}}, }, }, } patchEndpoint := func(json []byte) (runtime.Object, error) { return c.Patch(api.MergePatchType).Resource("endpoints").Namespace(api.NamespaceDefault).Name("patchendpoint").Body(json).Do().Get() } // Make sure patch doesn't get to CreateOnUpdate { endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { t.Fatalf("Failed creating endpoint JSON: %v", err) } if obj, err := patchEndpoint(endpointJSON); !apierrors.IsNotFound(err) { t.Errorf("Expected notfound creating from patch, got error=%v and object: %#v", err, obj) } } // Create the endpoint (endpoints set AllowCreateOnUpdate=true) to get a UID and resource version createdEndpoint, err := c.Endpoints(api.NamespaceDefault).Update(endpointTemplate) if err != nil { t.Fatalf("Failed creating endpoint: %v", err) } // Make sure identity patch is accepted { endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), createdEndpoint) if err != nil { t.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); err != nil { t.Errorf("Failed patching endpoint: %v", err) } } // Make sure patch complains about a mismatched resourceVersion { endpointTemplate.Name = "" endpointTemplate.UID = "" endpointTemplate.ResourceVersion = "1" endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { t.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); !apierrors.IsConflict(err) { t.Errorf("Expected error, got %#v", err) } } // Make sure patch complains about mutating the UID { endpointTemplate.Name = "" endpointTemplate.UID = "abc" endpointTemplate.ResourceVersion = "" endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { t.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); !apierrors.IsInvalid(err) { t.Errorf("Expected error, got %#v", err) } } // Make sure patch complains about a mismatched name { endpointTemplate.Name = "changedname" endpointTemplate.UID = "" endpointTemplate.ResourceVersion = "" endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { t.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); !apierrors.IsBadRequest(err) { t.Errorf("Expected error, got %#v", err) } } // Make sure patch containing originally submitted JSON is accepted { endpointTemplate.Name = "" endpointTemplate.UID = "" endpointTemplate.ResourceVersion = "" endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { t.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); err != nil { t.Errorf("Failed patching endpoint: %v", err) } } }
func TestImageRemoveSignature(t *testing.T) { adminClient, userClient, image := testSetupImageSignatureTest(t, testUserName) makeUserAnImageSigner(adminClient, userClient, testUserName) // create some signatures sigData := []struct { sigName string content string }{ {"a", "binaryblob"}, {"b", "security without obscurity"}, {"c", "distrust and caution are the parents of security"}, {"d", "he who sacrifices freedom for security deserves neither"}, } for i, d := range sigData { name, err := imageapi.JoinImageSignatureName(image.Name, d.sigName) if err != nil { t.Fatalf("creating signature %d: unexpected error: %v", i, err) } signature := imageapi.ImageSignature{ ObjectMeta: kapi.ObjectMeta{ Name: name, }, Type: "unknown", Content: []byte(d.content), } _, err = userClient.ImageSignatures().Create(&signature) if err != nil { t.Fatalf("creating signature %d: unexpected error: %v", i, err) } } image, err := userClient.Images().Get(image.Name) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(image.Signatures) != 4 { t.Fatalf("expected 4 signatures, not %d", len(image.Signatures)) } // try to delete blob that does not exist err = userClient.ImageSignatures().Delete(image.Name + "@doesnotexist") if !kerrors.IsNotFound(err) { t.Fatalf("expected not found error, not: %#+v", err) } // try to delete blob with missing signature name err = userClient.ImageSignatures().Delete(image.Name + "@") if !kerrors.IsBadRequest(err) { t.Fatalf("expected bad request, not: %#+v", err) } // delete the first err = userClient.ImageSignatures().Delete(image.Name + "@" + sigData[0].sigName) if err != nil { t.Fatalf("unexpected error: %v", err) } // try to delete it once more err = userClient.ImageSignatures().Delete(image.Name + "@" + sigData[0].sigName) if err == nil { t.Fatalf("unexpected nont error") } else if !kerrors.IsNotFound(err) { t.Errorf("expected not found error, not: %#+v", err) } // delete the one in the middle err = userClient.ImageSignatures().Delete(image.Name + "@" + sigData[2].sigName) if err != nil { t.Fatalf("unexpected error: %v", err) } if image, err = userClient.Images().Get(image.Name); err != nil { t.Fatalf("unexpected error: %v", err) } else if len(image.Signatures) != 2 { t.Fatalf("expected 2 signatures, not %d", len(image.Signatures)) } // delete the one at the end err = userClient.ImageSignatures().Delete(image.Name + "@" + sigData[3].sigName) if err != nil { t.Fatalf("unexpected error: %v", err) } // delete the last one err = userClient.ImageSignatures().Delete(image.Name + "@" + sigData[1].sigName) if err != nil { t.Fatalf("unexpected error: %v", err) } if image, err = userClient.Images().Get(image.Name); err != nil { t.Fatalf("unexpected error: %v", err) } else if len(image.Signatures) != 0 { t.Fatalf("expected 2 signatures, not %d", len(image.Signatures)) } }
func runPatchTest(c *client.Client) { name := "patchservice" resource := "services" svcBody := api.Service{ TypeMeta: unversioned.TypeMeta{ APIVersion: c.APIVersion().String(), }, ObjectMeta: api.ObjectMeta{ Name: name, Labels: map[string]string{}, }, Spec: api.ServiceSpec{ // This is here because validation requires it. Selector: map[string]string{ "foo": "bar", }, Ports: []api.ServicePort{{ Port: 12345, Protocol: "TCP", }}, SessionAffinity: "None", }, } services := c.Services(api.NamespaceDefault) svc, err := services.Create(&svcBody) if err != nil { glog.Fatalf("Failed creating patchservice: %v", err) } patchBodies := map[unversioned.GroupVersion]map[api.PatchType]struct { AddLabelBody []byte RemoveLabelBody []byte RemoveAllLabelsBody []byte }{ v1.SchemeGroupVersion: { api.JSONPatchType: { []byte(`[{"op":"add","path":"/metadata/labels","value":{"foo":"bar","baz":"qux"}}]`), []byte(`[{"op":"remove","path":"/metadata/labels/foo"}]`), []byte(`[{"op":"remove","path":"/metadata/labels"}]`), }, api.MergePatchType: { []byte(`{"metadata":{"labels":{"foo":"bar","baz":"qux"}}}`), []byte(`{"metadata":{"labels":{"foo":null}}}`), []byte(`{"metadata":{"labels":null}}`), }, api.StrategicMergePatchType: { []byte(`{"metadata":{"labels":{"foo":"bar","baz":"qux"}}}`), []byte(`{"metadata":{"labels":{"foo":null}}}`), []byte(`{"metadata":{"labels":{"$patch":"replace"}}}`), }, }, } pb := patchBodies[c.APIVersion()] execPatch := func(pt api.PatchType, body []byte) error { return c.Patch(pt). Resource(resource). Namespace(api.NamespaceDefault). Name(name). Body(body). Do(). Error() } for k, v := range pb { // add label err := execPatch(k, v.AddLabelBody) if err != nil { glog.Fatalf("Failed updating patchservice with patch type %s: %v", k, err) } svc, err = services.Get(name) if err != nil { glog.Fatalf("Failed getting patchservice: %v", err) } if len(svc.Labels) != 2 || svc.Labels["foo"] != "bar" || svc.Labels["baz"] != "qux" { glog.Fatalf("Failed updating patchservice with patch type %s: labels are: %v", k, svc.Labels) } // remove one label err = execPatch(k, v.RemoveLabelBody) if err != nil { glog.Fatalf("Failed updating patchservice with patch type %s: %v", k, err) } svc, err = services.Get(name) if err != nil { glog.Fatalf("Failed getting patchservice: %v", err) } if len(svc.Labels) != 1 || svc.Labels["baz"] != "qux" { glog.Fatalf("Failed updating patchservice with patch type %s: labels are: %v", k, svc.Labels) } // remove all labels err = execPatch(k, v.RemoveAllLabelsBody) if err != nil { glog.Fatalf("Failed updating patchservice with patch type %s: %v", k, err) } svc, err = services.Get(name) if err != nil { glog.Fatalf("Failed getting patchservice: %v", err) } if svc.Labels != nil { glog.Fatalf("Failed remove all labels from patchservice with patch type %s: %v", k, svc.Labels) } } // Test patch with a resource that allows create on update endpointTemplate := &api.Endpoints{ ObjectMeta: api.ObjectMeta{Name: "patchendpoint"}, Subsets: []api.EndpointSubset{ { Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, Ports: []api.EndpointPort{{Port: 80, Protocol: api.ProtocolTCP}}, }, }, } patchEndpoint := func(json []byte) (runtime.Object, error) { return c.Patch(api.MergePatchType).Resource("endpoints").Namespace(api.NamespaceDefault).Name("patchendpoint").Body(json).Do().Get() } // Make sure patch doesn't get to CreateOnUpdate { endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { glog.Fatalf("Failed creating endpoint JSON: %v", err) } if obj, err := patchEndpoint(endpointJSON); !apierrors.IsNotFound(err) { glog.Fatalf("Expected notfound creating from patch, got error=%v and object: %#v", err, obj) } } // Create the endpoint (endpoints set AllowCreateOnUpdate=true) to get a UID and resource version createdEndpoint, err := c.Endpoints(api.NamespaceDefault).Update(endpointTemplate) if err != nil { glog.Fatalf("Failed creating endpoint: %v", err) } // Make sure identity patch is accepted { endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), createdEndpoint) if err != nil { glog.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); err != nil { glog.Fatalf("Failed patching endpoint: %v", err) } } // Make sure patch complains about a mismatched resourceVersion { endpointTemplate.Name = "" endpointTemplate.UID = "" endpointTemplate.ResourceVersion = "1" endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { glog.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); !apierrors.IsConflict(err) { glog.Fatalf("Expected error, got %#v", err) } } // Make sure patch complains about mutating the UID { endpointTemplate.Name = "" endpointTemplate.UID = "abc" endpointTemplate.ResourceVersion = "" endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { glog.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); !apierrors.IsInvalid(err) { glog.Fatalf("Expected error, got %#v", err) } } // Make sure patch complains about a mismatched name { endpointTemplate.Name = "changedname" endpointTemplate.UID = "" endpointTemplate.ResourceVersion = "" endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { glog.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); !apierrors.IsBadRequest(err) { glog.Fatalf("Expected error, got %#v", err) } } // Make sure patch containing originally submitted JSON is accepted { endpointTemplate.Name = "" endpointTemplate.UID = "" endpointTemplate.ResourceVersion = "" endpointJSON, err := runtime.Encode(api.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) if err != nil { glog.Fatalf("Failed creating endpoint JSON: %v", err) } if _, err := patchEndpoint(endpointJSON); err != nil { glog.Fatalf("Failed patching endpoint: %v", err) } } glog.Info("PATCHs work.") }
func TestProjectStatus(t *testing.T) { requestErr := errors.NewBadRequest("unavailable").Status() requestErr.Details = &unversioned.StatusDetails{Kind: "Project", Name: "example"} testCases := map[string]struct { File string Extra []runtime.Object ErrFn func(error) bool Contains []string Time time.Time }{ "missing project": { ErrFn: func(err error) bool { return err == nil }, }, "project error is returned": { Extra: []runtime.Object{ &requestErr, }, ErrFn: func(err error) bool { return errors.IsBadRequest(err) }, }, "empty project with display name": { Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{ Name: "example", Namespace: "", Annotations: map[string]string{ projectapi.ProjectDisplayName: "Test", }, }, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project Test (example) on server https://example.com:8443\n", "You have no services, deployment configs, or build configs.", }, }, "empty service": { File: "k8s-service-with-nothing.json", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "svc/empty-service", "<initializing>:5432", "View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.", }, }, "service with RC": { File: "k8s-unserviced-rc.json", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "svc/database-rc", "rc/database-rc-1 runs mysql", "0/1 pods growing to 1", "View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.", }, }, "rc with unmountable and missing secrets": { File: "bad_secret_with_just_rc.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "rc/my-rc runs centos/mysql-56-centos7", "0/1 pods growing to 1", "rc/my-rc is attempting to mount a missing secret secret/dne", }, }, "dueling rcs": { File: "dueling-rcs.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "dueling-rc", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "rc/rc-1 is competing for pod/conflicted-pod with rc/rc-2", "rc/rc-2 is competing for pod/conflicted-pod with rc/rc-1", }, }, "service with pod": { File: "service-with-pod.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "svc/frontend-app", "pod/frontend-app-1-bjwh8 runs openshift/ruby-hello-world", "View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.", }, }, "build chains": { File: "build-chains.json", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "from bc/frontend", }, }, "scheduled image stream": { File: "prereq-image-present-with-sched.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "import scheduled", }, }, "standalone rc": { File: "bare-rc.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", " rc/database-1 runs centos/mysql-56-centos7", "rc/frontend-rc-1 runs openshift/ruby-hello-world", }, }, "unstarted build": { File: "new-project-no-build.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "svc/sinatra-example-2 - 172.30.17.48:8080", "deploys istag/sinatra-example-2:latest <-", "builds git://github.com", "on docker.io/centos/ruby-22-centos7:latest", "not built yet", "deployment #1 waiting on image or update", "View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.", }, }, "unpushable build": { File: "unpushable-build.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "bc/ruby-hello-world is pushing to istag/ruby-hello-world:latest, but the administrator has not configured the integrated Docker registry.", }, }, "bare-bc-can-push": { File: "bare-bc-can-push.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ // this makes sure that status knows this can push. If it fails, there's a "(can't push image)" next to like #8 " hours\n build #7", "on fedora:23", "-> repo-base:latest", }, Time: mustParseTime("2015-12-17T20:36:15Z"), }, "cyclical build": { File: "circular.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "Cycle detected in build configurations:", "on istag/ruby-22-centos7:latest", "-> istag/ruby-hello-world:latest", }, }, "running build": { File: "new-project-one-build.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "svc/sinatra-example-1 - 172.30.17.47:8080", "builds git://github.com", "on docker.io/centos/ruby-22-centos7:latest", "build #1 running for about a minute", "deployment #1 waiting on image or update", "View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.", }, Time: mustParseTime("2015-04-06T21:20:03Z"), }, "a/b test DeploymentConfig": { File: "new-project-two-deployment-configs.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "svc/sinatra-app-example - 172.30.17.49:8080", "sinatra-app-example-a deploys", "sinatra-app-example-b deploys", "on docker.io/centos/ruby-22-centos7:latest", "build #1 running for about a minute", "- 7a4f354: Prepare v1 Template types (Roy Programmer <*****@*****.**>)", "View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.", }, Time: mustParseTime("2015-04-06T21:20:03Z"), }, "with real deployments": { File: "new-project-deployed-app.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "svc/database - 172.30.17.240:5434 -> 3306", "https://www.test.com (redirects) to pod port 8080 (svc/frontend)", "http://frontend-example.router.default.svc.cluster.local to pod port 8080 (!)", "svc/database-external (all nodes):31000 -> 3306", "database test deploys", "frontend deploys", "istag/origin-ruby-sample:latest <-", "on docker.io/centos/ruby-22-centos7:latest", "deployment #3 pending on image", "deployment #2 failed less than a second ago: unable to contact server - 0/1 pods", "deployment #1 deployed less than a second ago", "test deployment #2 running for 7 seconds - 2/1 pods", "test deployment #1 deployed 8 seconds ago", "* bc/ruby-sample-build is pushing to istag/origin-ruby-sample:latest, but the image stream for that tag does not exist.", "* The image trigger for dc/frontend will have no effect because is/origin-ruby-sample does not exist", "* route/frontend was not accepted by router \"other\": (HostAlreadyClaimed)", "* dc/database has no readiness probe to verify pods are ready to accept traffic or ensure deployment is successful.", "View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.", }, Time: mustParseTime("2015-04-07T04:12:25Z"), }, "with pet sets": { File: "petset.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "svc/galera[default] (headless):3306", "petset/mysql manages erkules/galera:basic, created less than a second ago - 3 pods", "* pod/mysql-1[default] has restarted 7 times", }, Time: mustParseTime("2015-04-07T04:12:25Z"), }, "restarting pod": { File: "restarting-pod.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ `container "ruby-helloworld" in pod/frontend-app-1-bjwh8 has restarted 8 times`, `pod/gitlab-ce-1-lc411 is crash-looping`, `oc logs -p gitlab-ce-1-lc411 -c gitlab-ce`, // verifies we print the log command `policycommand example default`, // verifies that we print the help command }, }, "cross namespace reference": { File: "different-project-image-deployment.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ // If there was a warning we wouldn't get the following message. Since we ignore cross-namespace // links by default, there should be no warning here. `View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.`, }, }, "monopod": { File: "k8s-lonely-pod.json", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "pod/lonely-pod runs openshift/hello-openshift", "You have no services, deployment configs, or build configs.", }, }, "deploys single pod": { File: "simple-deployment.yaml", Extra: []runtime.Object{ &projectapi.Project{ ObjectMeta: kapi.ObjectMeta{Name: "example", Namespace: ""}, }, }, ErrFn: func(err error) bool { return err == nil }, Contains: []string{ "In project example on server https://example.com:8443\n", "dc/simple-deployment deploys docker.io/openshift/deployment-example:v1", `View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'.`, }, }, } oldTimeFn := timeNowFn defer func() { timeNowFn = oldTimeFn }() for k, test := range testCases { timeNowFn = func() time.Time { if !test.Time.IsZero() { return test.Time } return time.Now() } o := ktestclient.NewObjects(kapi.Scheme, kapi.Codecs.UniversalDecoder()) if len(test.File) > 0 { // Load data from a folder dedicated to mock data, which is never loaded into the API during tests if err := ktestclient.AddObjectsFromPath("../../../../pkg/api/graph/test/"+test.File, o, kapi.Codecs.UniversalDecoder()); err != nil { t.Errorf("%s: unexpected error: %v", k, err) } } for _, obj := range test.Extra { o.Add(obj) } oc, kc := testclient.NewFixtureClients(o) d := ProjectStatusDescriber{C: oc, K: kc, Server: "https://example.com:8443", Suggest: true, CommandBaseName: "oc", LogsCommandName: "oc logs -p", SecurityPolicyCommandFormat: "policycommand %s %s"} out, err := d.Describe("example", "") if !test.ErrFn(err) { t.Errorf("%s: unexpected error: %v", k, err) } if err != nil { continue } for _, s := range test.Contains { if !strings.Contains(out, s) { t.Errorf("%s: did not have %q:\n%s\n---", k, s, out) } } } }