diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 0b06d294b81e..554d57d87a3c 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -78,6 +78,10 @@ status: # along with a cluster-specific prefix (here, mydomain.com). domain: my-service.default.mydomain.com + # domainInternal: A DNS name for the default (traffic-split) route which can + # be accessed without leaving the cluster environment. + domainInternal: my-service.default.svc.cluster.local + traffic: # current rollout status list. configurationName references # are dereferenced to latest revision @@ -286,6 +290,11 @@ status: # Note that logs may still be access controlled separately from # access to the API object. logUrl: "http://logging.infra.mycompany.com/...?filter=revision_uid=a1e34&..." + + # serviceName: The name for the core Kubernetes Service that fronts this + # revision. Typically, the name will be the same as the name of the + # revision. + serviceName: myservice-a1e34 ``` @@ -367,7 +376,11 @@ status: # domain: The hostname used to access the default (traffic-split) # route. Typically, this will be composed of the name and namespace # along with a cluster-specific prefix (here, mydomain.com). - domain: my-service.default.mydomain.com + domain: myservice.default.mydomain.com + + # domainInternal: A DNS name for the default (traffic-split) route which can + # be accessed without leaving the cluster environment. + domainInternal: myservice.default.svc.cluster.local # current rollout status list. configurationName references # are dereferenced to latest revision diff --git a/pkg/apis/serving/v1alpha1/route_types.go b/pkg/apis/serving/v1alpha1/route_types.go index c62e98b4185b..3a33ad03703b 100644 --- a/pkg/apis/serving/v1alpha1/route_types.go +++ b/pkg/apis/serving/v1alpha1/route_types.go @@ -131,6 +131,12 @@ type RouteStatus struct { // +optional Domain string `json:"domain,omitempty"` + // DomainInternal holds the top-level domain that will distribute traffic over the provided + // targets from inside the cluster. It generally has the form + // {route-name}.{route-namespace}.svc.cluster.local + // +optional + DomainInternal string `json:"domainInternal,omitempty"` + // Traffic holds the configured traffic distribution. // These entries will always contain RevisionName references. // When ConfigurationName appears in the spec, this will hold the diff --git a/pkg/apis/serving/v1alpha1/service_types.go b/pkg/apis/serving/v1alpha1/service_types.go index 9d693d3a623d..c9df581e63c1 100644 --- a/pkg/apis/serving/v1alpha1/service_types.go +++ b/pkg/apis/serving/v1alpha1/service_types.go @@ -134,6 +134,13 @@ type ServiceStatus struct { // +optional Domain string `json:"domain,omitempty"` + // From RouteStatus. + // DomainInternal holds the top-level domain that will distribute traffic over the provided + // targets from inside the cluster. It generally has the form + // {route-name}.{route-namespace}.svc.cluster.local + // +optional + DomainInternal string `json:"domainInternal,omitempty"` + // From RouteStatus. // Traffic holds the configured traffic distribution. // These entries will always contain RevisionName references. @@ -274,6 +281,7 @@ func (ss *ServiceStatus) PropagateConfigurationStatus(cs ConfigurationStatus) { func (ss *ServiceStatus) PropagateRouteStatus(rs RouteStatus) { ss.Domain = rs.Domain + ss.DomainInternal = rs.DomainInternal ss.Traffic = rs.Traffic rc := rs.GetCondition(RouteConditionReady) diff --git a/pkg/controller/route/cruds.go b/pkg/controller/route/cruds.go index db1bb786bfc4..a37dee01f4c1 100644 --- a/pkg/controller/route/cruds.go +++ b/pkg/controller/route/cruds.go @@ -81,6 +81,7 @@ func (c *Controller) reconcilePlaceholderService(ctx context.Context, route *v1a return err } logger.Infof("Created service %s", name) + route.Status.DomainInternal = resourcenames.K8sServiceFullname(route) c.Recorder.Eventf(route, corev1.EventTypeNormal, "Created", "Created service %q", name) } else if err != nil { return err diff --git a/pkg/controller/route/table_test.go b/pkg/controller/route/table_test.go index cb336b263a19..ff92e41ccc39 100644 --- a/pkg/controller/route/table_test.go +++ b/pkg/controller/route/table_test.go @@ -64,7 +64,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "first-reconcile", "not-ready", &v1alpha1.RouteStatus{ - Domain: "first-reconcile.default.example.com", + Domain: "first-reconcile.default.example.com", + DomainInternal: "first-reconcile.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionUnknown, @@ -101,7 +102,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "first-reconcile", "permanently-failed", &v1alpha1.RouteStatus{ - Domain: "first-reconcile.default.example.com", + Domain: "first-reconcile.default.example.com", + DomainInternal: "first-reconcile.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionFalse, @@ -142,7 +144,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "first-reconcile", "not-ready", &v1alpha1.RouteStatus{ - Domain: "first-reconcile.default.example.com", + Domain: "first-reconcile.default.example.com", + DomainInternal: "first-reconcile.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionUnknown, @@ -193,7 +196,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "becomes-ready", "config", &v1alpha1.RouteStatus{ - Domain: "becomes-ready.default.example.com", + Domain: "becomes-ready.default.example.com", + DomainInternal: "becomes-ready.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -235,7 +239,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "label-config-failure", "config", &v1alpha1.RouteStatus{ - Domain: "label-config-failure.default.example.com", + Domain: "label-config-failure.default.example.com", + DomainInternal: "label-config-failure.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionUnknown, @@ -318,7 +323,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "vs-create-failure", "config", &v1alpha1.RouteStatus{ - Domain: "vs-create-failure.default.example.com", + Domain: "vs-create-failure.default.example.com", + DomainInternal: "vs-create-failure.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionUnknown, @@ -333,7 +339,8 @@ func TestReconcile(t *testing.T) { Name: "steady state", Objects: []runtime.Object{ simpleRunLatest("default", "steady-state", "config", &v1alpha1.RouteStatus{ - Domain: "steady-state.default.example.com", + Domain: "steady-state.default.example.com", + DomainInternal: "steady-state.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -379,7 +386,8 @@ func TestReconcile(t *testing.T) { Objects: []runtime.Object{ addRouteLabel( simpleRunLatest("default", "different-domain", "config", &v1alpha1.RouteStatus{ - Domain: "different-domain.default.another-example.com", + Domain: "different-domain.default.another-example.com", + DomainInternal: "different-domain.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -426,7 +434,8 @@ func TestReconcile(t *testing.T) { Name: "new latest created revision", Objects: []runtime.Object{ simpleRunLatest("default", "new-latest-created", "config", &v1alpha1.RouteStatus{ - Domain: "new-latest-created.default.example.com", + Domain: "new-latest-created.default.example.com", + DomainInternal: "new-latest-created.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -477,7 +486,8 @@ func TestReconcile(t *testing.T) { Name: "new latest ready revision", Objects: []runtime.Object{ simpleRunLatest("default", "new-latest-ready", "config", &v1alpha1.RouteStatus{ - Domain: "new-latest-ready.default.example.com", + Domain: "new-latest-ready.default.example.com", + DomainInternal: "new-latest-ready.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -541,7 +551,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "new-latest-ready", "config", &v1alpha1.RouteStatus{ - Domain: "new-latest-ready.default.example.com", + Domain: "new-latest-ready.default.example.com", + DomainInternal: "new-latest-ready.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -566,7 +577,8 @@ func TestReconcile(t *testing.T) { }, Objects: []runtime.Object{ simpleRunLatest("default", "update-vs-failure", "config", &v1alpha1.RouteStatus{ - Domain: "update-vs-failure.default.example.com", + Domain: "update-vs-failure.default.example.com", + DomainInternal: "update-vs-failure.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -634,7 +646,8 @@ func TestReconcile(t *testing.T) { Name: "reconcile service mutation", Objects: []runtime.Object{ simpleRunLatest("default", "svc-mutation", "config", &v1alpha1.RouteStatus{ - Domain: "svc-mutation.default.example.com", + Domain: "svc-mutation.default.example.com", + DomainInternal: "svc-mutation.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -687,7 +700,8 @@ func TestReconcile(t *testing.T) { }, Objects: []runtime.Object{ simpleRunLatest("default", "svc-mutation", "config", &v1alpha1.RouteStatus{ - Domain: "svc-mutation.default.example.com", + Domain: "svc-mutation.default.example.com", + DomainInternal: "svc-mutation.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -735,7 +749,8 @@ func TestReconcile(t *testing.T) { Name: "allow cluster ip", Objects: []runtime.Object{ simpleRunLatest("default", "cluster-ip", "config", &v1alpha1.RouteStatus{ - Domain: "cluster-ip.default.example.com", + Domain: "cluster-ip.default.example.com", + DomainInternal: "cluster-ip.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -780,7 +795,8 @@ func TestReconcile(t *testing.T) { Name: "reconcile virtual service mutation", Objects: []runtime.Object{ simpleRunLatest("default", "virt-svc-mutation", "config", &v1alpha1.RouteStatus{ - Domain: "virt-svc-mutation.default.example.com", + Domain: "virt-svc-mutation.default.example.com", + DomainInternal: "virt-svc-mutation.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -842,7 +858,8 @@ func TestReconcile(t *testing.T) { Name: "config labelled by another route", Objects: []runtime.Object{ simpleRunLatest("default", "licked-cookie", "config", &v1alpha1.RouteStatus{ - Domain: "licked-cookie.default.example.com", + Domain: "licked-cookie.default.example.com", + DomainInternal: "licked-cookie.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -889,7 +906,8 @@ func TestReconcile(t *testing.T) { Objects: []runtime.Object{ // The status reflects "oldconfig", but the spec "newconfig". simpleRunLatest("default", "change-configs", "newconfig", &v1alpha1.RouteStatus{ - Domain: "change-configs.default.example.com", + Domain: "change-configs.default.example.com", + DomainInternal: "change-configs.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -965,7 +983,8 @@ func TestReconcile(t *testing.T) { }, { // Status updated to "newconfig" Object: simpleRunLatest("default", "change-configs", "newconfig", &v1alpha1.RouteStatus{ - Domain: "change-configs.default.example.com", + Domain: "change-configs.default.example.com", + DomainInternal: "change-configs.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -991,7 +1010,8 @@ func TestReconcile(t *testing.T) { }, WantUpdates: []clientgotesting.UpdateActionImpl{{ Object: simpleRunLatest("default", "config-missing", "not-found", &v1alpha1.RouteStatus{ - Domain: "config-missing.default.example.com", + Domain: "config-missing.default.example.com", + DomainInternal: "config-missing.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionFalse, @@ -1018,7 +1038,8 @@ func TestReconcile(t *testing.T) { }, WantUpdates: []clientgotesting.UpdateActionImpl{{ Object: simplePinned("default", "missing-revision-direct", "not-found", &v1alpha1.RouteStatus{ - Domain: "missing-revision-direct.default.example.com", + Domain: "missing-revision-direct.default.example.com", + DomainInternal: "missing-revision-direct.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionFalse, @@ -1051,7 +1072,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "missing-revision-indirect", "config", &v1alpha1.RouteStatus{ - Domain: "missing-revision-indirect.default.example.com", + Domain: "missing-revision-indirect.default.example.com", + DomainInternal: "missing-revision-indirect.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionFalse, @@ -1112,7 +1134,8 @@ func TestReconcile(t *testing.T) { Object: simplePinned("default", "pinned-becomes-ready", // Use the config's revision name. simpleReadyConfig("default", "config").Status.LatestReadyRevisionName, &v1alpha1.RouteStatus{ - Domain: "pinned-becomes-ready.default.example.com", + Domain: "pinned-becomes-ready.default.example.com", + DomainInternal: "pinned-becomes-ready.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -1210,7 +1233,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: routeWithTraffic("default", "named-traffic-split", &v1alpha1.RouteStatus{ - Domain: "named-traffic-split.default.example.com", + Domain: "named-traffic-split.default.example.com", + DomainInternal: "named-traffic-split.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -1241,7 +1265,8 @@ func TestReconcile(t *testing.T) { // Start from a steady state referencing "blue", and modify the route spec to point to "green" instead. Objects: []runtime.Object{ simpleRunLatest("default", "switch-configs", "green", &v1alpha1.RouteStatus{ - Domain: "switch-configs.default.example.com", + Domain: "switch-configs.default.example.com", + DomainInternal: "switch-configs.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -1312,7 +1337,8 @@ func TestReconcile(t *testing.T) { ), }, { Object: simpleRunLatest("default", "switch-configs", "green", &v1alpha1.RouteStatus{ - Domain: "switch-configs.default.example.com", + Domain: "switch-configs.default.example.com", + DomainInternal: "switch-configs.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -1338,7 +1364,8 @@ func TestReconcile(t *testing.T) { }, Objects: []runtime.Object{ simpleRunLatest("default", "rmlabel-config-failure", "green", &v1alpha1.RouteStatus{ - Domain: "rmlabel-config-failure.default.example.com", + Domain: "rmlabel-config-failure.default.example.com", + DomainInternal: "rmlabel-config-failure.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, @@ -1397,7 +1424,8 @@ func TestReconcile(t *testing.T) { }, Objects: []runtime.Object{ simpleRunLatest("default", "addlabel-config-failure", "green", &v1alpha1.RouteStatus{ - Domain: "addlabel-config-failure.default.example.com", + Domain: "addlabel-config-failure.default.example.com", + DomainInternal: "addlabel-config-failure.default.svc.cluster.local", Conditions: []v1alpha1.RouteCondition{{ Type: v1alpha1.RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, diff --git a/test/conformance/route_test.go b/test/conformance/route_test.go index cf53d236fac1..cd85d560da81 100644 --- a/test/conformance/route_test.go +++ b/test/conformance/route_test.go @@ -106,6 +106,11 @@ func assertResourcesUpdatedWhenRevisionIsReady(t *testing.T, logger *zap.Sugared if err != nil { t.Fatalf("The Route %s was not updated to route traffic to the Revision %s: %v", names.Route, names.Revision, err) } + logger.Infof("TODO: The Route is accessible from inside the cluster without external DNS") + err = test.CheckRouteState(clients.Routes, names.Route, test.TODO_RouteTrafficToRevisionWithInClusterDNS) + if err != nil { + t.Fatalf("The Route %s was not able to route traffic to the Revision %s with in cluster DNS: %v", names.Route, names.Revision, err) + } } func getNextRevisionName(clients *test.Clients, names test.ResourceNames) (string, error) { diff --git a/test/conformance/service_test.go b/test/conformance/service_test.go index 96df2c586a43..05916f8bfd78 100644 --- a/test/conformance/service_test.go +++ b/test/conformance/service_test.go @@ -88,6 +88,12 @@ func assertServiceResourcesUpdated(t *testing.T, logger *zap.SugaredLogger, clie t.Fatalf("The Route %s was not updated to route traffic to the Revision %s: %v", names.Route, names.Revision, err) } + logger.Infof("TODO: The Service's Route is accessible from inside the cluster without external DNS") + err = test.CheckServiceState(clients.Services, names.Service, test.TODO_ServiceTrafficToRevisionWithInClusterDNS) + if err != nil { + t.Fatalf("The Service %s was not able to route traffic to the Revision %s with in cluster DNS: %v", names.Service, names.Revision, err) + } + // TODO(#1381): Check labels and annotations. } diff --git a/test/crd_checks.go b/test/crd_checks.go index d237f5bd87c8..fc9cd955c32f 100644 --- a/test/crd_checks.go +++ b/test/crd_checks.go @@ -157,6 +157,22 @@ func WaitForServiceState(client servingtyped.ServiceInterface, name string, inSt }) } +// CheckServiceState verifies the status of the Service called name from client +// is in a particular state by calling `inState` and expecting `true`. +// This is the non-polling variety of WaitForServiceState +func CheckServiceState(client servingtyped.ServiceInterface, name string, inState func(s *v1alpha1.Service) (bool, error)) error { + s, err := client.Get(name, metav1.GetOptions{}) + if err != nil { + return err + } + if done, err := inState(s); err != nil { + return err + } else if !done { + return fmt.Errorf("service %q is not in desired state: %+v", name, s) + } + return nil +} + // WaitForIngressState polls the status of the Ingress called name // 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 diff --git a/test/states.go b/test/states.go index 9ffcaaf875fd..4c91591fb3c6 100644 --- a/test/states.go +++ b/test/states.go @@ -46,6 +46,28 @@ func AllRouteTrafficAtRevision(names ResourceNames) func(r *v1alpha1.Route) (boo } } +// RouteTrafficToRevisionWithInClusterDNS will check the revision that route r is routing +// traffic using in cluster DNS and return true if the revision received the request. +func TODO_RouteTrafficToRevisionWithInClusterDNS(r *v1alpha1.Route) (bool, error) { + if r.Status.DomainInternal == "" { + return false, fmt.Errorf("Expected route %s to have in cluster dns status set", r.Name) + } + // TODO make a curl request from inside the cluster using + // r.Status.DomainInternal to validate DNS is set correctly + return true, nil +} + +// ServiceTrafficToRevisionWithInClusterDNS will check the revision that route r is routing +// traffic using in cluster DNS and return true if the revision received the request. +func TODO_ServiceTrafficToRevisionWithInClusterDNS(s *v1alpha1.Service) (bool, error) { + if s.Status.DomainInternal == "" { + return false, fmt.Errorf("Expected service %s to have in cluster dns status set", s.Name) + } + // TODO make a curl request from inside the cluster using + // s.Status.DomainInternal to validate DNS is set correctly + return true, nil +} + // IsRevisionReady will check the status conditions of the revision and return true if the revision is // ready to serve traffic. It will return false if the status indicates a state other than deploying // or being ready. It will also return false if the type of the condition is unexpected.