diff --git a/pkg/reconciler/route/route.go b/pkg/reconciler/route/route.go index 95985bd8ff30..0102b1a7aeab 100644 --- a/pkg/reconciler/route/route.go +++ b/pkg/reconciler/route/route.go @@ -233,6 +233,10 @@ func (c *Reconciler) reconcile(ctx context.Context, r *v1alpha1.Route) error { traffic, err := c.configureTraffic(ctx, r, clusterLocalServiceNames) if traffic == nil || err != nil { // Traffic targets aren't ready, no need to configure child resources. + // Need to update ObservedGeneration, otherwise Route's Ready state won't + // be propagated to Service and the Service's RoutesReady will stay in + // 'Unknown'. + r.Status.ObservedGeneration = r.Generation return err } diff --git a/test/e2e/image_pull_error_test.go b/test/e2e/image_pull_error_test.go index 8b84d57e26a0..660eb9f540a4 100644 --- a/test/e2e/image_pull_error_test.go +++ b/test/e2e/image_pull_error_test.go @@ -56,7 +56,7 @@ func TestImagePullError(t *testing.T) { names.Config = serviceresourcenames.Configuration(svc) err = v1a1test.WaitForServiceState(clients.ServingAlphaClient, names.Service, func(r *v1alpha1.Service) (bool, error) { - cond := r.Status.GetCondition(v1alpha1.ConfigurationConditionReady) + cond := r.Status.GetCondition(v1alpha1.ServiceConditionConfigurationsReady) if cond != nil && !cond.IsUnknown() { if cond.IsFalse() { if cond.Reason == "RevisionFailed" { diff --git a/test/e2e/pod_schedule_error_test.go b/test/e2e/pod_schedule_error_test.go index c04b629dc154..5131cb123fad 100644 --- a/test/e2e/pod_schedule_error_test.go +++ b/test/e2e/pod_schedule_error_test.go @@ -72,7 +72,7 @@ func TestPodScheduleError(t *testing.T) { names.Config = serviceresourcenames.Configuration(svc) err = v1a1test.WaitForServiceState(clients.ServingAlphaClient, names.Service, func(r *v1alpha1.Service) (bool, error) { - cond := r.Status.GetCondition(v1alpha1.ConfigurationConditionReady) + cond := r.Status.GetCondition(v1alpha1.ServiceConditionConfigurationsReady) if cond != nil && !cond.IsUnknown() { if strings.Contains(cond.Message, errorMsg) && cond.IsFalse() { return true, nil diff --git a/test/e2e/route_service_test.go b/test/e2e/route_service_test.go new file mode 100644 index 000000000000..84c094473c53 --- /dev/null +++ b/test/e2e/route_service_test.go @@ -0,0 +1,84 @@ +// +build e2e + +/* +Copyright 2019 The Knative 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 e2e + +import ( + "testing" + + "knative.dev/serving/pkg/apis/serving/v1alpha1" + "knative.dev/serving/pkg/apis/serving/v1beta1" + "knative.dev/serving/test" + v1a1test "knative.dev/serving/test/v1alpha1" + "knative.dev/pkg/test/logstream" + serviceresourcenames "knative.dev/serving/pkg/reconciler/service/resources/names" + . "knative.dev/serving/pkg/testing/v1alpha1" +) + +// TestRoutesNotReady tests the scenario that when Route's status is +// Ready == False, the Service's RoutesReady value should change from +// Unknown to False +func TestRoutesNotReady(t *testing.T) { + t.Parallel() + cancel := logstream.Start(t) + defer cancel() + + clients := Setup(t) + + names := test.ResourceNames{ + Service: test.ObjectNameForTest(t), + Image: test.PizzaPlanet1, + } + + test.CleanupOnInterrupt(func() { test.TearDown(clients, names) }) + defer test.TearDown(clients, names) + + withTrafficSpec := WithInlineRouteSpec(v1alpha1.RouteSpec{ + Traffic: []v1alpha1.TrafficTarget{ + { + TrafficTarget: v1beta1.TrafficTarget{ + RevisionName: "foobar", // Invalid revision name. This allows Revision creation to succeed and Route configuration to fail + Percent: 100, + }, + }, + }, + }) + + t.Log("Creating a new Service with an invalid traffic target.") + svc, err := v1a1test.CreateLatestService(t, clients, names, withTrafficSpec) + if err != nil { + t.Fatalf("Failed to create initial Service %q: %#v", names.Service, err) + } + + t.Logf("Waiting for Service %q ObservedGeneration to match Generation, and status transition to Ready == False.", names.Service) + if err := v1a1test.WaitForServiceState(clients.ServingAlphaClient, names.Service, v1a1test.IsServiceNotReady, "ServiceIsNotReady"); err != nil { + t.Fatalf("Failed waiting for Service %q to transition to Ready == False: %#v", names.Service, err) + } + + t.Logf("Validating Route %q has reconciled to Ready == False.", serviceresourcenames.Route(svc)) + // Check Route is not ready + if err = v1a1test.CheckRouteState(clients.ServingAlphaClient, serviceresourcenames.Route(svc), v1a1test.IsRouteNotReady); err != nil { + t.Fatalf("The Route %q was marked as Ready to serve traffic but it should not be: %#v", serviceresourcenames.Route(svc), err) + } + + // Wait for RoutesReady to become False + t.Logf("Validating Service %q has reconciled to RoutesReady == False.", names.Service) + if err = v1a1test.CheckServiceState(clients.ServingAlphaClient, names.Service, v1a1test.IsServiceRoutesNotReady); err != nil { + t.Fatalf("Service %q was not marked RoutesReady == False: %#v", names.Service, err) + } +} diff --git a/test/v1alpha1/service.go b/test/v1alpha1/service.go index 6752e53bd7e6..ec2999f79502 100644 --- a/test/v1alpha1/service.go +++ b/test/v1alpha1/service.go @@ -33,6 +33,7 @@ import ( "knative.dev/pkg/test/logging" "knative.dev/serving/pkg/apis/serving/v1alpha1" serviceresourcenames "knative.dev/serving/pkg/reconciler/service/resources/names" + corev1 "k8s.io/api/core/v1" ptest "knative.dev/pkg/test" rtesting "knative.dev/serving/pkg/testing/v1alpha1" @@ -346,8 +347,14 @@ func IsServiceReady(s *v1alpha1.Service) (bool, error) { return s.Generation == s.Status.ObservedGeneration && s.Status.IsReady(), nil } -// IsServiceNotReady will check the status conditions of the service and return true if the service is -// not ready. +// IsServiceNotReady checks the Ready status condition of the service and returns true only if Ready is set to False. func IsServiceNotReady(s *v1alpha1.Service) (bool, error) { - return s.Generation == s.Status.ObservedGeneration && !s.Status.IsReady(), nil + result := s.Status.GetCondition(v1alpha1.ServiceConditionReady) + return s.Generation == s.Status.ObservedGeneration && result != nil && result.Status == corev1.ConditionFalse, nil +} + +// IsServiceRoutesNotReady checks the RoutesReady status of the service and returns true only if RoutesReady is set to False. +func IsServiceRoutesNotReady(s *v1alpha1.Service) (bool, error) { + result := s.Status.GetCondition(v1alpha1.ServiceConditionRoutesReady) + return s.Generation == s.Status.ObservedGeneration && result != nil && result.Status == corev1.ConditionFalse, nil }