diff --git a/test/conformance/service_test.go b/test/conformance/service_test.go index e1c6cdfa31f6..8c4d97a88710 100644 --- a/test/conformance/service_test.go +++ b/test/conformance/service_test.go @@ -301,13 +301,36 @@ func TestRunLatestService(t *testing.T) { } } -// TestReleaseService creates a Service in runLatest mode and then updates it to release mode. Once in release mode the test -// goes through Update/Validate to try different possible configurations for a release service. -// Currently tests for the following combinations +func waitForDesiredTrafficShape(sName string, want map[string]v1alpha1.TrafficTarget, clients *test.Clients, logger *logging.BaseLogger) error { + return test.WaitForServiceState( + clients.ServingClient, sName, func(s *v1alpha1.Service) (bool, error) { + // IsServiceReady never returns an error. + if ok, _ := test.IsServiceReady(s); !ok { + return false, nil + } + // Match the traffic shape. + got := map[string]v1alpha1.TrafficTarget{} + for _, tt := range s.Status.Traffic { + got[tt.Name] = tt + } + if !cmp.Equal(got, want) { + logger.Info("For service %s traffic shape mismatch: (-got, +want)", sName, cmp.Diff(got, want)) + return false, nil + } + return true, nil + }, "Verify Service Trafic Shape", + ) +} + +// TestReleaseService creates a Service in `release` mode with the only revision +// being `@latest`. Once this succeeded, the test goes through Update/Validate to +// try different possible configurations for a release service. +// Currently tests for the following combinations: // 1. One Revision Specified, current == latest // 2. One Revision Specified, current != latset // 3. Two Revisions Specified, 50% rollout, candidate == latest // 4. Two Revisions Specified, 50% rollout, candidate != latest +// 5. Two Revisions Specified, 50% rollout, candidate != latest, latest referred to as `@latest`. func TestReleaseService(t *testing.T) { // Create Initial Service clients := setup(t) @@ -332,18 +355,34 @@ func TestReleaseService(t *testing.T) { if err != nil { t.Fatalf("Failed to create initial Service %v: %v", names.Service, err) } + logger.Info("Validating service shape.") if err := validateReleaseServiceShape(objects); err != nil { t.Fatalf("Release shape incorrect: %v", err) } revisions := []string{names.Revision} - // One Revision Specified, current == latest. - logger.Info("Updating Service to ReleaseType using lastCreatedRevision") + // 1. One Revision Specified, current == latest. + logger.Info("1. Updating Service to ReleaseType using lastCreatedRevision") objects.Service, err = test.PatchReleaseService(logger, clients, objects.Service, revisions, 0) if err != nil { t.Fatalf("Service %s was not updated to release: %v", names.Service, err) } + desiredTrafficShape := map[string]v1alpha1.TrafficTarget{ + "current": v1alpha1.TrafficTarget{ + Name: "current", + RevisionName: objects.Config.Status.LatestReadyRevisionName, + Percent: 100, + }, + "latest": v1alpha1.TrafficTarget{ + Name: "latest", + RevisionName: objects.Config.Status.LatestReadyRevisionName, + }, + } + logger.Info("Waiting for Service to become ready with the new shape.") + if err := waitForDesiredTrafficShape(names.Service, desiredTrafficShape, clients, logger); err != nil { + t.Fatal("Service never obtained expected shape") + } logger.Info("Service traffic should go to the first revision and be available on two names traffic targets: 'current' and 'latest'") if err := validateDomains(logger, clients, @@ -354,8 +393,8 @@ func TestReleaseService(t *testing.T) { t.Fatal(err) } - // One Revision Specified, current != latest. - logger.Info("Updating the Service Spec with a new image") + // 2. One Revision Specified, current != latest. + logger.Info("2. Updating the Service Spec with a new image") if _, err := test.PatchServiceImage(logger, clients, objects.Service, releaseImagePath2); err != nil { t.Fatalf("Patch update for Service %s with new image %s failed: %v", names.Service, releaseImagePath2, err) } @@ -366,6 +405,16 @@ func TestReleaseService(t *testing.T) { } revisions = append(revisions, names.Revision) + // Also verify traffic is in the correct shape. + desiredTrafficShape["latest"] = v1alpha1.TrafficTarget{ + Name: "latest", + RevisionName: names.Revision, + } + logger.Info("Waiting for Service to become ready with the new shape.") + if err := waitForDesiredTrafficShape(names.Service, desiredTrafficShape, clients, logger); err != nil { + t.Fatal("Service never obtained expected shape") + } + logger.Info("Since the Service is using release the Route will not be updated, but new revision will be available at 'latest'") if err := validateDomains(logger, clients, names.Domain, @@ -375,12 +424,33 @@ func TestReleaseService(t *testing.T) { t.Fatal(err) } - // Two Revisions Specified, 50% rollout, candidate == latest. - logger.Info("Updating Service to split traffic between two revisions using Release mode") + // 3. Two Revisions Specified, 50% rollout, candidate == latest. + logger.Info("3. Updating Service to split traffic between two revisions using Release mode") if objects.Service, err = test.PatchReleaseService(logger, clients, objects.Service, revisions, 50); err != nil { t.Fatalf("Service %s was not updated to release: %v", names.Service, err) } + desiredTrafficShape = map[string]v1alpha1.TrafficTarget{ + "current": v1alpha1.TrafficTarget{ + Name: "current", + RevisionName: revisions[0], + Percent: 50, + }, + "candidate": v1alpha1.TrafficTarget{ + Name: "candidate", + RevisionName: revisions[1], + Percent: 50, + }, + "latest": v1alpha1.TrafficTarget{ + Name: "latest", + RevisionName: revisions[1], + }, + } + logger.Info("Waiting for Service to become ready with the new shape.") + if err := waitForDesiredTrafficShape(names.Service, desiredTrafficShape, clients, logger); err != nil { + t.Fatal("Service never obtained expected shape") + } + logger.Info("Traffic should be split between the two revisions and available on three named traffic targets, 'current', 'candidate', and 'latest'") if err := validateDomains(logger, clients, names.Domain, @@ -390,11 +460,24 @@ func TestReleaseService(t *testing.T) { t.Fatal(err) } - // Two Revisions Specified, 50% rollout, candidate != latest. - logger.Info("Updating the Service Spec with a new image") + // 4. Two Revisions Specified, 50% rollout, candidate != latest. + logger.Info("4. Updating the Service Spec with a new image") if _, err := test.PatchServiceImage(logger, clients, objects.Service, releaseImagePath3); err != nil { t.Fatalf("Patch update for Service %s with new image %s failed: %v", names.Service, releaseImagePath3, err) } + logger.Info("Since the Service was updated a new Revision will be created") + if names.Revision, err = test.WaitForServiceLatestRevision(clients, names); err != nil { + t.Fatalf("The Service %s was not updated with new revision %s: %v", names.Service, names.Revision, err) + } + + desiredTrafficShape["latest"] = v1alpha1.TrafficTarget{ + Name: "latest", + RevisionName: names.Revision, + } + logger.Info("Waiting for Service to become ready with the new shape.") + if err := waitForDesiredTrafficShape(names.Service, desiredTrafficShape, clients, logger); err != nil { + t.Fatal("Service never obtained expected shape") + } logger.Info("Traffic should remain between the two images, and the new revision should be available on the named traffic target 'latest'") if err := validateDomains(logger, clients, @@ -404,6 +487,32 @@ func TestReleaseService(t *testing.T) { []string{expectedThirdRev, expectedSecondRev, expectedFirstRev}); err != nil { t.Fatal(err) } + + // Now update the service to use `@latest` as candidate. + revisions[1] = v1alpha1.ReleaseLatestRevisionKeyword + logger.Info("5. Updating Service to split traffic between two `current` and `@latest`") + if objects.Service, err = test.PatchReleaseService(logger, clients, objects.Service, revisions, 50); err != nil { + t.Fatalf("Service %s was not updated to release: %v", names.Service, err) + } + + // `candidate` now points to the latest. + desiredTrafficShape["candidate"] = v1alpha1.TrafficTarget{ + Name: "candidate", + RevisionName: names.Revision, + Percent: 50, + } + logger.Info("Waiting for Service to become ready with the new shape.") + if err := waitForDesiredTrafficShape(names.Service, desiredTrafficShape, clients, logger); err != nil { + t.Fatal("Service never obtained expected shape") + } + + if err := validateDomains(logger, clients, + names.Domain, + []string{expectedFirstRev, expectedThirdRev}, + []string{"latest", "candidate", "current"}, + []string{expectedThirdRev, expectedThirdRev, expectedFirstRev}); err != nil { + t.Fatal(err) + } } // TODO(jonjohnsonjr): Examples of deploying from source. diff --git a/test/crd_checks.go b/test/crd_checks.go index c2d1225a316b..c839a8b5b232 100644 --- a/test/crd_checks.go +++ b/test/crd_checks.go @@ -122,7 +122,7 @@ func CheckConfigurationState(client *ServingClients, name string, inState func(r } // WaitForRevisionState polls the status of the Revision called name -// from client every interval until inState returns `true` indicating it +// from client every `interval` until `inState` returns `true` indicating it // is done, returns an error or timeout. desc will be used to name the metric // that is emitted to track how long it took for name to get into the state checked by inState. func WaitForRevisionState(client *ServingClients, name string, inState func(r *v1alpha1.Revision) (bool, error), desc string) error { @@ -163,7 +163,7 @@ func CheckRevisionState(client *ServingClients, name string, inState func(r *v1a } // WaitForServiceState polls the status of the Service called name -// from client every interval until inState returns `true` indicating it +// from client every `interval` until `inState` returns `true` indicating it // is done, returns an error or timeout. desc will be used to name the metric // that is emitted to track how long it took for name to get into the state checked by inState. func WaitForServiceState(client *ServingClients, name string, inState func(s *v1alpha1.Service) (bool, error), desc string) error { diff --git a/test/service.go b/test/service.go index c5967b5264ff..27dc2309ebf4 100644 --- a/test/service.go +++ b/test/service.go @@ -66,7 +66,8 @@ func validateCreatedServiceStatus(clients *Clients, names *ResourceNames) error }) } -func getResourceObjects(clients *Clients, names ResourceNames) (*ResourceObjects, error) { +// GetResourceObjects obtains the services resources from the k8s API server. +func GetResourceObjects(clients *Clients, names ResourceNames) (*ResourceObjects, error) { routeObject, err := clients.ServingClient.Routes.Get(names.Route, metav1.GetOptions{}) if err != nil { return nil, err @@ -130,7 +131,7 @@ func CreateReleaseServiceWithLatest( } logger.Info("Getting latest objects Created by Service.") - return getResourceObjects(clients, *names) + return GetResourceObjects(clients, *names) } // CreateRunLatestServiceReady creates a new RunLatest Service in state 'Ready'. This function expects Service and Image name passed in through 'names'. @@ -163,7 +164,7 @@ func CreateRunLatestServiceReady(logger *logging.BaseLogger, clients *Clients, n } logger.Info("Getting latest objects Created by Service.") - return getResourceObjects(clients, *names) + return GetResourceObjects(clients, *names) } // CreateReleaseService creates a service in namespace with the name names.Service and names.Image,