diff --git a/Gopkg.lock b/Gopkg.lock index 849b000df983..cd06ddae2cd7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -972,6 +972,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "94726259256e54eeb5e76941f9f2c5aab4d05209d3fcf263fbe3eb045df21192" + inputs-digest = "07c1e4a97a3aec5e38a0e582935ac99619dd3e873e267886cc0cb67e4d600c43" solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/controller/main.go b/cmd/controller/main.go index ea199f94c9da..09cbe7436027 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -145,7 +145,6 @@ func main() { deploymentInformer := kubeInformerFactory.Apps().V1().Deployments() endpointsInformer := kubeInformerFactory.Core().V1().Endpoints() coreServiceInformer := kubeInformerFactory.Core().V1().Services() - ingressInformer := kubeInformerFactory.Extensions().V1beta1().Ingresses() vpaInformer := vpaInformerFactory.Poc().V1alpha1().VerticalPodAutoscalers() // Build all of our controllers, with the clients constructed above. @@ -171,7 +170,6 @@ func main() { opt, routeInformer, configurationInformer, - ingressInformer, autoscaleEnableScaleToZero, ), service.NewController( @@ -203,7 +201,6 @@ func main() { deploymentInformer.Informer().HasSynced, coreServiceInformer.Informer().HasSynced, endpointsInformer.Informer().HasSynced, - ingressInformer.Informer().HasSynced, } { if ok := cache.WaitForCacheSync(stopCh, synced); !ok { logger.Fatalf("failed to wait for cache at index %v to sync", i) diff --git a/config/200-clusterrole.yaml b/config/200-clusterrole.yaml index 0aef7b2ad07e..8d2074111e4b 100644 --- a/config/200-clusterrole.yaml +++ b/config/200-clusterrole.yaml @@ -23,11 +23,11 @@ rules: verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] - apiGroups: ["build.dev"] resources: ["builds"] - verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] - - apiGroups: ["config.istio.io"] - resources: ["routerules"] - verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] ---- + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + - apiGroups: ["networking.istio.io"] + resources: ["virtualservices"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] +--- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -53,7 +53,7 @@ rules: verbs: ["get", "list", "update", "patch", "watch"] - apiGroups: ["build.dev"] resources: ["builds"] - verbs: ["get", "list", "update", "patch", "watch"] - - apiGroups: ["config.istio.io"] - resources: ["routerules"] - verbs: ["get", "list", "update", "patch", "watch"] + verbs: ["get", "list", "update", "patch", "watch"] + - apiGroups: ["networking.istio.io"] + resources: ["virtualservices"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] diff --git a/config/202-gateway.yaml b/config/202-gateway.yaml new file mode 100644 index 000000000000..41936efdb228 --- /dev/null +++ b/config/202-gateway.yaml @@ -0,0 +1,221 @@ +# We stand up a new Gateway service to receive all external traffic +# for Knative pods. These pods are basically standalone Envoy proxy +# pods to convert all external traffic into cluster traffic. +# +# +# The reason for standing up these pods are because Istio Gateway +# cannot not share these ingress pods. Istio provide a default, but +# we don't want to use it and causing unwanted sharing with users' +# Gateways if they have some. +# +# The YAML is cloned from Istio's. However, in the future we may want +# to incorporate more of our logic to tailor to our users' specific +# needs. + +# This is the shared Gateway for all Knative routes to use. +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: knative-shared-gateway + namespace: knative-serving +spec: + selector: + knative: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" + - port: + number: 443 + name: https + protocol: HTTPS + hosts: + - "*" +--- +# This is the Service definition for the ingress pods serving +# Knative's shared Gateway. +# +# Source: istio/charts/ingressgateway/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: knative-ingressgateway + namespace: istio-system + labels: + chart: ingressgateway-0.8.0 + release: RELEASE-NAME + heritage: Tiller + knative: ingressgateway +spec: + type: LoadBalancer + selector: + knative: ingressgateway + ports: + - + name: http + nodePort: 32380 + port: 80 + - + name: https + nodePort: 32390 + port: 443 + - + name: tcp + nodePort: 32400 + port: 32400 +--- +# This is the corresponding Deployment to backed the aforementioned Service. +# +# Source: istio/charts/ingressgateway/templates/deployment.yaml +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: knative-ingressgateway + namespace: istio-system + labels: + app: knative-ingressgateway + chart: ingressgateway-0.8.0 + release: RELEASE-NAME + heritage: Tiller + knative: ingressgateway +spec: + replicas: + template: + metadata: + labels: + knative: ingressgateway + annotations: + sidecar.istio.io/inject: "false" + spec: + serviceAccountName: istio-ingressgateway-service-account + containers: + - name: ingressgateway + image: "docker.io/istio/proxyv2:0.8.0" + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + - containerPort: 443 + - containerPort: 32400 + args: + - proxy + - router + - -v + - "2" + - --discoveryRefreshDelay + - '1s' #discoveryRefreshDelay + - --drainDuration + - '45s' #drainDuration + - --parentShutdownDuration + - '1m0s' #parentShutdownDuration + - --connectTimeout + - '10s' #connectTimeout + - --serviceCluster + - knative-ingressgateway + - --zipkinAddress + - zipkin:9411 + - --statsdUdpAddress + - istio-statsd-prom-bridge:9125 + - --proxyAdminPort + - "15000" + - --controlPlaneAuthPolicy + - NONE + - --discoveryAddress + - istio-pilot:8080 + resources: + {} + + env: + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: INSTANCE_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: ISTIO_META_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + volumeMounts: + - name: istio-certs + mountPath: /etc/certs + readOnly: true + - name: ingressgateway-certs + mountPath: "/etc/istio/ingressgateway-certs" + readOnly: true + volumes: + - name: istio-certs + secret: + secretName: "istio.default" + optional: true + - name: ingressgateway-certs + secret: + secretName: "istio-ingressgateway-certs" + optional: true + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: beta.kubernetes.io/arch + operator: In + values: + - amd64 + - ppc64le + - s390x + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 2 + preference: + matchExpressions: + - key: beta.kubernetes.io/arch + operator: In + values: + - amd64 + - weight: 2 + preference: + matchExpressions: + - key: beta.kubernetes.io/arch + operator: In + values: + - ppc64le + - weight: 2 + preference: + matchExpressions: + - key: beta.kubernetes.io/arch + operator: In + values: + - s390x +--- +# This is the horizontal pod autoscaler to make sure the ingress Pods +# scale up to meet traffic demand. +# +# Source: istio/charts/ingressgateway/templates/autoscale.yaml +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: knative-ingressgateway + namespace: istio-system +spec: + minReplicas: 1 + # TODO(1411): Document/fix this. We are choosing an arbitrary 10 here. + maxReplicas: 10 + scaleTargetRef: + apiVersion: apps/v1beta1 + kind: Deployment + name: knative-ingressgateway + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: 60 diff --git a/docs/spec/errors.md b/docs/spec/errors.md index 76ed89d46402..559bdc61fb49 100644 --- a/docs/spec/errors.md +++ b/docs/spec/errors.md @@ -424,31 +424,6 @@ status: message: "Configuration 'abc' referenced in traffic not found" ``` -### Unable to create Ingress - -If the Route is unable to create an Ingress resource to route its -traffic to Revisions, the `IngressReady` condition will be marked -as `False` with a reason of `NoIngress`. - -```http -GET /apis/serving.knative.dev/v1alpha1/namespaces/default/routes/my-service -``` - -```yaml -... -status: - traffic: [] - conditions: - - type: Ready - status: False - reason: NoIngress - message: "Unable to create Ingress 'my-service-ingress'" - - type: IngressReady - status: False - reason: NoIngress - message: "Unable to create Ingress 'my-service-ingress'" -``` - ### Latest Revision of a Configuration deleted If the most recent Revision is deleted, the Configuration will set diff --git a/docs/spec/motivation.md b/docs/spec/motivation.md index f1826c3f24d6..f7f03ed9cc9b 100644 --- a/docs/spec/motivation.md +++ b/docs/spec/motivation.md @@ -10,13 +10,12 @@ We define serverless workloads as computing workloads that are: * Primarily driven by application level (L7 -- HTTP, for example) request traffic -While Kubernetes provides basic primitives like Deployment, Service, -and Ingress in support of this model, our experience suggests that a -more compact and richer opinionated model has substantial benefit for -developers. In particular, by standardizing on higher-level primitives -which perform substantial amounts of automation of common -infrastructure, it should be possible to build consistent toolkits -that provide a richer experience than updating yaml files with +While Kubernetes provides basic primitives like Deployment, and Service in +support of this model, our experience suggests that a more compact and richer +opinionated model has substantial benefit for developers. In particular, by +standardizing on higher-level primitives which perform substantial amounts of +automation of common infrastructure, it should be possible to build consistent +toolkits that provide a richer experience than updating yaml files with `kubectl`. The Knative Serving APIs consist of Compute API (these documents), diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 9703fad5e84b..7a2ccb1eee72 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -27,8 +27,11 @@ CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${SERVING_ROOT}; ls -d -1 ./vendor/k8s.io/code-g # instead of the $GOPATH directly. For normal projects this can be dropped. ${CODEGEN_PKG}/generate-groups.sh "deepcopy,client,informer,lister" \ github.com/knative/serving/pkg/client github.com/knative/serving/pkg/apis \ - "serving:v1alpha1 istio:v1alpha2" \ + "serving:v1alpha1 istio:v1alpha3" \ --go-header-file ${SERVING_ROOT}/hack/boilerplate/boilerplate.go.txt +# Update code to change Gatewaies -> Gateways to workaround cleverness of codegen pluralizer. +find -name '*.go' -exec grep -l atewaies {} \; | xargs sed 's/atewaies/ateways/g' -i + # Make sure our dependencies are up-to-date ${SERVING_ROOT}/hack/update-deps.sh diff --git a/pkg/apis/istio/register.go b/pkg/apis/istio/register.go index 00166ec3874d..647eb38a0e18 100644 --- a/pkg/apis/istio/register.go +++ b/pkg/apis/istio/register.go @@ -17,5 +17,5 @@ limitations under the License. package istio const ( - GroupName = "config.istio.io" + GroupName = "networking.istio.io" ) diff --git a/pkg/apis/istio/v1alpha2/routerule_types.go b/pkg/apis/istio/v1alpha2/routerule_types.go deleted file mode 100644 index 3323af9068ba..000000000000 --- a/pkg/apis/istio/v1alpha2/routerule_types.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2018 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 v1alpha2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// RouteRule -type RouteRule struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RouteRuleSpec `json:"spec,omitempty"` -} - -// Istio route looks like so, but couldn't find a k8s/go definition for it -// so we'll just create one. This is terrible, but it just might work for -// now, but if things change on their end, this will most certainly break :( -// spec: -// destination: -// # this matches what's in the ingress rule as a placeholder k8s service -// name: k8s-placeholder-service -// route: -// - destination: -// name: revision-service-1 -// match: -// request: -// headers: -// authority: -// regex: foo.example.com -// weight: 90 -// - destination: -// name: revision-service-2 -// namespace: revision-2-namespace -// weight: 10 -// # https://github.com/istio/istio/blob/master/tests/helm/templates/rule-default-route-append-headers.yaml -// appendHeaders: -// istio-custom-header: user-defined-value -type DestinationWeight struct { - Destination IstioService `json:"destination"` - Weight int `json:"weight"` -} - -type IstioService struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Domain string `json:"domain"` -} - -type Match struct { - Request MatchRequest `json:"request"` -} - -type MatchRequest struct { - Headers Headers `json:"headers"` -} - -type Headers struct { - Authority MatchString `json:"authority"` -} - -type MatchString struct { - Exact string `json:"exact,omitempty"` - Regex string `json:"regex,omitempty"` - Prefix string `json:"prefix,omitempty"` -} - -type RouteRuleSpec struct { - Destination IstioService `json:"destination"` - Match Match `json:"match,omitempty"` - Route []DestinationWeight `json:"route"` - AppendHeaders map[string]string `json:"appendHeaders,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// RouteRuleList is a list of RouteRule resources -type RouteRuleList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []RouteRule `json:"items"` -} diff --git a/pkg/apis/istio/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/istio/v1alpha2/zz_generated.deepcopy.go deleted file mode 100644 index e95021dfe807..000000000000 --- a/pkg/apis/istio/v1alpha2/zz_generated.deepcopy.go +++ /dev/null @@ -1,217 +0,0 @@ -// +build !ignore_autogenerated - -/* -Copyright 2018 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. -*/ - -// This file was autogenerated by deepcopy-gen. Do not edit it manually! - -package v1alpha2 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DestinationWeight) DeepCopyInto(out *DestinationWeight) { - *out = *in - out.Destination = in.Destination - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationWeight. -func (in *DestinationWeight) DeepCopy() *DestinationWeight { - if in == nil { - return nil - } - out := new(DestinationWeight) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Headers) DeepCopyInto(out *Headers) { - *out = *in - out.Authority = in.Authority - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Headers. -func (in *Headers) DeepCopy() *Headers { - if in == nil { - return nil - } - out := new(Headers) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IstioService) DeepCopyInto(out *IstioService) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioService. -func (in *IstioService) DeepCopy() *IstioService { - if in == nil { - return nil - } - out := new(IstioService) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Match) DeepCopyInto(out *Match) { - *out = *in - out.Request = in.Request - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Match. -func (in *Match) DeepCopy() *Match { - if in == nil { - return nil - } - out := new(Match) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MatchRequest) DeepCopyInto(out *MatchRequest) { - *out = *in - out.Headers = in.Headers - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchRequest. -func (in *MatchRequest) DeepCopy() *MatchRequest { - if in == nil { - return nil - } - out := new(MatchRequest) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MatchString) DeepCopyInto(out *MatchString) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchString. -func (in *MatchString) DeepCopy() *MatchString { - if in == nil { - return nil - } - out := new(MatchString) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRule) DeepCopyInto(out *RouteRule) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRule. -func (in *RouteRule) DeepCopy() *RouteRule { - if in == nil { - return nil - } - out := new(RouteRule) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteRule) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRuleList) DeepCopyInto(out *RouteRuleList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]RouteRule, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRuleList. -func (in *RouteRuleList) DeepCopy() *RouteRuleList { - if in == nil { - return nil - } - out := new(RouteRuleList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteRuleList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } else { - return nil - } -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRuleSpec) DeepCopyInto(out *RouteRuleSpec) { - *out = *in - out.Destination = in.Destination - out.Match = in.Match - if in.Route != nil { - in, out := &in.Route, &out.Route - *out = make([]DestinationWeight, len(*in)) - copy(*out, *in) - } - if in.AppendHeaders != nil { - in, out := &in.AppendHeaders, &out.AppendHeaders - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRuleSpec. -func (in *RouteRuleSpec) DeepCopy() *RouteRuleSpec { - if in == nil { - return nil - } - out := new(RouteRuleSpec) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/apis/istio/v1alpha3/README.md b/pkg/apis/istio/v1alpha3/README.md new file mode 100644 index 000000000000..04dd13d7d136 --- /dev/null +++ b/pkg/apis/istio/v1alpha3/README.md @@ -0,0 +1,16 @@ +# What are these files? + +These are Go structs for Istio CRD. We translated them from proto files in +https://github.com/istio/api/tree/master/networking/v1alpha3 . + +# Why do we hand-translate from proto? i.e Why can't we vendor these? +Istio needs to run on many platforms and as a reason they represent their +objects internally as proto. On Kubernetes, their API take in JSON objects +and convert to proto before processing them. + +So they have nothing we can vendor, except for the Go files that are generated +by the proto compiler, which is not compatible with K8s API code-generator at +all. + +We may be able to donate our translation so they can maintain it themselves. +See https://github.com/istio/istio/issues/6084. diff --git a/pkg/apis/istio/v1alpha2/doc.go b/pkg/apis/istio/v1alpha3/doc.go similarity index 93% rename from pkg/apis/istio/v1alpha2/doc.go rename to pkg/apis/istio/v1alpha3/doc.go index 6c6961d47c2b..47ec83daedf2 100644 --- a/pkg/apis/istio/v1alpha2/doc.go +++ b/pkg/apis/istio/v1alpha3/doc.go @@ -19,5 +19,5 @@ limitations under the License. // of the same resource // +k8s:deepcopy-gen=package -// +groupName=config.istio.io -package v1alpha2 +// +groupName=networking.istio.io +package v1alpha3 diff --git a/pkg/apis/istio/v1alpha3/gateway_types.go b/pkg/apis/istio/v1alpha3/gateway_types.go new file mode 100644 index 000000000000..0822a5e3b4e2 --- /dev/null +++ b/pkg/apis/istio/v1alpha3/gateway_types.go @@ -0,0 +1,318 @@ +/* +Copyright 2018 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 v1alpha3 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Gateway describes a load balancer operating at the edge of the mesh +// receiving incoming or outgoing HTTP/TCP connections. The specification +// describes a set of ports that should be exposed, the type of protocol to +// use, SNI configuration for the load balancer, etc. +// +// For example, the following gateway spec sets up a proxy to act as a load +// balancer exposing port 80 and 9080 (http), 443 (https), and port 2379 +// (TCP) for ingress. The gateway will be applied to the proxy running on +// a pod with labels "app: my-gateway-controller". While Istio will configure the +// proxy to listen on these ports, it is the responsibility of the user to +// ensure that external traffic to these ports are allowed into the mesh. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: Gateway +// metadata: +// name: my-gateway +// spec: +// selector: +// app: my-gatweway-controller +// servers: +// - port: +// number: 80 +// name: http +// protocol: HTTP +// hosts: +// - uk.bookinfo.com +// - eu.bookinfo.com +// tls: +// httpsRedirect: true # sends 302 redirect for http requests +// - port: +// number: 443 +// name: https +// protocol: HTTPS +// hosts: +// - uk.bookinfo.com +// - eu.bookinfo.com +// tls: +// mode: SIMPLE #enables HTTPS on this port +// serverCertificate: /etc/certs/servercert.pem +// privateKey: /etc/certs/privatekey.pem +// - port: +// number: 9080 +// name: http-wildcard +// protocol: HTTP +// # no hosts implies wildcard match +// - port: +// number: 2379 #to expose internal service via external port 2379 +// name: mongo +// protocol: MONGO +// +// The gateway specification above describes the L4-L6 properties of a load +// balancer. A VirtualService can then be bound to a gateway to control +// the forwarding of traffic arriving at a particular host or gateway port. +// +// For example, the following VirtualService splits traffic for +// https://uk.bookinfo.com/reviews, https://eu.bookinfo.com/reviews, +// http://uk.bookinfo.com:9080/reviews, http://eu.bookinfo.com:9080/reviews +// into two versions (prod and qa) of an internal reviews service on port +// 9080. In addition, requests containing the cookie user: dev-123 will be +// sent to special port 7777 in the qa version. The same rule is also +// applicable inside the mesh for requests to the reviews.prod +// service. This rule is applicable across ports 443, 9080. Note that +// http://uk.bookinfo.com gets redirected to https://uk.bookinfo.com +// (i.e. 80 redirects to 443). +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: bookinfo-rule +// spec: +// hosts: +// - reviews.prod +// - uk.bookinfo.com +// - eu.bookinfo.com +// gateways: +// - my-gateway +// - mesh # applies to all the sidecars in the mesh +// http: +// - match: +// - headers: +// cookie: +// user: dev-123 +// route: +// - destination: +// port: +// number: 7777 +// name: reviews.qa +// - match: +// uri: +// prefix: /reviews/ +// route: +// - destination: +// port: +// number: 9080 # can be omitted if its the only port for reviews +// name: reviews.prod +// weight: 80 +// - destination: +// name: reviews.qa +// weight: 20 +// +// The following VirtualService forwards traffic arriving at (external) port +// 2379 from 172.17.16.0/24 subnet to internal Mongo server on port 5555. This +// rule is not applicable internally in the mesh as the gateway list omits +// the reserved name "mesh". +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: bookinfo-Mongo +// spec: +// hosts: +// - mongosvr #name of Mongo service +// gateways: +// - my-gateway +// tcp: +// - match: +// - port: +// number: 2379 +// sourceSubnet: "172.17.16.0/24" +// route: +// - destination: +// name: mongo.prod +// +type Gateway struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GatewaySpec `json:"spec"` +} + +type GatewaySpec struct { + // REQUIRED: A list of server specifications. + Servers []Server `json:"servers"` + + // One or more labels that indicate a specific set of pods/VMs + // on which this gateway configuration should be applied. + // If no selectors are provided, the gateway will be implemented by + // the default istio-ingress controller. + Selector map[string]string `json:"selector,omitempty"` +} + +// Server describes the properties of the proxy on a given load balancer port. +// For example, +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: Gateway +// metadata: +// name: my-ingress +// spec: +// selector: +// app: my-ingress-controller +// servers: +// - port: +// number: 80 +// name: http2 +// protocol: HTTP2 +// +// Another example +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: Gateway +// metadata: +// name: my-tcp-ingress +// spec: +// selector: +// app: my-tcp-ingress-controller +// servers: +// - port: +// number: 27018 +// name: mongo +// protocol: MONGO +// +// The following is an example of TLS configuration for port 443 +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: Gateway +// metadata: +// name: my-tls-ingress +// spec: +// selector: +// app: my-tls-ingress-controller +// servers: +// - port: +// number: 443 +// name: https +// protocol: HTTPS +// tls: +// mode: SIMPLE +// serverCertificate: /etc/certs/server.pem +// privateKey: /etc/certs/privatekey.pem +// +type Server struct { + // REQUIRED: The Port on which the proxy should listen for incoming + // connections + Port Port `json:"port"` + + // A list of hosts exposed by this gateway. While + // typically applicable to HTTP services, it can also be used for TCP + // services using TLS with SNI. Standard DNS wildcard prefix syntax + // is permitted. + // + // A VirtualService that is bound to a gateway must having a matching host + // in its default destination. Specifically one of the VirtualService + // destination hosts is a strict suffix of a gateway host or + // a gateway host is a suffix of one of the VirtualService hosts. + Hosts []string `json:"hosts,omitempty"` + + // Set of TLS related options that govern the server's behavior. Use + // these options to control if all http requests should be redirected to + // https, and the TLS modes to use. + TLS *TLSOptions `json:"tls,omitempty"` +} + +type TLSOptions struct { + // If set to true, the load balancer will send a 302 redirect for all + // http connections, asking the clients to use HTTPS. + HttpsRedirect bool `json:"httpsRedirect"` + + // Optional: Indicates whether connections to this port should be + // secured using TLS. The value of this field determines how TLS is + // enforced. + Mode TLSMode `json:"mode,omitempty"` + + // REQUIRED if mode is "SIMPLE" or "MUTUAL". The path to the file + // holding the server-side TLS certificate to use. + ServerCertificate string `json:"serverCertificate"` + + // REQUIRED if mode is "SIMPLE" or "MUTUAL". The path to the file + // holding the server's private key. + PrivateKey string `json:"privateKey"` + + // REQUIRED if mode is "MUTUAL". The path to a file containing + // certificate authority certificates to use in verifying a presented + // client side certificate. + CaCertificates string `json:"caCertificates"` + + // A list of alternate names to verify the subject identity in the + // certificate presented by the client. + SubjectAltNames []string `json:"subjectAltNames"` +} + +// TLS modes enforced by the proxy +type TLSMode string + +const ( + // If set to "PASSTHROUGH", the proxy will forward the connection + // to the upstream server selected based on the SNI string presented + // by the client. + TLSModePassThrough TLSMode = "PASSTHROUGH" + + // If set to "SIMPLE", the proxy will secure connections with + // standard TLS semantics. + TLSModeSimple TLSMode = "SIMPLE" + + // If set to "MUTUAL", the proxy will secure connections to the + // upstream using mutual TLS by presenting client certificates for + // authentication. + TLSModeMutual TLSMode = "MUTUAL" +) + +// Port describes the properties of a specific port of a service. +type Port struct { + // REQUIRED: A valid non-negative integer port number. + Number int `json:"number"` + + // REQUIRED: The protocol exposed on the port. + // MUST BE one of HTTP|HTTPS|GRPC|HTTP2|MONGO|TCP. + Protocol PortProtocol `json:"protocol"` + + // Label assigned to the port. + Name string `json:"name,omitempty"` +} + +type PortProtocol string + +const ( + ProtocolHTTP PortProtocol = "HTTP" + ProtocolHTTPS PortProtocol = "HTTPS" + ProtocolGRPC PortProtocol = "GRPC" + ProtocolHTTP2 PortProtocol = "HTTP2" + ProtocolMongo PortProtocol = "Mongo" + ProtocolTCP PortProtocol = "TCP" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// GatewayList is a list of Gateway resources +type GatewayList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []Gateway `json:"items"` +} diff --git a/pkg/apis/istio/v1alpha2/register.go b/pkg/apis/istio/v1alpha3/register.go similarity index 92% rename from pkg/apis/istio/v1alpha2/register.go rename to pkg/apis/istio/v1alpha3/register.go index eaf5dff0bf94..2a70cb2340d7 100644 --- a/pkg/apis/istio/v1alpha2/register.go +++ b/pkg/apis/istio/v1alpha3/register.go @@ -14,18 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha2 +package v1alpha3 import ( "github.com/knative/serving/pkg/apis/istio" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: istio.GroupName, Version: "v1alpha2"} +var SchemeGroupVersion = schema.GroupVersion{Group: istio.GroupName, Version: "v1alpha3"} // Kind takes an unqualified kind and returns back a Group qualified GroupKind func Kind(kind string) schema.GroupKind { @@ -45,8 +44,10 @@ var ( // Adds the list of known types to Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &RouteRule{}, - &RouteRuleList{}, + &VirtualService{}, + &Gateway{}, + &VirtualServiceList{}, + &GatewayList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/apis/istio/v1alpha3/virtualservice_types.go b/pkg/apis/istio/v1alpha3/virtualservice_types.go new file mode 100644 index 000000000000..416227033b90 --- /dev/null +++ b/pkg/apis/istio/v1alpha3/virtualservice_types.go @@ -0,0 +1,783 @@ +/* +Copyright 2018 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 v1alpha3 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// VirtualService +type VirtualService struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VirtualServiceSpec `json:"spec"` +} + +// A VirtualService defines a set of traffic routing rules to apply when a host is +// addressed. Each routing rule defines matching criteria for traffic of a specific +// protocol. If the traffic is matched, then it is sent to a named destination service +// (or subset/version of it) defined in the registry. +// +// The source of traffic can also be matched in a routing rule. This allows routing +// to be customized for specific client contexts. +// +// The following example routes all HTTP traffic by default to +// pods of the reviews service with label "version: v1". In addition, +// HTTP requests containing /wpcatalog/, /consumercatalog/ url prefixes will +// be rewritten to /newcatalog and sent to pods with label "version: v2". The +// rules will be applied at the gateway named "bookinfo" as well as at all +// the sidecars in the mesh (indicated by the reserved gateway name +// "mesh"). +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: reviews-route +// spec: +// hosts: +// - reviews +// gateways: # if omitted, defaults to "mesh" +// - bookinfo +// - mesh +// http: +// - match: +// - uri: +// prefix: "/wpcatalog" +// - uri: +// prefix: "/consumercatalog" +// rewrite: +// uri: "/newcatalog" +// route: +// - destination: +// host: reviews +// subset: v2 +// - route: +// - destination: +// host: reviews +// subset: v1 +// +// A subset/version of a route destination is identified with a reference +// to a named service subset which must be declared in a corresponding +// DestinationRule. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: DestinationRule +// metadata: +// name: reviews-destination +// spec: +// host: reviews +// subsets: +// - name: v1 +// labels: +// version: v1 +// - name: v2 +// labels: +// version: v2 +// +// A host name can be defined by only one VirtualService. A single +// VirtualService can be used to describe traffic properties for multiple +// HTTP and TCP ports. +type VirtualServiceSpec struct { + // REQUIRED. The destination address for traffic captured by this virtual + // service. Could be a DNS name with wildcard prefix or a CIDR + // prefix. Depending on the platform, short-names can also be used + // instead of a FQDN (i.e. has no dots in the name). In such a scenario, + // the FQDN of the host would be derived based on the underlying + // platform. + // + // For example on Kubernetes, when hosts contains a short name, Istio will + // interpret the short name based on the namespace of the rule. Thus, when a + // client namespace applies a rule in the "default" namespace containing a name + // "reviews, Istio will setup routes to the "reviews.default.svc.cluster.local" + // service. However, if a different name such as "reviews.sales.svc.cluster.local" + // is used, it would be treated as a FQDN during virtual host matching. + // In Consul, a plain service name would be resolved to the FQDN + // "reviews.service.consul". + // + // Note that the hosts field applies to both HTTP and TCP + // services. Service inside the mesh, i.e., those found in the service + // registry, must always be referred to using their alphanumeric + // names. IP addresses or CIDR prefixes are allowed only for services + // defined via the Gateway. + Hosts []string `json:"hosts"` + + // The names of gateways and sidecars that should apply these routes. A + // single VirtualService is used for sidecars inside the mesh as well + // as for one or more gateways. The selection condition imposed by this field + // can be overridden using the source field in the match conditions of HTTP/TCP + // routes. The reserved word "mesh" is used to imply all the sidecars in + // the mesh. When this field is omitted, the default gateway ("mesh") + // will be used, which would apply the rule to all sidecars in the + // mesh. If a list of gateway names is provided, the rules will apply + // only to the gateways. To apply the rules to both gateways and sidecars, + // specify "mesh" as one of the gateway names. + Gateways []string `json:"gateways,omitempty"` + + // An ordered list of route rules for HTTP traffic. + // The first rule matching an incoming request is used. + Http []HTTPRoute `json:"http,omitempty"` + + // An ordered list of route rules for TCP traffic. + // The first rule matching an incoming request is used. + Tcp []TCPRoute `json:"tcp,omitempty"` +} + +// Describes match conditions and actions for routing HTTP/1.1, HTTP2, and +// gRPC traffic. See VirtualService for usage examples. +type HTTPRoute struct { + // Match conditions to be satisfied for the rule to be + // activated. All conditions inside a single match block have AND + // semantics, while the list of match blocks have OR semantics. The rule + // is matched if any one of the match blocks succeed. + Match []HTTPMatchRequest `json:"match,omitempty"` + + // A http rule can either redirect or forward (default) traffic. The + // forwarding target can be one of several versions of a service (see + // glossary in beginning of document). Weights associated with the + // service version determine the proportion of traffic it receives. + Route []DestinationWeight `json:"route,omitempty"` + + // A http rule can either redirect or forward (default) traffic. If + // traffic passthrough option is specified in the rule, + // route/redirect will be ignored. The redirect primitive can be used to + // send a HTTP 302 redirect to a different URI or Authority. + Redirect *HTTPRedirect `json:"redirect,omitempty"` + + // Rewrite HTTP URIs and Authority headers. Rewrite cannot be used with + // Redirect primitive. Rewrite will be performed before forwarding. + Rewrite *HTTPRewrite `json:"rewrite,omitempty"` + + // Indicates that a HTTP/1.1 client connection to this particular route + // should be allowed (and expected) to upgrade to a WebSocket connection. + // The default is false. Istio's reference sidecar implementation (Envoy) + // expects the first request to this route to contain the WebSocket + // upgrade headers. Otherwise, the request will be rejected. Note that + // Websocket allows secondary protocol negotiation which may then be + // subject to further routing rules based on the protocol selected. + WebsocketUpgrade bool `json:"websocketUpgrade,omitempty"` + + // Timeout for HTTP requests. + Timeout string `json:"timeout,omitempty"` + + // Retry policy for HTTP requests. + Retries *HTTPRetry `json:"retries,omitempty"` + + //Fault injection policy to apply on HTTP traffic. + Fault *HTTPFaultInjection `json:"fault,omitempty"` + + // Mirror HTTP traffic to a another destination in addition to forwarding + // the requests to the intended destination. Mirrored traffic is on a + // best effort basis where the sidecar/gateway will not wait for the + // mirrored cluster to respond before returning the response from the + // original destination. Statistics will be generated for the mirrored + // destination. + Mirror *Destination `json:"mirror,omitempty"` + + // Additional HTTP headers to add before forwarding a request to the + // destination service. + AppendHeaders map[string]string `json:"appendHeaders,omitempty"` + + // Http headers to remove before returning the response to the caller + RemoveResponseHeaders map[string]string `json:"removeResponseHeaders,omitempty"` +} + +// HttpMatchRequest specifies a set of criterion to be met in order for the +// rule to be applied to the HTTP request. For example, the following +// restricts the rule to match only requests where the URL path +// starts with /ratings/v2/ and the request contains a "cookie" with value +// "user=jason". +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: ratings-route +// spec: +// hosts: +// - ratings +// http: +// - match: +// - headers: +// cookie: +// regex: "^(.*?;)?(user=jason)(;.*)?" +// uri: +// prefix: "/ratings/v2/" +// route: +// - destination: +// host: ratings +// +// HTTPMatchRequest CANNOT be empty. +type HTTPMatchRequest struct { + // URI to match + // values are case-sensitive and formatted as follows: + // + // - `exact: "value"` for exact string match + // + // - `prefix: "value"` for prefix-based match + // + // - `regex: "value"` for ECMAscript style regex-based match + // + Uri *StringMatch `json:"uri,omitempty"` + + // URI Scheme + // values are case-sensitive and formatted as follows: + // + // - `exact: "value"` for exact string match + // + // - `prefix: "value"` for prefix-based match + // + // - `regex: "value"` for ECMAscript style regex-based match + // + Scheme *StringMatch `json:"scheme,omitempty"` + + // HTTP Method + // values are case-sensitive and formatted as follows: + // + // - `exact: "value"` for exact string match + // + // - `prefix: "value"` for prefix-based match + // + // - `regex: "value"` for ECMAscript style regex-based match + // + Method *StringMatch `json:"method,omitempty"` + + // HTTP Authority + // values are case-sensitive and formatted as follows: + // + // - `exact: "value"` for exact string match + // + // - `prefix: "value"` for prefix-based match + // + // - `regex: "value"` for ECMAscript style regex-based match + // + Authority *StringMatch `json:"authority,omitempty"` + + // The header keys must be lowercase and use hyphen as the separator, + // e.g. _x-request-id_. + // + // Header values are case-sensitive and formatted as follows: + // + // - `exact: "value"` for exact string match + // + // - `prefix: "value"` for prefix-based match + // + // - `regex: "value"` for ECMAscript style regex-based match + // + // **Note:** The keys `uri`, `scheme`, `method`, and `authority` will be ignored. + Headers map[string]StringMatch `json:"headers,omitempty"` +} + +// Describes how to match a given string in HTTP headers. Match is +// case-sensitive. +type StringMatch struct { + // Specified exactly one of the fields below. + + // exact string match + Exact string `json:"exact,omitempty"` + + // prefix-based match + Prefix string `json:"prefix,omitempty"` + + // ECMAscript style regex-based match + Regex string `json:"regex,omitempty"` +} + +type DestinationWeight struct { + // REQUIRED. Destination uniquely identifies the instances of a service + // to which the request/connection should be forwarded to. + Destination Destination `json:"destination"` + + // REQUIRED. The proportion of traffic to be forwarded to the service + // version. (0-100). Sum of weights across destinations SHOULD BE == 100. + // If there is only destination in a rule, the weight value is assumed to + // be 100. + Weight int `json:"weight"` +} + +// Destination indicates the network addressable service to which the +// request/connection will be sent after processing a routing rule. The +// destination.name should unambiguously refer to a service in the service +// registry. It can be a short name or a fully qualified domain name from +// the service registry, a resolvable DNS name, an IP address or a service +// name from the service registry and a subset name. The order of inference +// is as follows: +// +// 1. Service registry lookup. The entire name is looked up in the service +// registry. If the lookup succeeds, the search terminates. The requests +// will be routed to any instance of the service in the mesh. When the +// service name consists of a single word, the FQDN will be constructed in +// a platform specific manner. For example, in Kubernetes, the namespace +// associated with the routing rule will be used to identify the service as +// .. However, if the service name contains +// multiple words separated by a dot (e.g., reviews.prod), the name in its +// entirety would be looked up in the service registry. +// +// 2. Runtime DNS lookup by the proxy. If step 1 fails, and the name is not +// an IP address, it will be considered as a DNS name that is not in the +// service registry (e.g., wikipedia.org). The sidecar/gateway will resolve +// the DNS and load balance requests appropriately. See Envoy's strict_dns +// for details. +// +// The following example routes all traffic by default to pods of the +// reviews service with label "version: v1" (i.e., subset v1), and some +// to subset v2, in a kubernetes environment. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: reviews-route +// spec: +// hosts: +// - reviews # namespace is same as the client/caller's namespace +// http: +// - match: +// - uri: +// prefix: "/wpcatalog" +// - uri: +// prefix: "/consumercatalog" +// rewrite: +// uri: "/newcatalog" +// route: +// - destination: +// host: reviews +// subset: v2 +// - route: +// - destination: +// host: reviews +// subset: v1 +// +// And the associated DestinationRule +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: DestinationRule +// metadata: +// name: reviews-destination +// spec: +// host: reviews +// subsets: +// - name: v1 +// labels: +// version: v1 +// - name: v2 +// labels: +// version: v2 +// +// The following VirtualService sets a timeout of 5s for all calls to +// productpage.prod service. Notice that there are no subsets defined in +// this rule. Istio will fetch all instances of productpage.prod service +// from the service registry and populate the sidecar's load balancing +// pool. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: my-productpage-rule +// spec: +// hosts: +// - productpage.prod # in kubernetes, this applies only to prod namespace +// http: +// - timeout: 5s +// route: +// - destination: +// host: productpage.prod +// +// The following sets a timeout of 5s for all calls to the external +// service wikipedia.org, as there is no internal service of that name. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: my-wiki-rule +// spec: +// hosts: +// - wikipedia.org +// http: +// - timeout: 5s +// route: +// - destination: +// host: wikipedia.org +// +type Destination struct { + // REQUIRED. The name of a service from the service registry. Service + // names are looked up from the platform's service registry (e.g., + // Kubernetes services, Consul services, etc.) and from the hosts + // declared by [ServiceEntry](#ServiceEntry). Traffic forwarded to + // destinations that are not found in either of the two, will be dropped. + // + // *Note for Kubernetes users*: When short names are used (e.g. "reviews" + // instead of "reviews.default.svc.cluster.local"), Istio will interpret + // the short name based on the namespace of the rule, not the service. A + // rule in the "default" namespace containing a host "reviews will be + // interpreted as "reviews.default.svc.cluster.local", irrespective of + // the actual namespace associated with the reviews service. _To avoid + // potential misconfigurations, it is recommended to always use fully + // qualified domain names over short names._ + Host string `json:"host"` + + // The name of a subset within the service. Applicable only to services + // within the mesh. The subset must be defined in a corresponding + // DestinationRule. + Subset string `json:"subset,omitempty"` + + // Specifies the port on the host that is being addressed. If a service + // exposes only a single port it is not required to explicitly select the + // port. + Port PortSelector `json:"port,omitempty"` +} + +// PortSelector specifies the number of a port to be used for +// matching or selection for final routing. +type PortSelector struct { + // Choose one of the fields below. + + // Valid port number + Number uint32 `json:"number,omitempty"` + + // Valid port name + Name string `json:"name,omitempty"` +} + +// Describes match conditions and actions for routing TCP traffic. The +// following routing rule forwards traffic arriving at port 27017 for +// mongo.prod.svc.cluster.local from 172.17.16.* subnet to another Mongo +// server on port 5555. +// +// ```yaml +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: bookinfo-Mongo +// spec: +// hosts: +// - mongo.prod.svc.cluster.local +// tcp: +// - match: +// - port: 27017 +// sourceSubnet: "172.17.16.0/24" +// route: +// - destination: +// host: mongo.backup.svc.cluster.local +// port: +// number: 5555 +// ``` +type TCPRoute struct { + // Match conditions to be satisfied for the rule to be + // activated. All conditions inside a single match block have AND + // semantics, while the list of match blocks have OR semantics. The rule + // is matched if any one of the match blocks succeed. + Match []L4MatchAttributes `json:"match"` + + // The destination to which the connection should be forwarded to. + // Currently, only one destination is allowed for TCP services. When TCP + // weighted routing support is introduced in Envoy, multiple destinations + // with weights can be specified. + Route DestinationWeight `json:"route"` +} + +// L4 connection match attributes. Note that L4 connection matching support +// is incomplete. +type L4MatchAttributes struct { + // IPv4 or IPv6 ip address of destination with optional subnet. E.g., + // a.b.c.d/xx form or just a.b.c.d. This is only valid when the + // destination service has several IPs and the application explicitly + // specifies a particular IP. + DestinationSubnet string `json:"destinationSubnet,omitempty"` + + // Specifies the port on the host that is being addressed. Many services + // only expose a single port or label ports with the protocols they support, + // in these cases it is not required to explicitly select the port. + Port int `json:"port,omitempty"` + + // IPv4 or IPv6 ip address of source with optional subnet. E.g., a.b.c.d/xx + // form or just a.b.c.d + SourceSubnet string `json:"sourceSubnet,omitempty"` + + // One or more labels that constrain the applicability of a rule to + // workloads with the given labels. If the VirtualService has a list of + // gateways specified at the top, it should include the reserved gateway + // `mesh` in order for this field to be applicable. + SourceLabel map[string]string `json:"sourceLabel,omitempty"` + + // Names of gateways where the rule should be applied to. Gateway names + // at the top of the VirtualService (if any) are overridden. The gateway match is + // independent of sourceLabels. + Gateways []string `json:"gateways,omitempty"` +} + +// HTTPRedirect can be used to send a 302 redirect response to the caller, +// where the Authority/Host and the URI in the response can be swapped with +// the specified values. For example, the following rule redirects +// requests for /v1/getProductRatings API on the ratings service to +// /v1/bookRatings provided by the bookratings service. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: ratings-route +// spec: +// hosts: +// - ratings +// http: +// - match: +// - uri: +// exact: /v1/getProductRatings +// redirect: +// uri: /v1/bookRatings +// authority: bookratings.default.svc.cluster.local +// ... +// +type HTTPRedirect struct { + // On a redirect, overwrite the Path portion of the URL with this + // value. Note that the entire path will be replaced, irrespective of the + // request URI being matched as an exact path or prefix. + Uri string `json:"uri,omitempty"` + + // On a redirect, overwrite the Authority/Host portion of the URL with + // this value. + Authority string `json:"authority,omitempty"` +} + +// HTTPRewrite can be used to rewrite specific parts of a HTTP request +// before forwarding the request to the destination. Rewrite primitive can +// be used only with the DestinationWeights. The following example +// demonstrates how to rewrite the URL prefix for api call (/ratings) to +// ratings service before making the actual API call. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: ratings-route +// spec: +// hosts: +// - ratings +// http: +// - match: +// - uri: +// prefix: /ratings +// rewrite: +// uri: /v1/bookRatings +// route: +// - destination: +// host: ratings +// subset: v1 +// +type HTTPRewrite struct { + // rewrite the path (or the prefix) portion of the URI with this + // value. If the original URI was matched based on prefix, the value + // provided in this field will replace the corresponding matched prefix. + Uri string `json:"uri,omitempty"` + + // rewrite the Authority/Host header with this value. + Authority string `json:"authority,omitempty"` +} + +// Describes the retry policy to use when a HTTP request fails. For +// example, the following rule sets the maximum number of retries to 3 when +// calling ratings:v1 service, with a 2s timeout per retry attempt. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: ratings-route +// spec: +// hosts: +// - ratings +// http: +// - route: +// - destination: +// host: ratings +// subset: v1 +// retries: +// attempts: 3 +// perTryTimeout: 2s +// +type HTTPRetry struct { + // REQUIRED. Number of retries for a given request. The interval + // between retries will be determined automatically (25ms+). Actual + // number of retries attempted depends on the httpReqTimeout. + Attempts int `json:"attempts"` + + // Timeout per retry attempt for a given request. format: 1h/1m/1s/1ms. MUST BE >=1ms. + PerTryTimeout string `json:"perTryTimeout"` +} + +// Describes the Cross-Origin Resource Sharing (CORS) policy, for a given +// service. Refer to +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS +// for further details about cross origin resource sharing. For example, +// the following rule restricts cross origin requests to those originating +// from example.com domain using HTTP POST/GET, and sets the +// Access-Control-Allow-Credentials header to false. In addition, it only +// exposes X-Foo-bar header and sets an expiry period of 1 day. +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: ratings-route +// spec: +// hosts: +// - ratings +// http: +// - route: +// - destination: +// host: ratings +// subset: v1 +// corsPolicy: +// allowOrigin: +// - example.com +// allowMethods: +// - POST +// - GET +// allowCredentials: false +// allowHeaders: +// - X-Foo-Bar +// maxAge: "1d" +// +type CorsPolicy struct { + // The list of origins that are allowed to perform CORS requests. The + // content will be serialized into the Access-Control-Allow-Origin + // header. Wildcard * will allow all origins. + AllowOrigin []string `json:"allowOrigin,omitempty"` + + // List of HTTP methods allowed to access the resource. The content will + // be serialized into the Access-Control-Allow-Methods header. + AllowMethods []string `json:"allowMethods,omitempty"` + + // List of HTTP headers that can be used when requesting the + // resource. Serialized to Access-Control-Allow-Methods header. + AllowHeaders []string `json:"allowHeaders,omitempty"` + + // A white list of HTTP headers that the browsers are allowed to + // access. Serialized into Access-Control-Expose-Headers header. + ExposeHeaders []string `json:"exposeHeaders,omitempty"` + + // Specifies how long the the results of a preflight request can be + // cached. Translates to the Access-Control-Max-Age header. + MaxAge string `json:"maxAge,omitempty"` + + // Indicates whether the caller is allowed to send the actual request + // (not the preflight) using credentials. Translates to + // Access-Control-Allow-Credentials header. + AllowCredentials bool `json:"allowCredentials,omitempty"` +} + +// HTTPFaultInjection can be used to specify one or more faults to inject +// while forwarding http requests to the destination specified in a route. +// Fault specification is part of a VirtualService rule. Faults include +// aborting the Http request from downstream service, and/or delaying +// proxying of requests. A fault rule MUST HAVE delay or abort or both. +// +// *Note:* Delay and abort faults are independent of one another, even if +// both are specified simultaneously. +type HTTPFaultInjection struct { + // Delay requests before forwarding, emulating various failures such as + // network issues, overloaded upstream service, etc. + Delay *InjectDelay `json:"delay,omitempty"` + + // Abort Http request attempts and return error codes back to downstream + // service, giving the impression that the upstream service is faulty. + Abort *InjectAbort `json:"abort,omitempty"` +} + +// Delay specification is used to inject latency into the request +// forwarding path. The following example will introduce a 5 second delay +// in 10% of the requests to the "v1" version of the "reviews" +// service from all pods with label env: prod +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: reviews-route +// spec: +// hosts: +// - reviews +// http: +// - match: +// - sourceLabels: +// env: prod +// route: +// - destination: +// host: reviews +// subset: v1 +// fault: +// delay: +// percent: 10 +// fixedDelay: 5s +// +// The _fixedDelay_ field is used to indicate the amount of delay in +// seconds. An optional _percent_ field, a value between 0 and 100, can +// be used to only delay a certain percentage of requests. If left +// unspecified, all request will be delayed. +type InjectDelay struct { + // Percentage of requests on which the delay will be injected (0-100). + Percent int `json:"percent,omitempty"` + + // REQUIRED. Add a fixed delay before forwarding the request. Format: + // 1h/1m/1s/1ms. MUST be >=1ms. + FixedDelay string `json:"fixedDelay"` + + // (-- Add a delay (based on an exponential function) before forwarding + // the request. mean delay needed to derive the exponential delay + // values --) + ExponentialDelay string `json:"exponentialDelay,omitempty"` +} + +// Abort specification is used to prematurely abort a request with a +// pre-specified error code. The following example will return an HTTP +// 400 error code for 10% of the requests to the "ratings" service "v1". +// +// apiVersion: networking.istio.io/v1alpha3 +// kind: VirtualService +// metadata: +// name: ratings-route +// spec: +// hosts: +// - ratings +// http: +// - route: +// - destination: +// host: ratings +// subset: v1 +// fault: +// abort: +// percent: 10 +// httpStatus: 400 +// +// The _httpStatus_ field is used to indicate the HTTP status code to +// return to the caller. The optional _percent_ field, a value between 0 +// and 100, is used to only abort a certain percentage of requests. If +// not specified, all requests are aborted. +type InjectAbort struct { + // Percentage of requests to be aborted with the error code provided (0-100). + Perecent int `json:"percent,omitempty"` + + // REQUIRED. HTTP status code to use to abort the Http request. + HttpStatus int `json:"httpStatus"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// VirtualServiceList is a list of VirtualService resources +type VirtualServiceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []VirtualService `json:"items"` +} diff --git a/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go new file mode 100644 index 000000000000..5857c8585ae0 --- /dev/null +++ b/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go @@ -0,0 +1,701 @@ +// +build !ignore_autogenerated + +/* +Copyright 2018 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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1alpha3 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CorsPolicy) DeepCopyInto(out *CorsPolicy) { + *out = *in + if in.AllowOrigin != nil { + in, out := &in.AllowOrigin, &out.AllowOrigin + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AllowMethods != nil { + in, out := &in.AllowMethods, &out.AllowMethods + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AllowHeaders != nil { + in, out := &in.AllowHeaders, &out.AllowHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExposeHeaders != nil { + in, out := &in.ExposeHeaders, &out.ExposeHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CorsPolicy. +func (in *CorsPolicy) DeepCopy() *CorsPolicy { + if in == nil { + return nil + } + out := new(CorsPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Destination) DeepCopyInto(out *Destination) { + *out = *in + out.Port = in.Port + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Destination. +func (in *Destination) DeepCopy() *Destination { + if in == nil { + return nil + } + out := new(Destination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DestinationWeight) DeepCopyInto(out *DestinationWeight) { + *out = *in + out.Destination = in.Destination + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestinationWeight. +func (in *DestinationWeight) DeepCopy() *DestinationWeight { + if in == nil { + return nil + } + out := new(DestinationWeight) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Gateway) DeepCopyInto(out *Gateway) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Gateway. +func (in *Gateway) DeepCopy() *Gateway { + if in == nil { + return nil + } + out := new(Gateway) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Gateway) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayList) DeepCopyInto(out *GatewayList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Gateway, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayList. +func (in *GatewayList) DeepCopy() *GatewayList { + if in == nil { + return nil + } + out := new(GatewayList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GatewayList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewaySpec) DeepCopyInto(out *GatewaySpec) { + *out = *in + if in.Servers != nil { + in, out := &in.Servers, &out.Servers + *out = make([]Server, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewaySpec. +func (in *GatewaySpec) DeepCopy() *GatewaySpec { + if in == nil { + return nil + } + out := new(GatewaySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPFaultInjection) DeepCopyInto(out *HTTPFaultInjection) { + *out = *in + if in.Delay != nil { + in, out := &in.Delay, &out.Delay + if *in == nil { + *out = nil + } else { + *out = new(InjectDelay) + **out = **in + } + } + if in.Abort != nil { + in, out := &in.Abort, &out.Abort + if *in == nil { + *out = nil + } else { + *out = new(InjectAbort) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPFaultInjection. +func (in *HTTPFaultInjection) DeepCopy() *HTTPFaultInjection { + if in == nil { + return nil + } + out := new(HTTPFaultInjection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPMatchRequest) DeepCopyInto(out *HTTPMatchRequest) { + *out = *in + if in.Uri != nil { + in, out := &in.Uri, &out.Uri + if *in == nil { + *out = nil + } else { + *out = new(StringMatch) + **out = **in + } + } + if in.Scheme != nil { + in, out := &in.Scheme, &out.Scheme + if *in == nil { + *out = nil + } else { + *out = new(StringMatch) + **out = **in + } + } + if in.Method != nil { + in, out := &in.Method, &out.Method + if *in == nil { + *out = nil + } else { + *out = new(StringMatch) + **out = **in + } + } + if in.Authority != nil { + in, out := &in.Authority, &out.Authority + if *in == nil { + *out = nil + } else { + *out = new(StringMatch) + **out = **in + } + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string]StringMatch, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPMatchRequest. +func (in *HTTPMatchRequest) DeepCopy() *HTTPMatchRequest { + if in == nil { + return nil + } + out := new(HTTPMatchRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRedirect) DeepCopyInto(out *HTTPRedirect) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRedirect. +func (in *HTTPRedirect) DeepCopy() *HTTPRedirect { + if in == nil { + return nil + } + out := new(HTTPRedirect) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRetry) DeepCopyInto(out *HTTPRetry) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRetry. +func (in *HTTPRetry) DeepCopy() *HTTPRetry { + if in == nil { + return nil + } + out := new(HTTPRetry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRewrite) DeepCopyInto(out *HTTPRewrite) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRewrite. +func (in *HTTPRewrite) DeepCopy() *HTTPRewrite { + if in == nil { + return nil + } + out := new(HTTPRewrite) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { + *out = *in + if in.Match != nil { + in, out := &in.Match, &out.Match + *out = make([]HTTPMatchRequest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Route != nil { + in, out := &in.Route, &out.Route + *out = make([]DestinationWeight, len(*in)) + copy(*out, *in) + } + if in.Redirect != nil { + in, out := &in.Redirect, &out.Redirect + if *in == nil { + *out = nil + } else { + *out = new(HTTPRedirect) + **out = **in + } + } + if in.Rewrite != nil { + in, out := &in.Rewrite, &out.Rewrite + if *in == nil { + *out = nil + } else { + *out = new(HTTPRewrite) + **out = **in + } + } + if in.Retries != nil { + in, out := &in.Retries, &out.Retries + if *in == nil { + *out = nil + } else { + *out = new(HTTPRetry) + **out = **in + } + } + if in.Fault != nil { + in, out := &in.Fault, &out.Fault + if *in == nil { + *out = nil + } else { + *out = new(HTTPFaultInjection) + (*in).DeepCopyInto(*out) + } + } + if in.Mirror != nil { + in, out := &in.Mirror, &out.Mirror + if *in == nil { + *out = nil + } else { + *out = new(Destination) + **out = **in + } + } + if in.AppendHeaders != nil { + in, out := &in.AppendHeaders, &out.AppendHeaders + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.RemoveResponseHeaders != nil { + in, out := &in.RemoveResponseHeaders, &out.RemoveResponseHeaders + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute. +func (in *HTTPRoute) DeepCopy() *HTTPRoute { + if in == nil { + return nil + } + out := new(HTTPRoute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InjectAbort) DeepCopyInto(out *InjectAbort) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InjectAbort. +func (in *InjectAbort) DeepCopy() *InjectAbort { + if in == nil { + return nil + } + out := new(InjectAbort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InjectDelay) DeepCopyInto(out *InjectDelay) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InjectDelay. +func (in *InjectDelay) DeepCopy() *InjectDelay { + if in == nil { + return nil + } + out := new(InjectDelay) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *L4MatchAttributes) DeepCopyInto(out *L4MatchAttributes) { + *out = *in + if in.SourceLabel != nil { + in, out := &in.SourceLabel, &out.SourceLabel + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Gateways != nil { + in, out := &in.Gateways, &out.Gateways + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new L4MatchAttributes. +func (in *L4MatchAttributes) DeepCopy() *L4MatchAttributes { + if in == nil { + return nil + } + out := new(L4MatchAttributes) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Port) DeepCopyInto(out *Port) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Port. +func (in *Port) DeepCopy() *Port { + if in == nil { + return nil + } + out := new(Port) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PortSelector) DeepCopyInto(out *PortSelector) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortSelector. +func (in *PortSelector) DeepCopy() *PortSelector { + if in == nil { + return nil + } + out := new(PortSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Server) DeepCopyInto(out *Server) { + *out = *in + out.Port = in.Port + if in.Hosts != nil { + in, out := &in.Hosts, &out.Hosts + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + if *in == nil { + *out = nil + } else { + *out = new(TLSOptions) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server. +func (in *Server) DeepCopy() *Server { + if in == nil { + return nil + } + out := new(Server) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StringMatch) DeepCopyInto(out *StringMatch) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringMatch. +func (in *StringMatch) DeepCopy() *StringMatch { + if in == nil { + return nil + } + out := new(StringMatch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPRoute) DeepCopyInto(out *TCPRoute) { + *out = *in + if in.Match != nil { + in, out := &in.Match, &out.Match + *out = make([]L4MatchAttributes, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.Route = in.Route + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRoute. +func (in *TCPRoute) DeepCopy() *TCPRoute { + if in == nil { + return nil + } + out := new(TCPRoute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOptions) DeepCopyInto(out *TLSOptions) { + *out = *in + if in.SubjectAltNames != nil { + in, out := &in.SubjectAltNames, &out.SubjectAltNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptions. +func (in *TLSOptions) DeepCopy() *TLSOptions { + if in == nil { + return nil + } + out := new(TLSOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualService) DeepCopyInto(out *VirtualService) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualService. +func (in *VirtualService) DeepCopy() *VirtualService { + if in == nil { + return nil + } + out := new(VirtualService) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualService) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualServiceList) DeepCopyInto(out *VirtualServiceList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VirtualService, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualServiceList. +func (in *VirtualServiceList) DeepCopy() *VirtualServiceList { + if in == nil { + return nil + } + out := new(VirtualServiceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualServiceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualServiceSpec) DeepCopyInto(out *VirtualServiceSpec) { + *out = *in + if in.Hosts != nil { + in, out := &in.Hosts, &out.Hosts + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Gateways != nil { + in, out := &in.Gateways, &out.Gateways + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Http != nil { + in, out := &in.Http, &out.Http + *out = make([]HTTPRoute, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Tcp != nil { + in, out := &in.Tcp, &out.Tcp + *out = make([]TCPRoute, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualServiceSpec. +func (in *VirtualServiceSpec) DeepCopy() *VirtualServiceSpec { + if in == nil { + return nil + } + out := new(VirtualServiceSpec) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/serving/v1alpha1/revision_types.go b/pkg/apis/serving/v1alpha1/revision_types.go index 51ec79a693e4..2857204332c5 100644 --- a/pkg/apis/serving/v1alpha1/revision_types.go +++ b/pkg/apis/serving/v1alpha1/revision_types.go @@ -229,6 +229,18 @@ func (rs *RevisionStatus) IsReady() bool { return false } +func (rs *RevisionStatus) IsActivationRequired() bool { + if c := rs.GetCondition(RevisionConditionReady); c != nil { + return (c.Reason == "Inactive" && c.Status == corev1.ConditionFalse) || + (c.Reason == "Updating" && c.Status == corev1.ConditionUnknown) + } + return false +} + +func (rs *RevisionStatus) IsRoutable() bool { + return rs.IsReady() || rs.IsActivationRequired() +} + func (rs *RevisionStatus) GetCondition(t RevisionConditionType) *RevisionCondition { for _, cond := range rs.Conditions { if cond.Type == t { diff --git a/pkg/apis/serving/v1alpha1/revision_types_test.go b/pkg/apis/serving/v1alpha1/revision_types_test.go index 33af218947e6..1f60f1bc583e 100644 --- a/pkg/apis/serving/v1alpha1/revision_types_test.go +++ b/pkg/apis/serving/v1alpha1/revision_types_test.go @@ -34,6 +34,138 @@ func TestGeneration(t *testing.T) { } +func TestIsActivationRequired(t *testing.T) { + cases := []struct { + name string + status RevisionStatus + isActivationRequired bool + }{{ + name: "empty status should not be inactive", + status: RevisionStatus{}, + isActivationRequired: false, + }, { + name: "Ready status should not be inactive", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionTrue, + }}, + }, + isActivationRequired: false, + }, { + name: "Inactive status should be inactive", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionFalse, + Reason: "Inactive", + }}, + }, + isActivationRequired: true, + }, { + name: "Updating status should be inactive", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionUnknown, + Reason: "Updating", + }}, + }, + isActivationRequired: true, + }, { + name: "NotReady status without reason should not be inactive", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionFalse, + }}, + }, + isActivationRequired: false, + }, { + name: "Ready/Unknown status without reason should not be inactive", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionUnknown, + }}, + }, + isActivationRequired: false, + }} + + for _, tc := range cases { + if e, a := tc.isActivationRequired, tc.status.IsActivationRequired(); e != a { + t.Errorf("%q expected: %v got: %v", tc.name, e, a) + } + } +} + +func TestIsRoutable(t *testing.T) { + cases := []struct { + name string + status RevisionStatus + isRoutable bool + }{{ + name: "empty status should not be routable", + status: RevisionStatus{}, + isRoutable: false, + }, { + name: "Ready status should be routable", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionTrue, + }}, + }, + isRoutable: true, + }, { + name: "Inactive status should be routable", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionFalse, + Reason: "Inactive", + }}, + }, + isRoutable: true, + }, { + name: "Updating status should be routable", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionUnknown, + Reason: "Updating", + }}, + }, + isRoutable: true, + }, { + name: "NotReady status without reason should not be routable", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionFalse, + }}, + }, + isRoutable: false, + }, { + name: "Ready/Unknown status without reason should not be routable", + status: RevisionStatus{ + Conditions: []RevisionCondition{{ + Type: RevisionConditionReady, + Status: corev1.ConditionUnknown, + }}, + }, + isRoutable: false, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got, want := tc.isRoutable, tc.status.IsRoutable(); got != want { + t.Errorf("IsRoutable() = %v want: %v", got, want) + } + }) + } +} + func TestIsReady(t *testing.T) { cases := []struct { name string @@ -461,7 +593,7 @@ func TestTypicalFlowWithSuspendResume(t *testing.T) { } // From an Inactive state, start to activate the revision. - want := "Activating" + want := "Updating" r.Status.MarkDeploying(want) r.Status.MarkDeploying(want) if got := checkConditionOngoingRevision(r.Status, RevisionConditionResourcesAvailable, t); got == nil || got.Reason != want { diff --git a/pkg/apis/serving/v1alpha1/route_types.go b/pkg/apis/serving/v1alpha1/route_types.go index f15b4a524d34..eafcf64b0718 100644 --- a/pkg/apis/serving/v1alpha1/route_types.go +++ b/pkg/apis/serving/v1alpha1/route_types.go @@ -113,9 +113,7 @@ const ( // RouteConditionReady is set when the service is configured // and has available backends ready to receive traffic. RouteConditionReady RouteConditionType = "Ready" - // RouteConditionIngressReady is set when the route's underlying ingress - // resource has been set up. - RouteConditionIngressReady RouteConditionType = "IngressReady" + // RouteConditionAllTrafficAssigned is set to False when the // service is not configured properly or has no available // backends ready to receive traffic. @@ -223,7 +221,6 @@ func (rs *RouteStatus) RemoveCondition(t RouteConditionType) { func (rs *RouteStatus) InitializeConditions() { for _, cond := range []RouteConditionType{ RouteConditionAllTrafficAssigned, - RouteConditionIngressReady, RouteConditionReady, } { if rc := rs.GetCondition(cond); rc == nil { @@ -257,18 +254,9 @@ func (rs *RouteStatus) MarkTrafficNotAssigned(kind, name string) { } } -func (rs *RouteStatus) MarkIngressReady() { - rs.setCondition(&RouteCondition{ - Type: RouteConditionIngressReady, - Status: corev1.ConditionTrue, - }) - rs.checkAndMarkReady() -} - func (rs *RouteStatus) checkAndMarkReady() { for _, cond := range []RouteConditionType{ RouteConditionAllTrafficAssigned, - RouteConditionIngressReady, } { ata := rs.GetCondition(cond) if ata == nil || ata.Status != corev1.ConditionTrue { diff --git a/pkg/apis/serving/v1alpha1/route_types_test.go b/pkg/apis/serving/v1alpha1/route_types_test.go index eb803b5f0f58..22e388bcfbfe 100644 --- a/pkg/apis/serving/v1alpha1/route_types_test.go +++ b/pkg/apis/serving/v1alpha1/route_types_test.go @@ -43,7 +43,7 @@ func TestRouteIsReady(t *testing.T) { name: "Different condition type should not be ready", status: RouteStatus{ Conditions: []RouteCondition{{ - Type: RouteConditionIngressReady, + Type: RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, }}, }, @@ -87,7 +87,7 @@ func TestRouteIsReady(t *testing.T) { name: "Multiple conditions with ready status should be ready", status: RouteStatus{ Conditions: []RouteCondition{{ - Type: RouteConditionIngressReady, + Type: RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, }, { Type: RouteConditionReady, @@ -99,7 +99,7 @@ func TestRouteIsReady(t *testing.T) { name: "Multiple conditions with ready status false should not be ready", status: RouteStatus{ Conditions: []RouteCondition{{ - Type: RouteConditionIngressReady, + Type: RouteConditionAllTrafficAssigned, Status: corev1.ConditionTrue, }, { Type: RouteConditionReady, @@ -172,44 +172,18 @@ func TestRouteConditions(t *testing.T) { } } -func TestTypicalFlowIngressFirst(t *testing.T) { +func TestTypicalRouteFlow(t *testing.T) { r := &Route{} r.Status.InitializeConditions() - checkConditionOngoingRoute(r.Status, RouteConditionIngressReady, t) - checkConditionOngoingRoute(r.Status, RouteConditionAllTrafficAssigned, t) - checkConditionOngoingRoute(r.Status, RouteConditionReady, t) - - r.Status.MarkIngressReady() - checkConditionSucceededRoute(r.Status, RouteConditionIngressReady, t) checkConditionOngoingRoute(r.Status, RouteConditionAllTrafficAssigned, t) checkConditionOngoingRoute(r.Status, RouteConditionReady, t) r.Status.MarkTrafficAssigned() - checkConditionSucceededRoute(r.Status, RouteConditionIngressReady, t) checkConditionSucceededRoute(r.Status, RouteConditionAllTrafficAssigned, t) checkConditionSucceededRoute(r.Status, RouteConditionReady, t) // Verify that this doesn't reset our conditions. r.Status.InitializeConditions() - checkConditionSucceededRoute(r.Status, RouteConditionIngressReady, t) - checkConditionSucceededRoute(r.Status, RouteConditionAllTrafficAssigned, t) - checkConditionSucceededRoute(r.Status, RouteConditionReady, t) -} - -func TestTypicalFlowIngressLast(t *testing.T) { - r := &Route{} - r.Status.InitializeConditions() - checkConditionOngoingRoute(r.Status, RouteConditionIngressReady, t) - checkConditionOngoingRoute(r.Status, RouteConditionAllTrafficAssigned, t) - checkConditionOngoingRoute(r.Status, RouteConditionReady, t) - - r.Status.MarkTrafficAssigned() - checkConditionOngoingRoute(r.Status, RouteConditionIngressReady, t) - checkConditionSucceededRoute(r.Status, RouteConditionAllTrafficAssigned, t) - checkConditionOngoingRoute(r.Status, RouteConditionReady, t) - - r.Status.MarkIngressReady() - checkConditionSucceededRoute(r.Status, RouteConditionIngressReady, t) checkConditionSucceededRoute(r.Status, RouteConditionAllTrafficAssigned, t) checkConditionSucceededRoute(r.Status, RouteConditionReady, t) } @@ -217,17 +191,10 @@ func TestTypicalFlowIngressLast(t *testing.T) { func TestTrafficNotAssignedFlow(t *testing.T) { r := &Route{} r.Status.InitializeConditions() - checkConditionOngoingRoute(r.Status, RouteConditionIngressReady, t) checkConditionOngoingRoute(r.Status, RouteConditionAllTrafficAssigned, t) checkConditionOngoingRoute(r.Status, RouteConditionReady, t) r.Status.MarkTrafficNotAssigned("Revision", "does-not-exist") - checkConditionOngoingRoute(r.Status, RouteConditionIngressReady, t) - checkConditionFailedRoute(r.Status, RouteConditionAllTrafficAssigned, t) - checkConditionFailedRoute(r.Status, RouteConditionReady, t) - - r.Status.MarkIngressReady() - checkConditionSucceededRoute(r.Status, RouteConditionIngressReady, t) checkConditionFailedRoute(r.Status, RouteConditionAllTrafficAssigned, t) checkConditionFailedRoute(r.Status, RouteConditionReady, t) } diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 61fb78bd74f5..2041ea8b12e0 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -17,7 +17,7 @@ package versioned import ( glog "github.com/golang/glog" - configv1alpha2 "github.com/knative/serving/pkg/client/clientset/versioned/typed/istio/v1alpha2" + networkingv1alpha3 "github.com/knative/serving/pkg/client/clientset/versioned/typed/istio/v1alpha3" servingv1alpha1 "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" @@ -26,9 +26,9 @@ import ( type Interface interface { Discovery() discovery.DiscoveryInterface - ConfigV1alpha2() configv1alpha2.ConfigV1alpha2Interface + NetworkingV1alpha3() networkingv1alpha3.NetworkingV1alpha3Interface // Deprecated: please explicitly pick a version if possible. - Config() configv1alpha2.ConfigV1alpha2Interface + Networking() networkingv1alpha3.NetworkingV1alpha3Interface ServingV1alpha1() servingv1alpha1.ServingV1alpha1Interface // Deprecated: please explicitly pick a version if possible. Serving() servingv1alpha1.ServingV1alpha1Interface @@ -38,19 +38,19 @@ type Interface interface { // version included in a Clientset. type Clientset struct { *discovery.DiscoveryClient - configV1alpha2 *configv1alpha2.ConfigV1alpha2Client - servingV1alpha1 *servingv1alpha1.ServingV1alpha1Client + networkingV1alpha3 *networkingv1alpha3.NetworkingV1alpha3Client + servingV1alpha1 *servingv1alpha1.ServingV1alpha1Client } -// ConfigV1alpha2 retrieves the ConfigV1alpha2Client -func (c *Clientset) ConfigV1alpha2() configv1alpha2.ConfigV1alpha2Interface { - return c.configV1alpha2 +// NetworkingV1alpha3 retrieves the NetworkingV1alpha3Client +func (c *Clientset) NetworkingV1alpha3() networkingv1alpha3.NetworkingV1alpha3Interface { + return c.networkingV1alpha3 } -// Deprecated: Config retrieves the default version of ConfigClient. +// Deprecated: Networking retrieves the default version of NetworkingClient. // Please explicitly pick a version. -func (c *Clientset) Config() configv1alpha2.ConfigV1alpha2Interface { - return c.configV1alpha2 +func (c *Clientset) Networking() networkingv1alpha3.NetworkingV1alpha3Interface { + return c.networkingV1alpha3 } // ServingV1alpha1 retrieves the ServingV1alpha1Client @@ -80,7 +80,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { } var cs Clientset var err error - cs.configV1alpha2, err = configv1alpha2.NewForConfig(&configShallowCopy) + cs.networkingV1alpha3, err = networkingv1alpha3.NewForConfig(&configShallowCopy) if err != nil { return nil, err } @@ -101,7 +101,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { // panics if there is an error in the config. func NewForConfigOrDie(c *rest.Config) *Clientset { var cs Clientset - cs.configV1alpha2 = configv1alpha2.NewForConfigOrDie(c) + cs.networkingV1alpha3 = networkingv1alpha3.NewForConfigOrDie(c) cs.servingV1alpha1 = servingv1alpha1.NewForConfigOrDie(c) cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) @@ -111,7 +111,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { // New creates a new Clientset for the given RESTClient. func New(c rest.Interface) *Clientset { var cs Clientset - cs.configV1alpha2 = configv1alpha2.New(c) + cs.networkingV1alpha3 = networkingv1alpha3.New(c) cs.servingV1alpha1 = servingv1alpha1.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index ab3e9ad969f1..27cc52736220 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -17,8 +17,8 @@ package fake import ( clientset "github.com/knative/serving/pkg/client/clientset/versioned" - configv1alpha2 "github.com/knative/serving/pkg/client/clientset/versioned/typed/istio/v1alpha2" - fakeconfigv1alpha2 "github.com/knative/serving/pkg/client/clientset/versioned/typed/istio/v1alpha2/fake" + networkingv1alpha3 "github.com/knative/serving/pkg/client/clientset/versioned/typed/istio/v1alpha3" + fakenetworkingv1alpha3 "github.com/knative/serving/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake" servingv1alpha1 "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" fakeservingv1alpha1 "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake" "k8s.io/apimachinery/pkg/runtime" @@ -69,14 +69,14 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface { var _ clientset.Interface = &Clientset{} -// ConfigV1alpha2 retrieves the ConfigV1alpha2Client -func (c *Clientset) ConfigV1alpha2() configv1alpha2.ConfigV1alpha2Interface { - return &fakeconfigv1alpha2.FakeConfigV1alpha2{Fake: &c.Fake} +// NetworkingV1alpha3 retrieves the NetworkingV1alpha3Client +func (c *Clientset) NetworkingV1alpha3() networkingv1alpha3.NetworkingV1alpha3Interface { + return &fakenetworkingv1alpha3.FakeNetworkingV1alpha3{Fake: &c.Fake} } -// Config retrieves the ConfigV1alpha2Client -func (c *Clientset) Config() configv1alpha2.ConfigV1alpha2Interface { - return &fakeconfigv1alpha2.FakeConfigV1alpha2{Fake: &c.Fake} +// Networking retrieves the NetworkingV1alpha3Client +func (c *Clientset) Networking() networkingv1alpha3.NetworkingV1alpha3Interface { + return &fakenetworkingv1alpha3.FakeNetworkingV1alpha3{Fake: &c.Fake} } // ServingV1alpha1 retrieves the ServingV1alpha1Client diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index 8c4e004e9db6..023640b42225 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -16,7 +16,7 @@ limitations under the License. package fake import ( - configv1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" + networkingv1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -48,6 +48,6 @@ func init() { // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types // correctly. func AddToScheme(scheme *runtime.Scheme) { - configv1alpha2.AddToScheme(scheme) + networkingv1alpha3.AddToScheme(scheme) servingv1alpha1.AddToScheme(scheme) } diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index 4e4a8caf367e..f9745bd97661 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -16,7 +16,7 @@ limitations under the License. package scheme import ( - configv1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" + networkingv1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -48,6 +48,6 @@ func init() { // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types // correctly. func AddToScheme(scheme *runtime.Scheme) { - configv1alpha2.AddToScheme(scheme) + networkingv1alpha3.AddToScheme(scheme) servingv1alpha1.AddToScheme(scheme) } diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha2/fake/fake_routerule.go b/pkg/client/clientset/versioned/typed/istio/v1alpha2/fake/fake_routerule.go deleted file mode 100644 index e5afef81880a..000000000000 --- a/pkg/client/clientset/versioned/typed/istio/v1alpha2/fake/fake_routerule.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -Copyright 2018 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 fake - -import ( - v1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" -) - -// FakeRouteRules implements RouteRuleInterface -type FakeRouteRules struct { - Fake *FakeConfigV1alpha2 - ns string -} - -var routerulesResource = schema.GroupVersionResource{Group: "config.istio.io", Version: "v1alpha2", Resource: "routerules"} - -var routerulesKind = schema.GroupVersionKind{Group: "config.istio.io", Version: "v1alpha2", Kind: "RouteRule"} - -// Get takes name of the routeRule, and returns the corresponding routeRule object, and an error if there is any. -func (c *FakeRouteRules) Get(name string, options v1.GetOptions) (result *v1alpha2.RouteRule, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(routerulesResource, c.ns, name), &v1alpha2.RouteRule{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha2.RouteRule), err -} - -// List takes label and field selectors, and returns the list of RouteRules that match those selectors. -func (c *FakeRouteRules) List(opts v1.ListOptions) (result *v1alpha2.RouteRuleList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(routerulesResource, routerulesKind, c.ns, opts), &v1alpha2.RouteRuleList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1alpha2.RouteRuleList{} - for _, item := range obj.(*v1alpha2.RouteRuleList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested routeRules. -func (c *FakeRouteRules) Watch(opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(routerulesResource, c.ns, opts)) - -} - -// Create takes the representation of a routeRule and creates it. Returns the server's representation of the routeRule, and an error, if there is any. -func (c *FakeRouteRules) Create(routeRule *v1alpha2.RouteRule) (result *v1alpha2.RouteRule, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(routerulesResource, c.ns, routeRule), &v1alpha2.RouteRule{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha2.RouteRule), err -} - -// Update takes the representation of a routeRule and updates it. Returns the server's representation of the routeRule, and an error, if there is any. -func (c *FakeRouteRules) Update(routeRule *v1alpha2.RouteRule) (result *v1alpha2.RouteRule, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(routerulesResource, c.ns, routeRule), &v1alpha2.RouteRule{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha2.RouteRule), err -} - -// Delete takes name of the routeRule and deletes it. Returns an error if one occurs. -func (c *FakeRouteRules) Delete(name string, options *v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteAction(routerulesResource, c.ns, name), &v1alpha2.RouteRule{}) - - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeRouteRules) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(routerulesResource, c.ns, listOptions) - - _, err := c.Fake.Invokes(action, &v1alpha2.RouteRuleList{}) - return err -} - -// Patch applies the patch and returns the patched routeRule. -func (c *FakeRouteRules) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.RouteRule, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(routerulesResource, c.ns, name, data, subresources...), &v1alpha2.RouteRule{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha2.RouteRule), err -} diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha2/routerule.go b/pkg/client/clientset/versioned/typed/istio/v1alpha2/routerule.go deleted file mode 100644 index 8b05853d64b7..000000000000 --- a/pkg/client/clientset/versioned/typed/istio/v1alpha2/routerule.go +++ /dev/null @@ -1,154 +0,0 @@ -/* -Copyright 2018 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 v1alpha2 - -import ( - v1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" - scheme "github.com/knative/serving/pkg/client/clientset/versioned/scheme" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" -) - -// RouteRulesGetter has a method to return a RouteRuleInterface. -// A group's client should implement this interface. -type RouteRulesGetter interface { - RouteRules(namespace string) RouteRuleInterface -} - -// RouteRuleInterface has methods to work with RouteRule resources. -type RouteRuleInterface interface { - Create(*v1alpha2.RouteRule) (*v1alpha2.RouteRule, error) - Update(*v1alpha2.RouteRule) (*v1alpha2.RouteRule, error) - Delete(name string, options *v1.DeleteOptions) error - DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error - Get(name string, options v1.GetOptions) (*v1alpha2.RouteRule, error) - List(opts v1.ListOptions) (*v1alpha2.RouteRuleList, error) - Watch(opts v1.ListOptions) (watch.Interface, error) - Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.RouteRule, err error) - RouteRuleExpansion -} - -// routeRules implements RouteRuleInterface -type routeRules struct { - client rest.Interface - ns string -} - -// newRouteRules returns a RouteRules -func newRouteRules(c *ConfigV1alpha2Client, namespace string) *routeRules { - return &routeRules{ - client: c.RESTClient(), - ns: namespace, - } -} - -// Get takes name of the routeRule, and returns the corresponding routeRule object, and an error if there is any. -func (c *routeRules) Get(name string, options v1.GetOptions) (result *v1alpha2.RouteRule, err error) { - result = &v1alpha2.RouteRule{} - err = c.client.Get(). - Namespace(c.ns). - Resource("routerules"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of RouteRules that match those selectors. -func (c *routeRules) List(opts v1.ListOptions) (result *v1alpha2.RouteRuleList, err error) { - result = &v1alpha2.RouteRuleList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("routerules"). - VersionedParams(&opts, scheme.ParameterCodec). - Do(). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested routeRules. -func (c *routeRules) Watch(opts v1.ListOptions) (watch.Interface, error) { - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("routerules"). - VersionedParams(&opts, scheme.ParameterCodec). - Watch() -} - -// Create takes the representation of a routeRule and creates it. Returns the server's representation of the routeRule, and an error, if there is any. -func (c *routeRules) Create(routeRule *v1alpha2.RouteRule) (result *v1alpha2.RouteRule, err error) { - result = &v1alpha2.RouteRule{} - err = c.client.Post(). - Namespace(c.ns). - Resource("routerules"). - Body(routeRule). - Do(). - Into(result) - return -} - -// Update takes the representation of a routeRule and updates it. Returns the server's representation of the routeRule, and an error, if there is any. -func (c *routeRules) Update(routeRule *v1alpha2.RouteRule) (result *v1alpha2.RouteRule, err error) { - result = &v1alpha2.RouteRule{} - err = c.client.Put(). - Namespace(c.ns). - Resource("routerules"). - Name(routeRule.Name). - Body(routeRule). - Do(). - Into(result) - return -} - -// Delete takes name of the routeRule and deletes it. Returns an error if one occurs. -func (c *routeRules) Delete(name string, options *v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("routerules"). - Name(name). - Body(options). - Do(). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *routeRules) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("routerules"). - VersionedParams(&listOptions, scheme.ParameterCodec). - Body(options). - Do(). - Error() -} - -// Patch applies the patch and returns the patched routeRule. -func (c *routeRules) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.RouteRule, err error) { - result = &v1alpha2.RouteRule{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("routerules"). - SubResource(subresources...). - Name(name). - Body(data). - Do(). - Into(result) - return -} diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha2/doc.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/doc.go similarity index 97% rename from pkg/client/clientset/versioned/typed/istio/v1alpha2/doc.go rename to pkg/client/clientset/versioned/typed/istio/v1alpha3/doc.go index ef0e5bffca0a..d1909a47d812 100644 --- a/pkg/client/clientset/versioned/typed/istio/v1alpha2/doc.go +++ b/pkg/client/clientset/versioned/typed/istio/v1alpha3/doc.go @@ -14,4 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ // This package has the automatically generated typed clients. -package v1alpha2 +package v1alpha3 diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha2/fake/doc.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/doc.go similarity index 100% rename from pkg/client/clientset/versioned/typed/istio/v1alpha2/fake/doc.go rename to pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/doc.go diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_gateway.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_gateway.go new file mode 100644 index 000000000000..8c940fedccba --- /dev/null +++ b/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_gateway.go @@ -0,0 +1,125 @@ +/* +Copyright 2018 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 fake + +import ( + v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeGateways implements GatewayInterface +type FakeGateways struct { + Fake *FakeNetworkingV1alpha3 + ns string +} + +var gatewaysResource = schema.GroupVersionResource{Group: "networking.istio.io", Version: "v1alpha3", Resource: "gateways"} + +var gatewaysKind = schema.GroupVersionKind{Group: "networking.istio.io", Version: "v1alpha3", Kind: "Gateway"} + +// Get takes name of the gateway, and returns the corresponding gateway object, and an error if there is any. +func (c *FakeGateways) Get(name string, options v1.GetOptions) (result *v1alpha3.Gateway, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(gatewaysResource, c.ns, name), &v1alpha3.Gateway{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Gateway), err +} + +// List takes label and field selectors, and returns the list of Gateways that match those selectors. +func (c *FakeGateways) List(opts v1.ListOptions) (result *v1alpha3.GatewayList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(gatewaysResource, gatewaysKind, c.ns, opts), &v1alpha3.GatewayList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha3.GatewayList{} + for _, item := range obj.(*v1alpha3.GatewayList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested gateways. +func (c *FakeGateways) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(gatewaysResource, c.ns, opts)) + +} + +// Create takes the representation of a gateway and creates it. Returns the server's representation of the gateway, and an error, if there is any. +func (c *FakeGateways) Create(gateway *v1alpha3.Gateway) (result *v1alpha3.Gateway, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(gatewaysResource, c.ns, gateway), &v1alpha3.Gateway{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Gateway), err +} + +// Update takes the representation of a gateway and updates it. Returns the server's representation of the gateway, and an error, if there is any. +func (c *FakeGateways) Update(gateway *v1alpha3.Gateway) (result *v1alpha3.Gateway, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(gatewaysResource, c.ns, gateway), &v1alpha3.Gateway{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Gateway), err +} + +// Delete takes name of the gateway and deletes it. Returns an error if one occurs. +func (c *FakeGateways) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(gatewaysResource, c.ns, name), &v1alpha3.Gateway{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeGateways) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(gatewaysResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha3.GatewayList{}) + return err +} + +// Patch applies the patch and returns the patched gateway. +func (c *FakeGateways) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha3.Gateway, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(gatewaysResource, c.ns, name, data, subresources...), &v1alpha3.Gateway{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.Gateway), err +} diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha2/fake/fake_istio_client.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_istio_client.go similarity index 64% rename from pkg/client/clientset/versioned/typed/istio/v1alpha2/fake/fake_istio_client.go rename to pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_istio_client.go index d10174ba55b0..ec4cc4e90bb2 100644 --- a/pkg/client/clientset/versioned/typed/istio/v1alpha2/fake/fake_istio_client.go +++ b/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_istio_client.go @@ -16,22 +16,26 @@ limitations under the License. package fake import ( - v1alpha2 "github.com/knative/serving/pkg/client/clientset/versioned/typed/istio/v1alpha2" + v1alpha3 "github.com/knative/serving/pkg/client/clientset/versioned/typed/istio/v1alpha3" rest "k8s.io/client-go/rest" testing "k8s.io/client-go/testing" ) -type FakeConfigV1alpha2 struct { +type FakeNetworkingV1alpha3 struct { *testing.Fake } -func (c *FakeConfigV1alpha2) RouteRules(namespace string) v1alpha2.RouteRuleInterface { - return &FakeRouteRules{c, namespace} +func (c *FakeNetworkingV1alpha3) Gateways(namespace string) v1alpha3.GatewayInterface { + return &FakeGateways{c, namespace} +} + +func (c *FakeNetworkingV1alpha3) VirtualServices(namespace string) v1alpha3.VirtualServiceInterface { + return &FakeVirtualServices{c, namespace} } // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. -func (c *FakeConfigV1alpha2) RESTClient() rest.Interface { +func (c *FakeNetworkingV1alpha3) RESTClient() rest.Interface { var ret *rest.RESTClient return ret } diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_virtualservice.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_virtualservice.go new file mode 100644 index 000000000000..152e2898ace6 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/istio/v1alpha3/fake/fake_virtualservice.go @@ -0,0 +1,125 @@ +/* +Copyright 2018 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 fake + +import ( + v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeVirtualServices implements VirtualServiceInterface +type FakeVirtualServices struct { + Fake *FakeNetworkingV1alpha3 + ns string +} + +var virtualservicesResource = schema.GroupVersionResource{Group: "networking.istio.io", Version: "v1alpha3", Resource: "virtualservices"} + +var virtualservicesKind = schema.GroupVersionKind{Group: "networking.istio.io", Version: "v1alpha3", Kind: "VirtualService"} + +// Get takes name of the virtualService, and returns the corresponding virtualService object, and an error if there is any. +func (c *FakeVirtualServices) Get(name string, options v1.GetOptions) (result *v1alpha3.VirtualService, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(virtualservicesResource, c.ns, name), &v1alpha3.VirtualService{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.VirtualService), err +} + +// List takes label and field selectors, and returns the list of VirtualServices that match those selectors. +func (c *FakeVirtualServices) List(opts v1.ListOptions) (result *v1alpha3.VirtualServiceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(virtualservicesResource, virtualservicesKind, c.ns, opts), &v1alpha3.VirtualServiceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha3.VirtualServiceList{} + for _, item := range obj.(*v1alpha3.VirtualServiceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested virtualServices. +func (c *FakeVirtualServices) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(virtualservicesResource, c.ns, opts)) + +} + +// Create takes the representation of a virtualService and creates it. Returns the server's representation of the virtualService, and an error, if there is any. +func (c *FakeVirtualServices) Create(virtualService *v1alpha3.VirtualService) (result *v1alpha3.VirtualService, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(virtualservicesResource, c.ns, virtualService), &v1alpha3.VirtualService{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.VirtualService), err +} + +// Update takes the representation of a virtualService and updates it. Returns the server's representation of the virtualService, and an error, if there is any. +func (c *FakeVirtualServices) Update(virtualService *v1alpha3.VirtualService) (result *v1alpha3.VirtualService, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(virtualservicesResource, c.ns, virtualService), &v1alpha3.VirtualService{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.VirtualService), err +} + +// Delete takes name of the virtualService and deletes it. Returns an error if one occurs. +func (c *FakeVirtualServices) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(virtualservicesResource, c.ns, name), &v1alpha3.VirtualService{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeVirtualServices) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(virtualservicesResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha3.VirtualServiceList{}) + return err +} + +// Patch applies the patch and returns the patched virtualService. +func (c *FakeVirtualServices) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha3.VirtualService, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(virtualservicesResource, c.ns, name, data, subresources...), &v1alpha3.VirtualService{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.VirtualService), err +} diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha3/gateway.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/gateway.go new file mode 100644 index 000000000000..70ccd02d1459 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/istio/v1alpha3/gateway.go @@ -0,0 +1,154 @@ +/* +Copyright 2018 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 v1alpha3 + +import ( + v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" + scheme "github.com/knative/serving/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// GatewaysGetter has a method to return a GatewayInterface. +// A group's client should implement this interface. +type GatewaysGetter interface { + Gateways(namespace string) GatewayInterface +} + +// GatewayInterface has methods to work with Gateway resources. +type GatewayInterface interface { + Create(*v1alpha3.Gateway) (*v1alpha3.Gateway, error) + Update(*v1alpha3.Gateway) (*v1alpha3.Gateway, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha3.Gateway, error) + List(opts v1.ListOptions) (*v1alpha3.GatewayList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha3.Gateway, err error) + GatewayExpansion +} + +// gateways implements GatewayInterface +type gateways struct { + client rest.Interface + ns string +} + +// newGateways returns a Gateways +func newGateways(c *NetworkingV1alpha3Client, namespace string) *gateways { + return &gateways{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the gateway, and returns the corresponding gateway object, and an error if there is any. +func (c *gateways) Get(name string, options v1.GetOptions) (result *v1alpha3.Gateway, err error) { + result = &v1alpha3.Gateway{} + err = c.client.Get(). + Namespace(c.ns). + Resource("gateways"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Gateways that match those selectors. +func (c *gateways) List(opts v1.ListOptions) (result *v1alpha3.GatewayList, err error) { + result = &v1alpha3.GatewayList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("gateways"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested gateways. +func (c *gateways) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("gateways"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a gateway and creates it. Returns the server's representation of the gateway, and an error, if there is any. +func (c *gateways) Create(gateway *v1alpha3.Gateway) (result *v1alpha3.Gateway, err error) { + result = &v1alpha3.Gateway{} + err = c.client.Post(). + Namespace(c.ns). + Resource("gateways"). + Body(gateway). + Do(). + Into(result) + return +} + +// Update takes the representation of a gateway and updates it. Returns the server's representation of the gateway, and an error, if there is any. +func (c *gateways) Update(gateway *v1alpha3.Gateway) (result *v1alpha3.Gateway, err error) { + result = &v1alpha3.Gateway{} + err = c.client.Put(). + Namespace(c.ns). + Resource("gateways"). + Name(gateway.Name). + Body(gateway). + Do(). + Into(result) + return +} + +// Delete takes name of the gateway and deletes it. Returns an error if one occurs. +func (c *gateways) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("gateways"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *gateways) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("gateways"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched gateway. +func (c *gateways) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha3.Gateway, err error) { + result = &v1alpha3.Gateway{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("gateways"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha2/generated_expansion.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/generated_expansion.go similarity index 85% rename from pkg/client/clientset/versioned/typed/istio/v1alpha2/generated_expansion.go rename to pkg/client/clientset/versioned/typed/istio/v1alpha3/generated_expansion.go index d76b49759c7d..c746b14b13ef 100644 --- a/pkg/client/clientset/versioned/typed/istio/v1alpha2/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/istio/v1alpha3/generated_expansion.go @@ -13,6 +13,8 @@ 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 v1alpha2 +package v1alpha3 -type RouteRuleExpansion interface{} +type GatewayExpansion interface{} + +type VirtualServiceExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha2/istio_client.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/istio_client.go similarity index 57% rename from pkg/client/clientset/versioned/typed/istio/v1alpha2/istio_client.go rename to pkg/client/clientset/versioned/typed/istio/v1alpha3/istio_client.go index 6929f6930db0..e2869ab0b292 100644 --- a/pkg/client/clientset/versioned/typed/istio/v1alpha2/istio_client.go +++ b/pkg/client/clientset/versioned/typed/istio/v1alpha3/istio_client.go @@ -13,31 +13,36 @@ 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 v1alpha2 +package v1alpha3 import ( - v1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" + v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" "github.com/knative/serving/pkg/client/clientset/versioned/scheme" serializer "k8s.io/apimachinery/pkg/runtime/serializer" rest "k8s.io/client-go/rest" ) -type ConfigV1alpha2Interface interface { +type NetworkingV1alpha3Interface interface { RESTClient() rest.Interface - RouteRulesGetter + GatewaysGetter + VirtualServicesGetter } -// ConfigV1alpha2Client is used to interact with features provided by the config.istio.io group. -type ConfigV1alpha2Client struct { +// NetworkingV1alpha3Client is used to interact with features provided by the networking.istio.io group. +type NetworkingV1alpha3Client struct { restClient rest.Interface } -func (c *ConfigV1alpha2Client) RouteRules(namespace string) RouteRuleInterface { - return newRouteRules(c, namespace) +func (c *NetworkingV1alpha3Client) Gateways(namespace string) GatewayInterface { + return newGateways(c, namespace) } -// NewForConfig creates a new ConfigV1alpha2Client for the given config. -func NewForConfig(c *rest.Config) (*ConfigV1alpha2Client, error) { +func (c *NetworkingV1alpha3Client) VirtualServices(namespace string) VirtualServiceInterface { + return newVirtualServices(c, namespace) +} + +// NewForConfig creates a new NetworkingV1alpha3Client for the given config. +func NewForConfig(c *rest.Config) (*NetworkingV1alpha3Client, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err @@ -46,12 +51,12 @@ func NewForConfig(c *rest.Config) (*ConfigV1alpha2Client, error) { if err != nil { return nil, err } - return &ConfigV1alpha2Client{client}, nil + return &NetworkingV1alpha3Client{client}, nil } -// NewForConfigOrDie creates a new ConfigV1alpha2Client for the given config and +// NewForConfigOrDie creates a new NetworkingV1alpha3Client for the given config and // panics if there is an error in the config. -func NewForConfigOrDie(c *rest.Config) *ConfigV1alpha2Client { +func NewForConfigOrDie(c *rest.Config) *NetworkingV1alpha3Client { client, err := NewForConfig(c) if err != nil { panic(err) @@ -59,13 +64,13 @@ func NewForConfigOrDie(c *rest.Config) *ConfigV1alpha2Client { return client } -// New creates a new ConfigV1alpha2Client for the given RESTClient. -func New(c rest.Interface) *ConfigV1alpha2Client { - return &ConfigV1alpha2Client{c} +// New creates a new NetworkingV1alpha3Client for the given RESTClient. +func New(c rest.Interface) *NetworkingV1alpha3Client { + return &NetworkingV1alpha3Client{c} } func setConfigDefaults(config *rest.Config) error { - gv := v1alpha2.SchemeGroupVersion + gv := v1alpha3.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} @@ -79,7 +84,7 @@ func setConfigDefaults(config *rest.Config) error { // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. -func (c *ConfigV1alpha2Client) RESTClient() rest.Interface { +func (c *NetworkingV1alpha3Client) RESTClient() rest.Interface { if c == nil { return nil } diff --git a/pkg/client/clientset/versioned/typed/istio/v1alpha3/virtualservice.go b/pkg/client/clientset/versioned/typed/istio/v1alpha3/virtualservice.go new file mode 100644 index 000000000000..feec05907109 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/istio/v1alpha3/virtualservice.go @@ -0,0 +1,154 @@ +/* +Copyright 2018 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 v1alpha3 + +import ( + v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" + scheme "github.com/knative/serving/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// VirtualServicesGetter has a method to return a VirtualServiceInterface. +// A group's client should implement this interface. +type VirtualServicesGetter interface { + VirtualServices(namespace string) VirtualServiceInterface +} + +// VirtualServiceInterface has methods to work with VirtualService resources. +type VirtualServiceInterface interface { + Create(*v1alpha3.VirtualService) (*v1alpha3.VirtualService, error) + Update(*v1alpha3.VirtualService) (*v1alpha3.VirtualService, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha3.VirtualService, error) + List(opts v1.ListOptions) (*v1alpha3.VirtualServiceList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha3.VirtualService, err error) + VirtualServiceExpansion +} + +// virtualServices implements VirtualServiceInterface +type virtualServices struct { + client rest.Interface + ns string +} + +// newVirtualServices returns a VirtualServices +func newVirtualServices(c *NetworkingV1alpha3Client, namespace string) *virtualServices { + return &virtualServices{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the virtualService, and returns the corresponding virtualService object, and an error if there is any. +func (c *virtualServices) Get(name string, options v1.GetOptions) (result *v1alpha3.VirtualService, err error) { + result = &v1alpha3.VirtualService{} + err = c.client.Get(). + Namespace(c.ns). + Resource("virtualservices"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of VirtualServices that match those selectors. +func (c *virtualServices) List(opts v1.ListOptions) (result *v1alpha3.VirtualServiceList, err error) { + result = &v1alpha3.VirtualServiceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("virtualservices"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested virtualServices. +func (c *virtualServices) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("virtualservices"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a virtualService and creates it. Returns the server's representation of the virtualService, and an error, if there is any. +func (c *virtualServices) Create(virtualService *v1alpha3.VirtualService) (result *v1alpha3.VirtualService, err error) { + result = &v1alpha3.VirtualService{} + err = c.client.Post(). + Namespace(c.ns). + Resource("virtualservices"). + Body(virtualService). + Do(). + Into(result) + return +} + +// Update takes the representation of a virtualService and updates it. Returns the server's representation of the virtualService, and an error, if there is any. +func (c *virtualServices) Update(virtualService *v1alpha3.VirtualService) (result *v1alpha3.VirtualService, err error) { + result = &v1alpha3.VirtualService{} + err = c.client.Put(). + Namespace(c.ns). + Resource("virtualservices"). + Name(virtualService.Name). + Body(virtualService). + Do(). + Into(result) + return +} + +// Delete takes name of the virtualService and deletes it. Returns an error if one occurs. +func (c *virtualServices) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("virtualservices"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *virtualServices) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("virtualservices"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched virtualService. +func (c *virtualServices) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha3.VirtualService, err error) { + result = &v1alpha3.VirtualService{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("virtualservices"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index a82de83e8347..f55263ed0a98 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -121,11 +121,11 @@ type SharedInformerFactory interface { ForResource(resource schema.GroupVersionResource) (GenericInformer, error) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool - Config() istio.Interface + Networking() istio.Interface Serving() serving.Interface } -func (f *sharedInformerFactory) Config() istio.Interface { +func (f *sharedInformerFactory) Networking() istio.Interface { return istio.New(f, f.namespace, f.tweakListOptions) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 51c77e586c61..c3bf874d7bb1 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -18,7 +18,7 @@ package externalversions import ( "fmt" - v1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" + v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" v1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" @@ -50,9 +50,11 @@ func (f *genericInformer) Lister() cache.GenericLister { // TODO extend this to unknown resources with a client pool func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { - // Group=config.istio.io, Version=v1alpha2 - case v1alpha2.SchemeGroupVersion.WithResource("routerules"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Config().V1alpha2().RouteRules().Informer()}, nil + // Group=networking.istio.io, Version=v1alpha3 + case v1alpha3.SchemeGroupVersion.WithResource("gateways"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1alpha3().Gateways().Informer()}, nil + case v1alpha3.SchemeGroupVersion.WithResource("virtualservices"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1alpha3().VirtualServices().Informer()}, nil // Group=serving.knative.dev, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithResource("configurations"): diff --git a/pkg/client/informers/externalversions/istio/interface.go b/pkg/client/informers/externalversions/istio/interface.go index 941174088606..342acb7e9be4 100644 --- a/pkg/client/informers/externalversions/istio/interface.go +++ b/pkg/client/informers/externalversions/istio/interface.go @@ -13,17 +13,17 @@ 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 config +package networking import ( internalinterfaces "github.com/knative/serving/pkg/client/informers/externalversions/internalinterfaces" - v1alpha2 "github.com/knative/serving/pkg/client/informers/externalversions/istio/v1alpha2" + v1alpha3 "github.com/knative/serving/pkg/client/informers/externalversions/istio/v1alpha3" ) // Interface provides access to each of this group's versions. type Interface interface { - // V1alpha2 provides access to shared informers for resources in V1alpha2. - V1alpha2() v1alpha2.Interface + // V1alpha3 provides access to shared informers for resources in V1alpha3. + V1alpha3() v1alpha3.Interface } type group struct { @@ -37,7 +37,7 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } -// V1alpha2 returns a new v1alpha2.Interface. -func (g *group) V1alpha2() v1alpha2.Interface { - return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) +// V1alpha3 returns a new v1alpha3.Interface. +func (g *group) V1alpha3() v1alpha3.Interface { + return v1alpha3.New(g.factory, g.namespace, g.tweakListOptions) } diff --git a/pkg/client/informers/externalversions/istio/v1alpha2/routerule.go b/pkg/client/informers/externalversions/istio/v1alpha3/gateway.go similarity index 53% rename from pkg/client/informers/externalversions/istio/v1alpha2/routerule.go rename to pkg/client/informers/externalversions/istio/v1alpha3/gateway.go index 99074ac2bce8..161c5ff276e5 100644 --- a/pkg/client/informers/externalversions/istio/v1alpha2/routerule.go +++ b/pkg/client/informers/externalversions/istio/v1alpha3/gateway.go @@ -13,74 +13,74 @@ 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 v1alpha2 +package v1alpha3 import ( time "time" - istio_v1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" + istio_v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" versioned "github.com/knative/serving/pkg/client/clientset/versioned" internalinterfaces "github.com/knative/serving/pkg/client/informers/externalversions/internalinterfaces" - v1alpha2 "github.com/knative/serving/pkg/client/listers/istio/v1alpha2" + v1alpha3 "github.com/knative/serving/pkg/client/listers/istio/v1alpha3" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" ) -// RouteRuleInformer provides access to a shared informer and lister for -// RouteRules. -type RouteRuleInformer interface { +// GatewayInformer provides access to a shared informer and lister for +// Gateways. +type GatewayInformer interface { Informer() cache.SharedIndexInformer - Lister() v1alpha2.RouteRuleLister + Lister() v1alpha3.GatewayLister } -type routeRuleInformer struct { +type gatewayInformer struct { factory internalinterfaces.SharedInformerFactory tweakListOptions internalinterfaces.TweakListOptionsFunc namespace string } -// NewRouteRuleInformer constructs a new informer for RouteRule type. +// NewGatewayInformer constructs a new informer for Gateway type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. -func NewRouteRuleInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredRouteRuleInformer(client, namespace, resyncPeriod, indexers, nil) +func NewGatewayInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredGatewayInformer(client, namespace, resyncPeriod, indexers, nil) } -// NewFilteredRouteRuleInformer constructs a new informer for RouteRule type. +// NewFilteredGatewayInformer constructs a new informer for Gateway type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. -func NewFilteredRouteRuleInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { +func NewFilteredGatewayInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.ConfigV1alpha2().RouteRules(namespace).List(options) + return client.NetworkingV1alpha3().Gateways(namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.ConfigV1alpha2().RouteRules(namespace).Watch(options) + return client.NetworkingV1alpha3().Gateways(namespace).Watch(options) }, }, - &istio_v1alpha2.RouteRule{}, + &istio_v1alpha3.Gateway{}, resyncPeriod, indexers, ) } -func (f *routeRuleInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredRouteRuleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +func (f *gatewayInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredGatewayInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } -func (f *routeRuleInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&istio_v1alpha2.RouteRule{}, f.defaultInformer) +func (f *gatewayInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&istio_v1alpha3.Gateway{}, f.defaultInformer) } -func (f *routeRuleInformer) Lister() v1alpha2.RouteRuleLister { - return v1alpha2.NewRouteRuleLister(f.Informer().GetIndexer()) +func (f *gatewayInformer) Lister() v1alpha3.GatewayLister { + return v1alpha3.NewGatewayLister(f.Informer().GetIndexer()) } diff --git a/pkg/client/informers/externalversions/istio/v1alpha2/interface.go b/pkg/client/informers/externalversions/istio/v1alpha3/interface.go similarity index 66% rename from pkg/client/informers/externalversions/istio/v1alpha2/interface.go rename to pkg/client/informers/externalversions/istio/v1alpha3/interface.go index 22560cdda77a..a6b9fb5690b9 100644 --- a/pkg/client/informers/externalversions/istio/v1alpha2/interface.go +++ b/pkg/client/informers/externalversions/istio/v1alpha3/interface.go @@ -13,7 +13,7 @@ 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 v1alpha2 +package v1alpha3 import ( internalinterfaces "github.com/knative/serving/pkg/client/informers/externalversions/internalinterfaces" @@ -21,8 +21,10 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { - // RouteRules returns a RouteRuleInformer. - RouteRules() RouteRuleInformer + // Gateways returns a GatewayInformer. + Gateways() GatewayInformer + // VirtualServices returns a VirtualServiceInformer. + VirtualServices() VirtualServiceInformer } type version struct { @@ -36,7 +38,12 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } -// RouteRules returns a RouteRuleInformer. -func (v *version) RouteRules() RouteRuleInformer { - return &routeRuleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +// Gateways returns a GatewayInformer. +func (v *version) Gateways() GatewayInformer { + return &gatewayInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// VirtualServices returns a VirtualServiceInformer. +func (v *version) VirtualServices() VirtualServiceInformer { + return &virtualServiceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } diff --git a/pkg/client/informers/externalversions/istio/v1alpha3/virtualservice.go b/pkg/client/informers/externalversions/istio/v1alpha3/virtualservice.go new file mode 100644 index 000000000000..541b8ac580dc --- /dev/null +++ b/pkg/client/informers/externalversions/istio/v1alpha3/virtualservice.go @@ -0,0 +1,86 @@ +/* +Copyright 2018 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 v1alpha3 + +import ( + time "time" + + istio_v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" + versioned "github.com/knative/serving/pkg/client/clientset/versioned" + internalinterfaces "github.com/knative/serving/pkg/client/informers/externalversions/internalinterfaces" + v1alpha3 "github.com/knative/serving/pkg/client/listers/istio/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// VirtualServiceInformer provides access to a shared informer and lister for +// VirtualServices. +type VirtualServiceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha3.VirtualServiceLister +} + +type virtualServiceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewVirtualServiceInformer constructs a new informer for VirtualService type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVirtualServiceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVirtualServiceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredVirtualServiceInformer constructs a new informer for VirtualService type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredVirtualServiceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NetworkingV1alpha3().VirtualServices(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NetworkingV1alpha3().VirtualServices(namespace).Watch(options) + }, + }, + &istio_v1alpha3.VirtualService{}, + resyncPeriod, + indexers, + ) +} + +func (f *virtualServiceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVirtualServiceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *virtualServiceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&istio_v1alpha3.VirtualService{}, f.defaultInformer) +} + +func (f *virtualServiceInformer) Lister() v1alpha3.VirtualServiceLister { + return v1alpha3.NewVirtualServiceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/listers/istio/v1alpha2/routerule.go b/pkg/client/listers/istio/v1alpha2/routerule.go deleted file mode 100644 index a08db59ff849..000000000000 --- a/pkg/client/listers/istio/v1alpha2/routerule.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 2018 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 v1alpha2 - -import ( - v1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" -) - -// RouteRuleLister helps list RouteRules. -type RouteRuleLister interface { - // List lists all RouteRules in the indexer. - List(selector labels.Selector) (ret []*v1alpha2.RouteRule, err error) - // RouteRules returns an object that can list and get RouteRules. - RouteRules(namespace string) RouteRuleNamespaceLister - RouteRuleListerExpansion -} - -// routeRuleLister implements the RouteRuleLister interface. -type routeRuleLister struct { - indexer cache.Indexer -} - -// NewRouteRuleLister returns a new RouteRuleLister. -func NewRouteRuleLister(indexer cache.Indexer) RouteRuleLister { - return &routeRuleLister{indexer: indexer} -} - -// List lists all RouteRules in the indexer. -func (s *routeRuleLister) List(selector labels.Selector) (ret []*v1alpha2.RouteRule, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha2.RouteRule)) - }) - return ret, err -} - -// RouteRules returns an object that can list and get RouteRules. -func (s *routeRuleLister) RouteRules(namespace string) RouteRuleNamespaceLister { - return routeRuleNamespaceLister{indexer: s.indexer, namespace: namespace} -} - -// RouteRuleNamespaceLister helps list and get RouteRules. -type RouteRuleNamespaceLister interface { - // List lists all RouteRules in the indexer for a given namespace. - List(selector labels.Selector) (ret []*v1alpha2.RouteRule, err error) - // Get retrieves the RouteRule from the indexer for a given namespace and name. - Get(name string) (*v1alpha2.RouteRule, error) - RouteRuleNamespaceListerExpansion -} - -// routeRuleNamespaceLister implements the RouteRuleNamespaceLister -// interface. -type routeRuleNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all RouteRules in the indexer for a given namespace. -func (s routeRuleNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.RouteRule, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha2.RouteRule)) - }) - return ret, err -} - -// Get retrieves the RouteRule from the indexer for a given namespace and name. -func (s routeRuleNamespaceLister) Get(name string) (*v1alpha2.RouteRule, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1alpha2.Resource("routerule"), name) - } - return obj.(*v1alpha2.RouteRule), nil -} diff --git a/pkg/client/listers/istio/v1alpha3/expansion_generated.go b/pkg/client/listers/istio/v1alpha3/expansion_generated.go new file mode 100644 index 000000000000..5c121800bc30 --- /dev/null +++ b/pkg/client/listers/istio/v1alpha3/expansion_generated.go @@ -0,0 +1,32 @@ +/* +Copyright 2018 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 v1alpha3 + +// GatewayListerExpansion allows custom methods to be added to +// GatewayLister. +type GatewayListerExpansion interface{} + +// GatewayNamespaceListerExpansion allows custom methods to be added to +// GatewayNamespaceLister. +type GatewayNamespaceListerExpansion interface{} + +// VirtualServiceListerExpansion allows custom methods to be added to +// VirtualServiceLister. +type VirtualServiceListerExpansion interface{} + +// VirtualServiceNamespaceListerExpansion allows custom methods to be added to +// VirtualServiceNamespaceLister. +type VirtualServiceNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/istio/v1alpha3/gateway.go b/pkg/client/listers/istio/v1alpha3/gateway.go new file mode 100644 index 000000000000..b93ad7cad67f --- /dev/null +++ b/pkg/client/listers/istio/v1alpha3/gateway.go @@ -0,0 +1,91 @@ +/* +Copyright 2018 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 v1alpha3 + +import ( + v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// GatewayLister helps list Gateways. +type GatewayLister interface { + // List lists all Gateways in the indexer. + List(selector labels.Selector) (ret []*v1alpha3.Gateway, err error) + // Gateways returns an object that can list and get Gateways. + Gateways(namespace string) GatewayNamespaceLister + GatewayListerExpansion +} + +// gatewayLister implements the GatewayLister interface. +type gatewayLister struct { + indexer cache.Indexer +} + +// NewGatewayLister returns a new GatewayLister. +func NewGatewayLister(indexer cache.Indexer) GatewayLister { + return &gatewayLister{indexer: indexer} +} + +// List lists all Gateways in the indexer. +func (s *gatewayLister) List(selector labels.Selector) (ret []*v1alpha3.Gateway, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha3.Gateway)) + }) + return ret, err +} + +// Gateways returns an object that can list and get Gateways. +func (s *gatewayLister) Gateways(namespace string) GatewayNamespaceLister { + return gatewayNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// GatewayNamespaceLister helps list and get Gateways. +type GatewayNamespaceLister interface { + // List lists all Gateways in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha3.Gateway, err error) + // Get retrieves the Gateway from the indexer for a given namespace and name. + Get(name string) (*v1alpha3.Gateway, error) + GatewayNamespaceListerExpansion +} + +// gatewayNamespaceLister implements the GatewayNamespaceLister +// interface. +type gatewayNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Gateways in the indexer for a given namespace. +func (s gatewayNamespaceLister) List(selector labels.Selector) (ret []*v1alpha3.Gateway, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha3.Gateway)) + }) + return ret, err +} + +// Get retrieves the Gateway from the indexer for a given namespace and name. +func (s gatewayNamespaceLister) Get(name string) (*v1alpha3.Gateway, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha3.Resource("gateway"), name) + } + return obj.(*v1alpha3.Gateway), nil +} diff --git a/pkg/client/listers/istio/v1alpha3/virtualservice.go b/pkg/client/listers/istio/v1alpha3/virtualservice.go new file mode 100644 index 000000000000..3c96cf4ae885 --- /dev/null +++ b/pkg/client/listers/istio/v1alpha3/virtualservice.go @@ -0,0 +1,91 @@ +/* +Copyright 2018 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 v1alpha3 + +import ( + v1alpha3 "github.com/knative/serving/pkg/apis/istio/v1alpha3" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// VirtualServiceLister helps list VirtualServices. +type VirtualServiceLister interface { + // List lists all VirtualServices in the indexer. + List(selector labels.Selector) (ret []*v1alpha3.VirtualService, err error) + // VirtualServices returns an object that can list and get VirtualServices. + VirtualServices(namespace string) VirtualServiceNamespaceLister + VirtualServiceListerExpansion +} + +// virtualServiceLister implements the VirtualServiceLister interface. +type virtualServiceLister struct { + indexer cache.Indexer +} + +// NewVirtualServiceLister returns a new VirtualServiceLister. +func NewVirtualServiceLister(indexer cache.Indexer) VirtualServiceLister { + return &virtualServiceLister{indexer: indexer} +} + +// List lists all VirtualServices in the indexer. +func (s *virtualServiceLister) List(selector labels.Selector) (ret []*v1alpha3.VirtualService, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha3.VirtualService)) + }) + return ret, err +} + +// VirtualServices returns an object that can list and get VirtualServices. +func (s *virtualServiceLister) VirtualServices(namespace string) VirtualServiceNamespaceLister { + return virtualServiceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// VirtualServiceNamespaceLister helps list and get VirtualServices. +type VirtualServiceNamespaceLister interface { + // List lists all VirtualServices in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha3.VirtualService, err error) + // Get retrieves the VirtualService from the indexer for a given namespace and name. + Get(name string) (*v1alpha3.VirtualService, error) + VirtualServiceNamespaceListerExpansion +} + +// virtualServiceNamespaceLister implements the VirtualServiceNamespaceLister +// interface. +type virtualServiceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all VirtualServices in the indexer for a given namespace. +func (s virtualServiceNamespaceLister) List(selector labels.Selector) (ret []*v1alpha3.VirtualService, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha3.VirtualService)) + }) + return ret, err +} + +// Get retrieves the VirtualService from the indexer for a given namespace and name. +func (s virtualServiceNamespaceLister) Get(name string) (*v1alpha3.VirtualService, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha3.Resource("virtualservice"), name) + } + return obj.(*v1alpha3.VirtualService), nil +} diff --git a/pkg/controller/names.go b/pkg/controller/names.go index 34b18c273ed3..d8dca2ec7a0a 100644 --- a/pkg/controller/names.go +++ b/pkg/controller/names.go @@ -17,9 +17,14 @@ limitations under the License. package controller import ( + "fmt" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" ) +func GetK8SServiceFullname(name string, namespace string) string { + return fmt.Sprintf("%s.%s.svc.cluster.local", name, namespace) +} func GetDomainConfigMapName() string { return "config-domain" } @@ -51,23 +56,28 @@ func GetRevisionVPAName(u *v1alpha1.Revision) string { return u.Name + "-vpa" } -func GetRouteRuleName(u *v1alpha1.Route, tt *v1alpha1.TrafficTarget) string { - if tt != nil { - return u.Name + "-" + tt.Name + "-istio" - } +func GetServingK8SServiceNameForRevision(u *v1alpha1.Revision) string { + return u.Name + "-service" +} + +func GetServingK8SServiceNameForRoute(u *v1alpha1.Route) string { + return u.Name + "-service" +} + +func GetVirtualServiceName(u *v1alpha1.Route) string { return u.Name + "-istio" } -func GetServingK8SIngressName(u *v1alpha1.Route) string { - return u.Name + "-ingress" +func GetServingK8SGatewayFullname() string { + return GetK8SServiceFullname("knative-shared-gateway", "knative-serving") } -func GetServingK8SServiceNameForRevision(u *v1alpha1.Revision) string { - return u.Name + "-service" +func GetServingK8SServiceNameForObj(name string) string { + return name + "-service" } -func GetServingK8SServiceName(u *v1alpha1.Route) string { - return u.Name + "-service" +func GetServingK8SServiceFullnameForRoute(u *v1alpha1.Route) string { + return GetK8SServiceFullname(GetServingK8SServiceNameForRoute(u), u.Namespace) } func GetServiceConfigurationName(u *v1alpha1.Service) string { diff --git a/pkg/controller/revision/service.go b/pkg/controller/revision/service.go index b9750d46b3bc..ba7274137c0a 100644 --- a/pkg/controller/revision/service.go +++ b/pkg/controller/revision/service.go @@ -28,7 +28,10 @@ import ( ) var httpServicePortName = "http" -var servicePort = 80 + +const ( + ServicePort int32 = 80 +) // MakeRevisionK8sService creates a Service that targets all pods with the same // serving.RevisionLabelKey label. Traffic is routed to queue-proxy port. @@ -44,7 +47,7 @@ func MakeRevisionK8sService(rev *v1alpha1.Revision) *corev1.Service { Spec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{{ Name: httpServicePortName, - Port: int32(servicePort), + Port: ServicePort, TargetPort: intstr.IntOrString{Type: intstr.String, StrVal: queue.RequestQueuePortName}, }}, Type: "NodePort", diff --git a/pkg/controller/route/cruds.go b/pkg/controller/route/cruds.go new file mode 100644 index 000000000000..d1e8bc77db5d --- /dev/null +++ b/pkg/controller/route/cruds.go @@ -0,0 +1,111 @@ +/* +Copyright 2018 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 + + https://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 route + +import ( + "context" + "reflect" + + "github.com/knative/serving/pkg/apis/istio/v1alpha3" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/knative/serving/pkg/controller/route/istio" + "github.com/knative/serving/pkg/logging" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +///////////////////////////////////////// +// Helper functions for CREATE/UPDATE operations of various resources. +///////////////////////////////////////// +func (c *Controller) reconcileVirtualService(ctx context.Context, route *v1alpha1.Route, vs *v1alpha3.VirtualService) error { + logger := logging.FromContext(ctx) + vsClient := c.ServingClientSet.NetworkingV1alpha3().VirtualServices(vs.Namespace) + existing, err := vsClient.Get(vs.Name, metav1.GetOptions{}) + if err == nil && !reflect.DeepEqual(existing.Spec, vs.Spec) { + existing.Spec = vs.Spec + _, err = vsClient.Update(existing) + logger.Error("Failed to update VirtualService", zap.Error(err)) + return err + } else if apierrs.IsNotFound(err) { + _, err = vsClient.Create(vs) + if err != nil { + logger.Error("Failed to create VirtualService", zap.Error(err)) + } else { + c.Recorder.Eventf(route, corev1.EventTypeNormal, "Created", "Created VirtualService %q", vs.Name) + } + return err + } + return err +} + +func (c *Controller) reconcilePlaceholderService(ctx context.Context, route *v1alpha1.Route) error { + logger := logging.FromContext(ctx) + service := istio.MakeRouteK8SService(route) + if _, err := c.KubeClientSet.CoreV1().Services(route.Namespace).Create(service); err != nil { + if apierrs.IsAlreadyExists(err) { + // Service already exist. + return nil + } + logger.Error("Failed to create service", zap.Error(err)) + c.Recorder.Eventf(route, corev1.EventTypeWarning, "CreationFailed", "Failed to create service %q: %v", service.Name, err) + return err + } + logger.Infof("Created service %s", service.Name) + c.Recorder.Eventf(route, corev1.EventTypeNormal, "Created", "Created service %q", service.Name) + return nil +} + +// updateStatusErr updates a Route's Status due to some erroneous conditions. +func (c *Controller) updateStatusErr(ctx context.Context, route *v1alpha1.Route, reason error) (*v1alpha1.Route, error) { + if r, err := c.updateStatus(ctx, route); err != nil { + // When we can't update, we want to return that instead of returning the reason of the bad status. + return r, err + } + // Pass through the reason why we updated the Status. + return route, reason +} + +func (c *Controller) updateStatus(ctx context.Context, route *v1alpha1.Route) (*v1alpha1.Route, error) { + logger := logging.FromContext(ctx) + + routeClient := c.ServingClientSet.ServingV1alpha1().Routes(route.Namespace) + existing, err := routeClient.Get(route.Name, metav1.GetOptions{}) + if err != nil { + logger.Warn("Failed to update route status", zap.Error(err)) + c.Recorder.Eventf(route, corev1.EventTypeWarning, "UpdateFailed", "Failed to get current status for route %q: %v", route.Name, err) + return nil, err + } + + // If there's nothing to update, just return. + if reflect.DeepEqual(existing.Status, route.Status) { + return existing, nil + } + + existing.Status = route.Status + // TODO: for CRD there's no updatestatus, so use normal update. + updated, err := routeClient.Update(existing) + if err != nil { + logger.Warn("Failed to update route status", zap.Error(err)) + c.Recorder.Eventf(route, corev1.EventTypeWarning, "UpdateFailed", "Failed to update status for route %q: %v", route.Name, err) + return nil, err + } + + c.Recorder.Eventf(route, corev1.EventTypeNormal, "Updated", "Updated status for route %q", route.Name) + return updated, nil +} diff --git a/pkg/controller/route/cruds_test.go b/pkg/controller/route/cruds_test.go new file mode 100644 index 000000000000..e7604cf7a91d --- /dev/null +++ b/pkg/controller/route/cruds_test.go @@ -0,0 +1,95 @@ +/* +Copyright 2018 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 route + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/knative/serving/pkg/apis/istio/v1alpha3" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/knative/serving/pkg/controller/route/istio" + "github.com/knative/serving/pkg/controller/route/traffic" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestReconcileVirtualService_Insert(t *testing.T) { + _, elaClient, c, _, _, _ := newTestController() + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{ + "route": "test-route", + }, + }, + Status: v1alpha1.RouteStatus{Domain: "foo.com"}, + } + vs := newTestVirtualService(r) + if err := c.reconcileVirtualService(testCtx, r, vs); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if created, err := elaClient.NetworkingV1alpha3().VirtualServices(vs.Namespace).Get(vs.Name, metav1.GetOptions{}); err != nil { + t.Errorf("Unexpected error: %v", err) + } else if diff := cmp.Diff(vs, created); diff != "" { + t.Errorf("Unexpected diff (-want +got): %v", diff) + } +} + +func TestReconcileVirtualService_Update(t *testing.T) { + _, elaClient, c, _, _, _ := newTestController() + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{ + "route": "test-route", + }, + }, + } + vs := newTestVirtualService(r) + if err := c.reconcileVirtualService(testCtx, r, vs); err != nil { + t.Errorf("Unexpected error: %v", err) + } + r.Status.Domain = "bar.com" + vs2 := newTestVirtualService(r) + if err := c.reconcileVirtualService(testCtx, r, vs2); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if updated, err := elaClient.NetworkingV1alpha3().VirtualServices(vs.Namespace).Get(vs.Name, metav1.GetOptions{}); err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + if diff := cmp.Diff(vs2, updated); diff != "" { + t.Errorf("Unexpected diff (-want +got): %v", diff) + } + if diff := cmp.Diff(vs, updated); diff == "" { + t.Error("Expected difference, but found none") + } + } +} + +func newTestVirtualService(r *v1alpha1.Route) *v1alpha3.VirtualService { + tc := &traffic.TrafficConfig{Targets: map[string][]traffic.RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + RevisionName: "revision", + Percent: 100, + }, + Active: true, + }}}} + return istio.MakeVirtualService(r, tc) +} diff --git a/pkg/controller/route/ingress.go b/pkg/controller/route/ingress.go deleted file mode 100644 index 74cc21e09c2d..000000000000 --- a/pkg/controller/route/ingress.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright 2018 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 route - -import ( - "fmt" - - "github.com/knative/serving/pkg/apis/serving/v1alpha1" - "github.com/knative/serving/pkg/controller" - - "k8s.io/api/extensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" -) - -// MakeRouteIngress creates an ingress rule, owned by the provided v1alpha1.Route. This ingress rule -// targets Istio by using the simple placeholder service name. All the routing actually happens in -// the route rules. -func MakeRouteIngress(route *v1alpha1.Route) *v1beta1.Ingress { - // We used to have a distinct service, but in the serving world, use the - // name for serviceID too. - - // Construct hostnames the ingress accepts traffic for. - hostRules := []string{ - route.Status.Domain, - fmt.Sprintf("*.%s", route.Status.Domain), - } - - path := v1beta1.HTTPIngressPath{ - Backend: v1beta1.IngressBackend{ - ServiceName: controller.GetServingK8SServiceName(route), - ServicePort: intstr.IntOrString{Type: intstr.String, StrVal: "http"}, - }, - } - - rules := []v1beta1.IngressRule{} - for _, hostRule := range hostRules { - rule := v1beta1.IngressRule{ - Host: hostRule, - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{path}, - }, - }, - } - rules = append(rules, rule) - } - - return &v1beta1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: controller.GetServingK8SIngressName(route), - Namespace: route.Namespace, - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "istio", - }, - OwnerReferences: []metav1.OwnerReference{ - *controller.NewRouteControllerRef(route), - }, - }, - Spec: v1beta1.IngressSpec{ - Rules: rules, - }, - } -} diff --git a/pkg/client/listers/istio/v1alpha2/expansion_generated.go b/pkg/controller/route/istio/doc.go similarity index 60% rename from pkg/client/listers/istio/v1alpha2/expansion_generated.go rename to pkg/controller/route/istio/doc.go index 1f5bc44ae4e1..0548e86c9e76 100644 --- a/pkg/client/listers/istio/v1alpha2/expansion_generated.go +++ b/pkg/controller/route/istio/doc.go @@ -5,7 +5,7 @@ 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 + https://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, @@ -13,12 +13,10 @@ 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 v1alpha2 -// RouteRuleListerExpansion allows custom methods to be added to -// RouteRuleLister. -type RouteRuleListerExpansion interface{} +/* +This package contains code that builds Istio CRDs to be use by the +route controller for configuring an Istio mesh for Routes. +*/ -// RouteRuleNamespaceListerExpansion allows custom methods to be added to -// RouteRuleNamespaceLister. -type RouteRuleNamespaceListerExpansion interface{} +package istio diff --git a/pkg/controller/route/service.go b/pkg/controller/route/istio/service.go similarity index 71% rename from pkg/controller/route/service.go rename to pkg/controller/route/istio/service.go index 8e67330383d3..3719422df114 100644 --- a/pkg/controller/route/service.go +++ b/pkg/controller/route/istio/service.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package route +package istio import ( "github.com/knative/serving/pkg/apis/serving/v1alpha1" @@ -24,29 +24,27 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -var httpServicePortName = "http" -var servicePort = 80 - -// MakeRouteK8SService creates a Service that targets nothing, owned by the provided -// v1alpha1.Route. This is now only a placeholder so that we can route the traffic to Istio and the -// balance with route rules exclusively to underlying k8s services that represent Revisions. +// MakeRouteK8SService creates a Service that targets nothing, owned by the provided v1alpha1.Route. The purpose of +// this service is to provide a FQDN for Istio routing. Since Istio does not provide IP address routing, a ClusterIP is +// useless here. As a result we assign ClusterIP: "None" for this Service to avoid redundant IP assignment. func MakeRouteK8SService(route *v1alpha1.Route) *corev1.Service { return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: controller.GetServingK8SServiceName(route), + Name: controller.GetServingK8SServiceNameForRoute(route), Namespace: route.Namespace, OwnerReferences: []metav1.OwnerReference{ + // This service is owned by the Route. *controller.NewRouteControllerRef(route), }, }, Spec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ { - Name: httpServicePortName, - Port: int32(servicePort), + Name: PortName, + Port: PortNumber, }, }, - Selector: map[string]string{}, + ClusterIP: "None", }, } } diff --git a/pkg/controller/route/istio/service_test.go b/pkg/controller/route/istio/service_test.go new file mode 100644 index 000000000000..dc078d2499fa --- /dev/null +++ b/pkg/controller/route/istio/service_test.go @@ -0,0 +1,74 @@ +/* +Copyright 2018 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 istio + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/knative/serving/pkg/controller" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMakeRouteK8SService_ValidSpec(t *testing.T) { + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{ + "route": "test-route", + }, + }, + } + expectedSpec := corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{ + Name: "http", + Port: 80, + }}, + ClusterIP: "None", + } + spec := MakeRouteK8SService(r).Spec + if diff := cmp.Diff(expectedSpec, spec); diff != "" { + t.Errorf("Unexpected ServiceSpec (-want +got): %v", diff) + } +} + +func TestMakeRouteK8SService_ValidMeta(t *testing.T) { + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{ + "route": "test-route", + }, + }, + } + expectedMeta := metav1.ObjectMeta{ + Name: "test-route-service", + Namespace: "test-ns", + OwnerReferences: []metav1.OwnerReference{ + // This service is owned by the Route. + *controller.NewRouteControllerRef(r), + }, + } + meta := MakeRouteK8SService(r).ObjectMeta + if diff := cmp.Diff(expectedMeta, meta); diff != "" { + t.Errorf("Unexpected Metadata (-want +got): %v", diff) + } +} diff --git a/pkg/controller/route/istio/virtual_service.go b/pkg/controller/route/istio/virtual_service.go new file mode 100644 index 000000000000..cb6ec0925b18 --- /dev/null +++ b/pkg/controller/route/istio/virtual_service.go @@ -0,0 +1,181 @@ +/* +Copyright 2018 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 istio + +import ( + "fmt" + "sort" + + "github.com/knative/serving/pkg" + "github.com/knative/serving/pkg/apis/istio/v1alpha3" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/knative/serving/pkg/controller" + "github.com/knative/serving/pkg/controller/revision" + "github.com/knative/serving/pkg/controller/route/traffic" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + PortNumber = 80 + PortName = "http" + EnvoyTimeoutHeader = "x-envoy-upstream-rq-timeout-ms" + DefaultEnvoyTimeoutMs = "60000" +) + +// MakeVirtualService creates an Istio VirtualService to set up routing rules. Such VirtualService specifies +// which Gateways and Hosts that it applies to, as well as the routing rules. +func MakeVirtualService(u *v1alpha1.Route, tc *traffic.TrafficConfig) *v1alpha3.VirtualService { + return &v1alpha3.VirtualService{ + ObjectMeta: metav1.ObjectMeta{ + Name: controller.GetVirtualServiceName(u), + Namespace: u.Namespace, + Labels: map[string]string{"route": u.Name}, + OwnerReferences: []metav1.OwnerReference{*controller.NewRouteControllerRef(u)}, + }, + Spec: makeVirtualServiceSpec(u, tc.Targets), + } +} + +func makeVirtualServiceSpec(u *v1alpha1.Route, targets map[string][]traffic.RevisionTarget) v1alpha3.VirtualServiceSpec { + domain := u.Status.Domain + spec := v1alpha3.VirtualServiceSpec{ + // We want to connect to two Gateways: the Knative shared + // Gateway, and the 'mesh' Gateway. The former provides + // access from outside of the cluster, and the latter provides + // access for services from inside the cluster. + Gateways: []string{ + controller.GetServingK8SGatewayFullname(), + "mesh", + }, + Hosts: []string{ + // Traffic originates from outside of the cluster would be of the form "*.domain", or "domain" + fmt.Sprintf("*.%s", domain), + domain, + // Traffic from inside the cluster will use the FQDN of the Route's headless Service. + controller.GetServingK8SServiceFullnameForRoute(u), + }, + } + names := []string{} + for name := range targets { + names = append(names, name) + } + // Sort the names to give things a deterministic ordering. + sort.Strings(names) + // The routes are matching rule based on domain name to traffic split targets. + for _, name := range names { + spec.Http = append(spec.Http, *makeVirtualServiceRoute(getRouteDomains(name, u, domain), u.Namespace, targets[name])) + } + return spec +} + +func getRouteDomains(targetName string, u *v1alpha1.Route, domain string) []string { + if targetName == "" { + // Nameless traffic targets correspond to two domains: the Route.Status.Domain, and also the FQDN + // of the Route's headless Service. + return []string{domain, controller.GetServingK8SServiceFullnameForRoute(u)} + } + // Named traffic targets correspond to a subdomain of the Route.Status.Domain. + return []string{fmt.Sprintf("%s.%s", targetName, domain)} +} + +func makeVirtualServiceRoute(domains []string, ns string, targets []traffic.RevisionTarget) *v1alpha3.HTTPRoute { + matches := []v1alpha3.HTTPMatchRequest{} + // Istio list of matches are OR'ed together. The following build a match set that matches any of the given domains. + for _, domain := range domains { + matches = append(matches, v1alpha3.HTTPMatchRequest{ + Authority: &v1alpha3.StringMatch{ + Exact: domain, + }, + }) + } + active, inactive := groupInactiveTargets(targets) + weights := []v1alpha3.DestinationWeight{} + for _, t := range active { + if t.Percent == 0 { + continue + } + weights = append(weights, v1alpha3.DestinationWeight{ + Destination: v1alpha3.Destination{ + Host: controller.GetK8SServiceFullname( + controller.GetServingK8SServiceNameForObj(t.TrafficTarget.RevisionName), ns), + Port: v1alpha3.PortSelector{ + Number: uint32(revision.ServicePort), + }, + }, + Weight: t.Percent, + }) + } + route := v1alpha3.HTTPRoute{ + Match: matches, + Route: weights, + } + // Add traffic rules for activator. + return addActivatorRoutes(&route, ns, inactive) +} + +///////////////////////////////////////////////// +// Activator routing logic. +///////////////////////////////////////////////// + +// TODO: The ideal solution is to append different revision name as headers for each inactive revision. +// See https://github.com/istio/issues/issues/332 +// +// We will direct traffic for all inactive revisions to activator service; and the activator will send +// the request to the inactive revision with the largest traffic weight. +// The consequence of using appendHeaders at Spec is: if there are more than one inactive revisions, the +// traffic split percentage would be distorted in a short period of time. +func addActivatorRoutes(r *v1alpha3.HTTPRoute, ns string, inactive []traffic.RevisionTarget) *v1alpha3.HTTPRoute { + if len(inactive) == 0 { + // No need to change + return r + } + totalInactivePercent := 0 + maxInactiveTarget := traffic.RevisionTarget{} + + for _, t := range inactive { + totalInactivePercent += t.Percent + if t.Percent >= maxInactiveTarget.Percent { + maxInactiveTarget = t + } + } + r.Route = append(r.Route, v1alpha3.DestinationWeight{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", controller.GetServingK8SActivatorServiceName(), pkg.GetServingSystemNamespace()), + Port: v1alpha3.PortSelector{ + Number: uint32(revision.ServicePort), + }, + }, + Weight: totalInactivePercent, + }) + r.AppendHeaders = map[string]string{ + controller.GetRevisionHeaderName(): maxInactiveTarget.RevisionName, + controller.GetRevisionHeaderNamespace(): ns, + EnvoyTimeoutHeader: DefaultEnvoyTimeoutMs, + } + return r +} + +func groupInactiveTargets(targets []traffic.RevisionTarget) (active []traffic.RevisionTarget, inactive []traffic.RevisionTarget) { + for _, t := range targets { + if t.Active { + active = append(active, t) + } else { + inactive = append(inactive, t) + } + } + return active, inactive +} diff --git a/pkg/controller/route/istio/virtual_service_test.go b/pkg/controller/route/istio/virtual_service_test.go new file mode 100644 index 000000000000..3f764c141879 --- /dev/null +++ b/pkg/controller/route/istio/virtual_service_test.go @@ -0,0 +1,334 @@ +/* +Copyright 2018 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 istio + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/knative/serving/pkg/apis/istio/v1alpha3" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/knative/serving/pkg/controller" + "github.com/knative/serving/pkg/controller/route/traffic" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMakeVirtualServiceSpec_CorrectMetadata(t *testing.T) { + targets := map[string][]traffic.RevisionTarget{} + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{"route": "test-route"}, + }, + Status: v1alpha1.RouteStatus{Domain: "domain.com"}, + } + expected := metav1.ObjectMeta{ + Name: "test-route-istio", + Namespace: "test-ns", + Labels: map[string]string{"route": "test-route"}, + OwnerReferences: []metav1.OwnerReference{ + *controller.NewRouteControllerRef(r), + }, + } + meta := MakeVirtualService(r, &traffic.TrafficConfig{Targets: targets}).ObjectMeta + if diff := cmp.Diff(expected, meta); diff != "" { + t.Errorf("Unexpected metadata (-want +got): %v", diff) + } +} + +func TestMakeVirtualServiceSpec_CorrectSpec(t *testing.T) { + targets := map[string][]traffic.RevisionTarget{} + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{"route": "test-route"}, + }, + Status: v1alpha1.RouteStatus{Domain: "domain.com"}, + } + expected := v1alpha3.VirtualServiceSpec{ + // We want to connect to two Gateways: the Route's ingress Gateway, and the 'mesh' Gateway. The former + // provides access from outside of the cluster, and the latter provides access for services from inside + // the cluster. + Gateways: []string{ + "knative-shared-gateway.knative-serving.svc.cluster.local", + "mesh", + }, + Hosts: []string{ + "*.domain.com", + "domain.com", + "test-route-service.test-ns.svc.cluster.local", + }, + } + routes := MakeVirtualService(r, &traffic.TrafficConfig{Targets: targets}).Spec + if diff := cmp.Diff(expected, routes); diff != "" { + t.Errorf("Unexpected routes (-want +got): %v", diff) + } +} + +func TestMakeVirtualServiceSpec_CorrectRoutes(t *testing.T) { + targets := map[string][]traffic.RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: "config", + RevisionName: "v2", + Percent: 100, + }, + Active: true, + }}, + "v1": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: "config", + RevisionName: "v1", + Percent: 100, + }, + Active: true, + }}, + } + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{"route": "test-route"}, + }, + Status: v1alpha1.RouteStatus{Domain: "domain.com"}, + } + expected := []v1alpha3.HTTPRoute{{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: "domain.com"}, + }, { + Authority: &v1alpha3.StringMatch{Exact: "test-route-service.test-ns.svc.cluster.local"}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: "v2-service.test-ns.svc.cluster.local", + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, + }, { + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: "v1.domain.com"}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: "v1-service.test-ns.svc.cluster.local", + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, + }} + routes := MakeVirtualService(r, &traffic.TrafficConfig{Targets: targets}).Spec.Http + if diff := cmp.Diff(expected, routes); diff != "" { + fmt.Printf("%+v\n", routes) + fmt.Printf("%+v\n", expected) + t.Errorf("Unexpected routes (-want +got): %v", diff) + } +} + +func TestGetRouteDomains_NamelessTarget(t *testing.T) { + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{ + "route": "test-route", + }, + }, + } + base := "domain.com" + expected := []string{base, "test-route-service.test-ns.svc.cluster.local"} + domains := getRouteDomains("", r, base) + if diff := cmp.Diff(expected, domains); diff != "" { + t.Errorf("Unexpected domains (-want +got): %v", diff) + } +} + +func TestGetRouteDomains_NamedTarget(t *testing.T) { + r := &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: "test-ns", + Labels: map[string]string{ + "route": "test-route", + }, + }, + } + name := "v1" + base := "domain.com" + expected := []string{"v1.domain.com"} + domains := getRouteDomains(name, r, base) + if diff := cmp.Diff(expected, domains); diff != "" { + t.Errorf("Unexpected domains (-want +got): %v", diff) + } +} + +// One active target. +func TestMakeVirtualServiceRoute_Vanilla(t *testing.T) { + targets := []traffic.RevisionTarget{{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: "config", + RevisionName: "revision", + Percent: 100, + }, + Active: true, + }} + domains := []string{"a.com", "b.org"} + ns := "test-ns" + route := makeVirtualServiceRoute(domains, ns, targets) + expected := v1alpha3.HTTPRoute{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: "a.com"}, + }, { + Authority: &v1alpha3.StringMatch{Exact: "b.org"}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: "revision-service.test-ns.svc.cluster.local", + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, + } + if diff := cmp.Diff(&expected, route); diff != "" { + t.Errorf("Unexpected route (-want +got): %v", diff) + } +} + +// Two active targets. +func TestMakeVirtualServiceRoute_TwoTargets(t *testing.T) { + targets := []traffic.RevisionTarget{{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: "config", + RevisionName: "revision", + Percent: 90, + }, + Active: true, + }, { + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: "new-config", + RevisionName: "new-revision", + Percent: 10, + }, + Active: true, + }} + domains := []string{"test.org"} + ns := "test-ns" + route := makeVirtualServiceRoute(domains, ns, targets) + expected := v1alpha3.HTTPRoute{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: "test.org"}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: "revision-service.test-ns.svc.cluster.local", + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 90, + }, { + Destination: v1alpha3.Destination{ + Host: "new-revision-service.test-ns.svc.cluster.local", + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 10, + }}, + } + if diff := cmp.Diff(&expected, route); diff != "" { + t.Errorf("Unexpected route (-want +got): %v", diff) + } +} + +// One Inactive target. +func TestMakeVirtualServiceRoute_VanillaScaledToZero(t *testing.T) { + targets := []traffic.RevisionTarget{{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: "config", + RevisionName: "revision", + Percent: 100, + }, + Active: false, + }} + domains := []string{"a.com", "b.org"} + ns := "test-ns" + route := makeVirtualServiceRoute(domains, ns, targets) + expected := v1alpha3.HTTPRoute{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: "a.com"}, + }, { + Authority: &v1alpha3.StringMatch{Exact: "b.org"}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: "activator-service.knative-serving.svc.cluster.local", + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, + AppendHeaders: map[string]string{ + "Knative-Serving-Revision": "revision", + "Knative-Serving-Namespace": "test-ns", + EnvoyTimeoutHeader: DefaultEnvoyTimeoutMs, + }, + } + if diff := cmp.Diff(&expected, route); diff != "" { + t.Errorf("Unexpected route (-want +got): %v", diff) + } +} + +// Two inactive targets. +func TestMakeVirtualServiceRoute_TwoInactiveTargets(t *testing.T) { + targets := []traffic.RevisionTarget{{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: "config", + RevisionName: "revision", + Percent: 90, + }, + Active: false, + }, { + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: "new-config", + RevisionName: "new-revision", + Percent: 10, + }, + Active: false, + }} + domains := []string{"test.org"} + ns := "test-ns" + route := makeVirtualServiceRoute(domains, ns, targets) + expected := v1alpha3.HTTPRoute{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: "test.org"}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: "activator-service.knative-serving.svc.cluster.local", + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, + AppendHeaders: map[string]string{ + "Knative-Serving-Revision": "revision", + "Knative-Serving-Namespace": "test-ns", + EnvoyTimeoutHeader: DefaultEnvoyTimeoutMs, + }, + } + if diff := cmp.Diff(&expected, route); diff != "" { + t.Errorf("Unexpected route (-want +got): %v", diff) + } +} diff --git a/pkg/controller/route/istio_route.go b/pkg/controller/route/istio_route.go deleted file mode 100644 index 8dd689bb8eac..000000000000 --- a/pkg/controller/route/istio_route.go +++ /dev/null @@ -1,127 +0,0 @@ -/* -Copyright 2018 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 route - -import ( - "fmt" - "regexp" - - istiov1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" - "github.com/knative/serving/pkg/apis/serving/v1alpha1" - "github.com/knative/serving/pkg/controller" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const requestTimeoutMs = "60000" - -// makeIstioRouteSpec creates an Istio route -func makeIstioRouteSpec(u *v1alpha1.Route, tt *v1alpha1.TrafficTarget, ns string, routes []RevisionRoute, domain string, inactiveRev string) istiov1alpha2.RouteRuleSpec { - destinationWeights := calculateDestinationWeights(tt, routes) - if tt != nil { - domain = fmt.Sprintf("%s.%s", tt.Name, domain) - } - spec := istiov1alpha2.RouteRuleSpec{ - Destination: istiov1alpha2.IstioService{ - Name: controller.GetServingK8SServiceName(u), - Namespace: ns, - }, - Match: istiov1alpha2.Match{ - Request: istiov1alpha2.MatchRequest{ - Headers: istiov1alpha2.Headers{ - Authority: istiov1alpha2.MatchString{ - Regex: regexp.QuoteMeta(domain), - }, - }, - }, - }, - Route: destinationWeights, - } - - // TODO: The ideal solution is to append different revision name as headers for each inactive revision. - // See https://github.com/istio/issues/issues/332 - // Since appendHeaders is a field for RouteRule Spec, we don't have that granularity. - // We will direct traffic for all inactive revisions to activator service; and the activator will send - // the request to the inactive revision with the largest traffic weight. - // The consequence of using appendHeaders at Spec is: if there are more than one inactive revisions, the - // traffic split percentage would be distorted in a short period of time. - if inactiveRev != "" { - appendHeaders := make(map[string]string) - appendHeaders[controller.GetRevisionHeaderName()] = inactiveRev - appendHeaders[controller.GetRevisionHeaderNamespace()] = u.Namespace - // Set the Envoy upstream timeout to be 60 seconds, in case the revision needs longer time to come up - // https://www.envoyproxy.io/docs/envoy/v1.5.0/configuration/http_filters/router_filter#x-envoy-upstream-rq-timeout-ms - appendHeaders["x-envoy-upstream-rq-timeout-ms"] = requestTimeoutMs - spec.AppendHeaders = appendHeaders - } - return spec -} - -// MakeIstioRoutes creates an Istio route -func MakeIstioRoutes(u *v1alpha1.Route, tt *v1alpha1.TrafficTarget, ns string, routes []RevisionRoute, domain string, inactiveRev string) *istiov1alpha2.RouteRule { - labels := map[string]string{"route": u.Name} - if tt != nil { - labels["traffictarget"] = tt.Name - } - - r := &istiov1alpha2.RouteRule{ - ObjectMeta: metav1.ObjectMeta{ - Name: controller.GetRouteRuleName(u, tt), - Namespace: ns, - Labels: labels, - }, - Spec: makeIstioRouteSpec(u, tt, ns, routes, domain, inactiveRev), - } - serviceRef := controller.NewRouteControllerRef(u) - r.OwnerReferences = append(r.OwnerReferences, *serviceRef) - return r -} - -// calculateDestinationWeights returns the destination weights for -// the istio route rule. -func calculateDestinationWeights(tt *v1alpha1.TrafficTarget, routes []RevisionRoute) []istiov1alpha2.DestinationWeight { - var istioServiceName string - var istioServiceNs string - - if tt != nil { - for _, r := range routes { - if r.Name == tt.Name { - istioServiceName = r.Service - istioServiceNs = r.Namespace - } - } - return []istiov1alpha2.DestinationWeight{{ - Destination: istiov1alpha2.IstioService{ - Name: istioServiceName, - Namespace: istioServiceNs, - }, - Weight: 100, - }} - } - - destinationWeights := []istiov1alpha2.DestinationWeight{} - for _, route := range routes { - destinationWeights = append(destinationWeights, - istiov1alpha2.DestinationWeight{ - Destination: istiov1alpha2.IstioService{ - Name: route.Service, - Namespace: route.Namespace, - }, - Weight: route.Weight, - }) - } - return destinationWeights -} diff --git a/pkg/controller/route/istio_route_test.go b/pkg/controller/route/istio_route_test.go deleted file mode 100644 index 2dd5e002fcc0..000000000000 --- a/pkg/controller/route/istio_route_test.go +++ /dev/null @@ -1,157 +0,0 @@ -/* -Copyright 2018 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 route - -import ( - "regexp" - "testing" - - "github.com/google/go-cmp/cmp" - istiov1alpha2 "github.com/knative/serving/pkg/apis/istio/v1alpha2" - "github.com/knative/serving/pkg/apis/serving/v1alpha1" - "github.com/knative/serving/pkg/controller" -) - -const ( - testDomain = "test-domain" - emptyInactiveRev = "" - testInactiveRev = "test-rev" -) - -func getTestRevisionRoutes() []RevisionRoute { - return []RevisionRoute{{ - Service: "p-deadbeef-service", - Namespace: testNamespace, - Weight: 98, - }, { - Service: "test-rev-service", - Namespace: testNamespace, - Weight: 2, - }} -} - -func TestMakeIstioRouteSpecRevisionsActive(t *testing.T) { - route := getTestRouteWithTrafficTargets(nil) - rr := getTestRevisionRoutes() - expectedIstioRouteSpec := istiov1alpha2.RouteRuleSpec{ - Destination: istiov1alpha2.IstioService{ - Name: "test-route-service", - Namespace: testNamespace, - }, - Match: istiov1alpha2.Match{ - Request: istiov1alpha2.MatchRequest{ - Headers: istiov1alpha2.Headers{ - Authority: istiov1alpha2.MatchString{ - Regex: regexp.QuoteMeta(testDomain), - }, - }, - }, - }, - Route: calculateDestinationWeights(nil, rr), - } - - istioRouteSpec := makeIstioRouteSpec(route, nil, testNamespace, rr, testDomain, emptyInactiveRev) - - if diff := cmp.Diff(expectedIstioRouteSpec, istioRouteSpec); diff != "" { - t.Errorf("Unexpected istio route spec diff (-want +got): %v", diff) - } -} - -func TestMakeIstioRouteSpecRevisionInactive(t *testing.T) { - route := getTestRouteWithTrafficTargets(nil) - rr := getTestRevisionRoutes() - appendHeaders := make(map[string]string) - appendHeaders[controller.GetRevisionHeaderName()] = testInactiveRev - appendHeaders[controller.GetRevisionHeaderNamespace()] = testNamespace - appendHeaders["x-envoy-upstream-rq-timeout-ms"] = requestTimeoutMs - expectedIstioRouteSpec := istiov1alpha2.RouteRuleSpec{ - Destination: istiov1alpha2.IstioService{ - Name: "test-route-service", - Namespace: testNamespace, - }, - Match: istiov1alpha2.Match{ - Request: istiov1alpha2.MatchRequest{ - Headers: istiov1alpha2.Headers{ - Authority: istiov1alpha2.MatchString{ - Regex: regexp.QuoteMeta(testDomain), - }, - }, - }, - }, - Route: []istiov1alpha2.DestinationWeight{{ - Destination: istiov1alpha2.IstioService{ - Name: "p-deadbeef-service", - Namespace: testNamespace, - }, - Weight: 98, - }, { - Destination: istiov1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 2, - }}, - AppendHeaders: appendHeaders, - } - - istioRouteSpec := makeIstioRouteSpec(route, nil, testNamespace, rr, testDomain, testInactiveRev) - - if diff := cmp.Diff(expectedIstioRouteSpec, istioRouteSpec); diff != "" { - t.Errorf("Unexpected istio route spec diff (-want +got): %v", diff) - } -} - -func TestCalculateDestinationWeightsNoTrafficTarget(t *testing.T) { - rr := getTestRevisionRoutes() - expectedDestinationWeights := []istiov1alpha2.DestinationWeight{{ - Destination: istiov1alpha2.IstioService{ - Name: "p-deadbeef-service", - Namespace: testNamespace, - }, - Weight: 98, - }, { - Destination: istiov1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 2, - }} - - destinationWeights := calculateDestinationWeights(nil, rr) - - if diff := cmp.Diff(expectedDestinationWeights, destinationWeights); diff != "" { - t.Errorf("Unexpected destination weights diff (-want +got): %v", diff) - } -} - -func TestCalculateDestinationWeightsWithTrafficTarget(t *testing.T) { - tt := v1alpha1.TrafficTarget{ - RevisionName: "test-rev", - Percent: 100, - } - rr := getTestRevisionRoutes() - expectedDestinationWeights := []istiov1alpha2.DestinationWeight{{ - Destination: istiov1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 100, - }} - - destinationWeights := calculateDestinationWeights(&tt, rr) - - if diff := cmp.Diff(expectedDestinationWeights, destinationWeights); diff != "" { - t.Errorf("Unexpected destination weights diff (-want +got): %v", diff) - } -} diff --git a/pkg/controller/route/labels.go b/pkg/controller/route/labels.go new file mode 100644 index 000000000000..99249d65aa2c --- /dev/null +++ b/pkg/controller/route/labels.go @@ -0,0 +1,106 @@ +/* +Copyright 2018 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 + + https://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 route + +import ( + "context" + "errors" + "fmt" + + "github.com/knative/serving/pkg/apis/serving" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/knative/serving/pkg/controller/route/traffic" + "github.com/knative/serving/pkg/logging" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (c *Controller) syncLabels(ctx context.Context, r *v1alpha1.Route, tc *traffic.TrafficConfig) error { + if err := c.deleteLabelForOutsideOfGivenConfigurations(ctx, r, tc.Configurations); err != nil { + return err + } + if err := c.setLabelForGivenConfigurations(ctx, r, tc.Configurations); err != nil { + return err + } + return nil +} + +func (c *Controller) setLabelForGivenConfigurations( + ctx context.Context, route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration) error { + logger := logging.FromContext(ctx) + configClient := c.ServingClientSet.ServingV1alpha1().Configurations(route.Namespace) + + // Validate + for _, config := range configMap { + if routeName, ok := config.Labels[serving.RouteLabelKey]; ok { + // TODO(yanweiguo): add a condition in status for this error + if routeName != route.Name { + errMsg := fmt.Sprintf("Configuration %q is already in use by %q, and cannot be used by %q", + config.Name, routeName, route.Name) + c.Recorder.Event(route, corev1.EventTypeWarning, "ConfigurationInUse", errMsg) + logger.Error(errMsg) + return errors.New(errMsg) + } + } + } + + // Set label for newly added configurations as traffic target. + for _, config := range configMap { + if config.Labels == nil { + config.Labels = make(map[string]string) + } else if _, ok := config.Labels[serving.RouteLabelKey]; ok { + continue + } + config.Labels[serving.RouteLabelKey] = route.Name + if _, err := configClient.Update(config); err != nil { + logger.Errorf("Failed to update Configuration %s: %s", config.Name, err) + return err + } + } + + return nil +} + +func (c *Controller) deleteLabelForOutsideOfGivenConfigurations( + ctx context.Context, route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration) error { + logger := logging.FromContext(ctx) + configClient := c.ServingClientSet.ServingV1alpha1().Configurations(route.Namespace) + // Get Configurations set as traffic target before this sync. + oldConfigsList, err := configClient.List( + metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", serving.RouteLabelKey, route.Name), + }, + ) + if err != nil { + logger.Errorf("Failed to fetch configurations with label '%s=%s': %s", + serving.RouteLabelKey, route.Name, err) + return err + } + + // Delete label for newly removed configurations as traffic target. + for _, config := range oldConfigsList.Items { + if _, ok := configMap[config.Name]; !ok { + delete(config.Labels, serving.RouteLabelKey) + if _, err := configClient.Update(&config); err != nil { + logger.Errorf("Failed to update Configuration %s: %s", config.Name, err) + return err + } + } + } + + return nil +} diff --git a/pkg/controller/route/route.go b/pkg/controller/route/route.go index 159deb4d808b..e87bfb21192d 100644 --- a/pkg/controller/route/route.go +++ b/pkg/controller/route/route.go @@ -18,19 +18,13 @@ package route import ( "context" - "errors" "fmt" - "reflect" "sync" - "github.com/knative/serving/pkg" - "github.com/josephburnett/k8sflag/pkg/k8sflag" corev1 "k8s.io/api/core/v1" - "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/equality" apierrs "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/cache" @@ -39,34 +33,17 @@ import ( servinginformers "github.com/knative/serving/pkg/client/informers/externalversions/serving/v1alpha1" listers "github.com/knative/serving/pkg/client/listers/serving/v1alpha1" "github.com/knative/serving/pkg/controller" + "github.com/knative/serving/pkg/controller/route/istio" + "github.com/knative/serving/pkg/controller/route/traffic" "github.com/knative/serving/pkg/logging" "github.com/knative/serving/pkg/logging/logkey" "go.uber.org/zap" - extv1beta1informers "k8s.io/client-go/informers/extensions/v1beta1" ) const ( controllerAgentName = "route-controller" ) -// RevisionRoute represents a single target to route to. -// Basically represents a k8s service representing a specific Revision -// and how much of the traffic goes to it. -type RevisionRoute struct { - // Name for external routing. Optional - Name string - // RevisionName is the underlying revision that we're currently - // routing to. Could be resolved from the Configuration or - // specified explicitly in TrafficTarget - RevisionName string - // Service is the name of the k8s service we route to. - // Note we should not put service namespace as a suffix in this field. - Service string - // The k8s service namespace - Namespace string - Weight int -} - // Controller implements the controller for Route resources. type Controller struct { *controller.Base @@ -92,7 +69,6 @@ func NewController( opt controller.Options, routeInformer servinginformers.RouteInformer, configInformer servinginformers.ConfigurationInformer, - ingressInformer extv1beta1informers.IngressInformer, enableScaleToZero *k8sflag.BoolFlag, ) *Controller { @@ -121,16 +97,6 @@ func NewController( c.SyncConfiguration(new.(*v1alpha1.Configuration)) }, }) - // v1beta1.Ingress - ingressInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - c.SyncIngress(obj.(*v1beta1.Ingress)) - }, - UpdateFunc: func(old, new interface{}) { - c.SyncIngress(new.(*v1beta1.Ingress)) - }, - }) - opt.ConfigMapWatcher.Watch(controller.GetDomainConfigMapName(), c.receiveDomainConfig) return c @@ -144,10 +110,9 @@ func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { return c.RunController(threadiness, stopCh, c.updateRouteEvent, "Route") } -// loggerWithRouteInfo enriches the logs with route name and namespace. -func loggerWithRouteInfo(logger *zap.SugaredLogger, ns string, name string) *zap.SugaredLogger { - return logger.With(zap.String(logkey.Namespace, ns), zap.String(logkey.Route, name)) -} +///////////////////////////////////////// +// Event handlers +///////////////////////////////////////// // updateRouteEvent compares the actual state with the desired, and attempts to // converge the two. It then updates the Status block of the Route resource @@ -196,665 +161,53 @@ func (c *Controller) reconcile(ctx context.Context, route *v1alpha1.Route) error route.Status.InitializeConditions() logger.Infof("Reconciling route :%v", route) - - // Create a placeholder service that is simply used by istio as a placeholder. - // This service could eventually be the 'activator' service that will get all the - // fallthrough traffic if there are no route rules (revisions to target). - // This is one way to implement the 0->1. For now, we'll just create a placeholder - // that selects nothing. logger.Info("Creating/Updating placeholder k8s services") - if err := c.reconcilePlaceholderService(ctx, route); err != nil { return err } - - // Call syncTrafficTargets, which also updates the Route.Status - // to contain the domain we will use for Ingress creation. - - if _, err := c.syncTrafficTargets(ctx, route); err != nil { - return err - } - - // Then create or update the Ingress rule for this service - logger.Info("Creating or updating ingress rule") - if err := c.reconcileIngress(ctx, route); err != nil { - logger.Error("Failed to create or update ingress rule", zap.Error(err)) + // Call configureTrafficAndUpdateRouteStatus, which also updates the Route.Status + // to contain the domain we will use for Gateway creation. + if _, err := c.configureTrafficAndUpdateRouteStatus(ctx, route); err != nil { return err } + logger.Info("Route successfully synced") return nil } -func (c *Controller) getDomainConfig() *DomainConfig { - c.domainConfigMutex.Lock() - defer c.domainConfigMutex.Unlock() - return c.domainConfig -} - -func (c *Controller) receiveDomainConfig(configMap *corev1.ConfigMap) { - newDomainConfig, err := NewDomainConfigFromConfigMap(configMap) - if err != nil { - c.Logger.Error("Failed to parse the new config map. Previous config map will be used.", - zap.Error(err)) - return - } - c.domainConfigMutex.Lock() - defer c.domainConfigMutex.Unlock() - c.domainConfig = newDomainConfig -} - -func (c *Controller) routeDomain(route *v1alpha1.Route) string { - domain := c.getDomainConfig().LookupDomainForLabels(route.ObjectMeta.Labels) - return fmt.Sprintf("%s.%s.%s", route.Name, route.Namespace, domain) -} - -// syncTrafficTargets attempts to converge the actual state and desired state -// according to the traffic targets in Spec field for Route resource. It then updates the Status -// block of the Route and returns the updated one. -func (c *Controller) syncTrafficTargets(ctx context.Context, route *v1alpha1.Route) (*v1alpha1.Route, error) { - logger := logging.FromContext(ctx) - c.consolidateTrafficTargets(ctx, route) - configMap, revMap, err := c.getDirectTrafficTargets(ctx, route) - if err != nil { - return nil, err - } - if err := c.extendConfigurationsWithIndirectTrafficTargets(ctx, route, configMap, revMap); err != nil { - return nil, err - } - if err := c.extendRevisionsWithIndirectTrafficTargets(ctx, route, configMap, revMap); err != nil { - return nil, err - } - - if err := c.deleteLabelForOutsideOfGivenConfigurations(ctx, route, configMap); err != nil { - return nil, err - } - if err := c.setLabelForGivenConfigurations(ctx, route, configMap); err != nil { - return nil, err - } - - // Then create the actual route rules. - logger.Info("Creating Istio route rules") - revisionRoutes, err := c.createOrUpdateRouteRules(ctx, route, configMap, revMap) - if err != nil { - logger.Error("Failed to create routes", zap.Error(err)) - return nil, err - } - - // If revision routes were configured, update them - if revisionRoutes != nil { - traffic := []v1alpha1.TrafficTarget{} - for _, r := range revisionRoutes { - traffic = append(traffic, v1alpha1.TrafficTarget{ - Name: r.Name, - RevisionName: r.RevisionName, - Percent: r.Weight, - }) - } - route.Status.Traffic = traffic - } - route.Status.Domain = c.routeDomain(route) - return route, nil -} - -func (c *Controller) reconcilePlaceholderService(ctx context.Context, route *v1alpha1.Route) error { - logger := logging.FromContext(ctx) - service := MakeRouteK8SService(route) - if _, err := c.KubeClientSet.CoreV1().Services(route.Namespace).Create(service); err != nil { - if apierrs.IsAlreadyExists(err) { - // Service already exist. - return nil - } - logger.Error("Failed to create service", zap.Error(err)) - c.Recorder.Eventf(route, corev1.EventTypeWarning, "CreationFailed", "Failed to create service %q: %v", service.Name, err) - return err - } - logger.Infof("Created service %s", service.Name) - c.Recorder.Eventf(route, corev1.EventTypeNormal, "Created", "Created service %q", service.Name) - return nil -} - -func (c *Controller) reconcileIngress(ctx context.Context, route *v1alpha1.Route) error { - logger := logging.FromContext(ctx) - ingressNamespace := route.Namespace - ingressName := controller.GetServingK8SIngressName(route) - ingress := MakeRouteIngress(route) - ingressClient := c.KubeClientSet.ExtensionsV1beta1().Ingresses(ingressNamespace) - existing, err := ingressClient.Get(ingressName, metav1.GetOptions{}) - - if err != nil { - if apierrs.IsNotFound(err) { - if _, err = ingressClient.Create(ingress); err == nil { - logger.Infof("Created ingress %q in namespace %q", ingressName, ingressNamespace) - c.Recorder.Eventf(route, corev1.EventTypeNormal, "Created", "Created Ingress %q in namespace %q", ingressName, ingressNamespace) - } - } - return err - } - // Check if there is anything to update. - if !reflect.DeepEqual(existing.Spec, ingress.Spec) { - existing.Spec = ingress.Spec - if _, err = ingressClient.Update(existing); err == nil { - logger.Infof("Updated ingress %q in namespace %q", ingressName, ingressNamespace) - c.Recorder.Eventf(route, corev1.EventTypeNormal, "Updated", "Updated Ingress %q in namespace %q", ingressName, ingressNamespace) - } - return err - } - return nil -} - -func (c *Controller) getDirectTrafficTargets(ctx context.Context, route *v1alpha1.Route) ( - map[string]*v1alpha1.Configuration, map[string]*v1alpha1.Revision, error) { - logger := logging.FromContext(ctx) - ns := route.Namespace - configClient := c.ServingClientSet.ServingV1alpha1().Configurations(ns) - revClient := c.ServingClientSet.ServingV1alpha1().Revisions(ns) - configMap := map[string]*v1alpha1.Configuration{} - revMap := map[string]*v1alpha1.Revision{} - - for _, tt := range route.Spec.Traffic { - if tt.ConfigurationName != "" { - configName := tt.ConfigurationName - config, err := configClient.Get(configName, metav1.GetOptions{}) - if err != nil { - logger.Infof("Failed to fetch Configuration %q: %v", configName, err) - if apierrs.IsNotFound(err) { - route.Status.MarkTrafficNotAssigned("Configuration", configName) - } - return nil, nil, err - } - configMap[configName] = config - } else { - revName := tt.RevisionName - rev, err := revClient.Get(revName, metav1.GetOptions{}) - if err != nil { - logger.Infof("Failed to fetch Revision %q: %v", revName, err) - if apierrs.IsNotFound(err) { - route.Status.MarkTrafficNotAssigned("Revision", revName) - } - return nil, nil, err - } - revMap[revName] = rev - } - } - - return configMap, revMap, nil -} - -func (c *Controller) extendConfigurationsWithIndirectTrafficTargets( - ctx context.Context, route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration, - revMap map[string]*v1alpha1.Revision) error { - logger := logging.FromContext(ctx) - ns := route.Namespace - configClient := c.ServingClientSet.ServingV1alpha1().Configurations(ns) - - // Get indirect configurations. - for _, rev := range revMap { - if configName, ok := rev.Labels[serving.ConfigurationLabelKey]; ok { - if _, ok := configMap[configName]; !ok { - // This is not a duplicated configuration - config, err := configClient.Get(configName, metav1.GetOptions{}) - if err != nil { - logger.Errorf("Failed to fetch Configuration %s: %s", configName, err) - return err - } - configMap[configName] = config - } - } else { - logger.Infof("Revision %s does not have label %s", rev.Name, serving.ConfigurationLabelKey) - } - } - - return nil -} - -func (c *Controller) extendRevisionsWithIndirectTrafficTargets( - ctx context.Context, route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration, - revMap map[string]*v1alpha1.Revision) error { - logger := logging.FromContext(ctx) - ns := route.Namespace - revisionClient := c.ServingClientSet.ServingV1alpha1().Revisions(ns) - - allTrafficAssigned := true - for _, tt := range route.Spec.Traffic { - configName := tt.ConfigurationName - if tt.ConfigurationName == "" { - continue - } - config, ok := configMap[configName] - if !ok { - continue - } - revName := config.Status.LatestReadyRevisionName - if revName == "" { - logger.Infof("Configuration %s is not ready, skipping.", tt.ConfigurationName) - allTrafficAssigned = false - continue - } - if _, ok := revMap[revName]; ok { - // Skip duplicated revisions - continue - } - rev, err := revisionClient.Get(revName, metav1.GetOptions{}) - if err != nil { - logger.Errorf("Failed to fetch Revision %s: %s", revName, err) - if apierrs.IsNotFound(err) { - route.Status.MarkTrafficNotAssigned("Revision", revName) - } - return err - } - revMap[revName] = rev - } - - if allTrafficAssigned { - route.Status.MarkTrafficAssigned() - } - - return nil -} - -func (c *Controller) setLabelForGivenConfigurations( - ctx context.Context, route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration) error { - logger := logging.FromContext(ctx) - configClient := c.ServingClientSet.ServingV1alpha1().Configurations(route.Namespace) - - // Validate - for _, config := range configMap { - if routeName, ok := config.Labels[serving.RouteLabelKey]; ok { - // TODO(yanweiguo): add a condition in status for this error - if routeName != route.Name { - errMsg := fmt.Sprintf("Configuration %q is already in use by %q, and cannot be used by %q", - config.Name, routeName, route.Name) - c.Recorder.Event(route, corev1.EventTypeWarning, "ConfigurationInUse", errMsg) - logger.Error(errMsg) - return errors.New(errMsg) - } - } - } - - // Set label for newly added configurations as traffic target. - for _, config := range configMap { - if config.Labels == nil { - config.Labels = make(map[string]string) - } else if _, ok := config.Labels[serving.RouteLabelKey]; ok { - continue - } - config.Labels[serving.RouteLabelKey] = route.Name - if _, err := configClient.Update(config); err != nil { - logger.Errorf("Failed to update Configuration %s: %s", config.Name, err) - return err - } - } - - return nil -} - -func (c *Controller) deleteLabelForOutsideOfGivenConfigurations( - ctx context.Context, route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration) error { - logger := logging.FromContext(ctx) - configClient := c.ServingClientSet.ServingV1alpha1().Configurations(route.Namespace) - // Get Configurations set as traffic target before this sync. - oldConfigsList, err := configClient.List( - metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", serving.RouteLabelKey, route.Name), - }, - ) - if err != nil { - logger.Errorf("Failed to fetch configurations with label '%s=%s': %s", - serving.RouteLabelKey, route.Name, err) - return err - } - - // Delete label for newly removed configurations as traffic target. - for _, config := range oldConfigsList.Items { - if _, ok := configMap[config.Name]; !ok { - delete(config.Labels, serving.RouteLabelKey) - if _, err := configClient.Update(&config); err != nil { - logger.Errorf("Failed to update Configuration %s: %s", config.Name, err) - return err - } - } - } - - return nil -} - -// computeRevisionRoutes computes RevisionRoute for a route object. If there is one or more inactive revisions and enableScaleToZero -// is true, a route rule with the activator service as the destination will be added. It returns the revision routes, the inactive -// revision name to which the activator should forward requests to, and error if there is any. -func (c *Controller) computeRevisionRoutes( - ctx context.Context, route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration, - revMap map[string]*v1alpha1.Revision) ([]RevisionRoute, string, error) { - - logger := logging.FromContext(ctx) - logger.Debug("Figuring out routes") - enableScaleToZero := c.enableScaleToZero.Get() - // The inactive revision name which has the largest traffic weight. - inactiveRev := "" - // The max percent in all inactive revisions. - maxInactivePercent := 0 - // The total percent of all inactive revisions. - totalInactivePercent := 0 - ns := route.Namespace - servingNS := controller.GetServingNamespaceName(ns) - ret := []RevisionRoute{} - - for _, tt := range route.Spec.Traffic { - var rev *v1alpha1.Revision - var err error - revName := tt.RevisionName - if tt.ConfigurationName != "" { - // Get the configuration's LatestReadyRevisionName - revName = configMap[tt.ConfigurationName].Status.LatestReadyRevisionName - if revName == "" { - logger.Errorf("Configuration %s is not ready. Should skip updating route rules", - tt.ConfigurationName) - return nil, "", nil - } - // Revision has been already fetched indirectly in extendRevisionsWithIndirectTrafficTargets - rev = revMap[revName] - - } else { - // Direct revision has already been fetched - rev = revMap[revName] - } - //TODO(grantr): What should happen if revisionName is empty? - - if rev == nil { - // For safety, which should never happen. - logger.Errorf("Failed to fetch Revision %s: %s", revName, err) - return nil, "", err - } - - hasRouteRule := true - cond := rev.Status.GetCondition(v1alpha1.RevisionConditionReady) - if enableScaleToZero && cond != nil { - // A revision is considered inactive (yet) if it's in - // "Inactive" and "Updating" condition - if (cond.Reason == "Inactive" && cond.Status == corev1.ConditionFalse) || - (cond.Reason == "Updating" && cond.Status == corev1.ConditionUnknown) { - // Let inactiveRev be the Reserve revision with the largest traffic weight. - if tt.Percent > maxInactivePercent { - maxInactivePercent = tt.Percent - inactiveRev = rev.Name - } - totalInactivePercent += tt.Percent - hasRouteRule = false - } - } - - if hasRouteRule { - rr := RevisionRoute{ - Name: tt.Name, - RevisionName: rev.Name, - Service: rev.Status.ServiceName, - Namespace: servingNS, - Weight: tt.Percent, - } - ret = append(ret, rr) - } - } - - // TODO: The ideal solution is to append different revision name as headers for each inactive revision. - // https://github.com/knative/serving/issues/882 - // TODO: We are adding activator to the route whether the weight is zero or positive - // to workaround https://github.com/istio/istio/issues/5204 - // Once migration to Istio Gateway completes, we should change this back so that activator - // is added to the list only if its weight is positive - activatorRoute := RevisionRoute{ - Name: controller.GetServingK8SActivatorServiceName(), - RevisionName: inactiveRev, - Service: controller.GetServingK8SActivatorServiceName(), - Namespace: pkg.GetServingSystemNamespace(), - Weight: totalInactivePercent, - } - ret = append(ret, activatorRoute) - return ret, inactiveRev, nil -} - -// computeEmptyRevisionRoutes is a hack to work around https://github.com/istio/istio/issues/5204. -// Here we add empty/dummy route rules for non-target revisions to prepare to switch traffic to -// them in the future. We are tracking this issue in https://github.com/knative/serving/issues/348. +// configureTrafficAndUpdateRouteStatus attempts to configure traffic based on the RouteSpec. If there are missing +// targets (e.g. Configurations without a Ready Revision, or Revision that isn't Ready or Inactive), no traffic will be +// configured. // -// TODO: Even though this fixes the 503s when switching revisions, revisions will have empty route -// rules to them for perpetuity, therefore not ideal. We should remove this hack once -// https://github.com/istio/istio/issues/5204 is fixed, probably in 0.8.1. -func (c *Controller) computeEmptyRevisionRoutes( - ctx context.Context, route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration, - revMap map[string]*v1alpha1.Revision) ([]RevisionRoute, error) { - logger := logging.FromContext(ctx) - ns := route.Namespace - servingNS := controller.GetServingNamespaceName(ns) - revClient := c.ServingClientSet.ServingV1alpha1().Revisions(ns) - revRoutes := []RevisionRoute{} - for _, tt := range route.Spec.Traffic { - configName := tt.ConfigurationName - if configName != "" { - // Get the configuration's LatestReadyRevisionName - latestReadyRevName := configMap[configName].Status.LatestReadyRevisionName - revs, err := revClient.List(metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", serving.ConfigurationLabelKey, configName), - }) - if err != nil { - logger.Errorf("Failed to fetch revisions of Configuration %q: %s", configName, err) - return nil, err - } - for _, rev := range revs.Items { - if _, ok := revMap[rev.Name]; !ok && rev.Name != latestReadyRevName { - // This is a non-target revision. Adding a dummy rule to make Istio happy, - // so that if we switch traffic to them later we won't cause Istio to - // throw spurious 503s. See https://github.com/istio/istio/issues/5204. - revRoutes = append(revRoutes, RevisionRoute{ - RevisionName: rev.Name, - Service: rev.Status.ServiceName, - Namespace: servingNS, - Weight: 0, - }) - } - } - } - } - return revRoutes, nil -} - -func (c *Controller) createOrUpdateRouteRules(ctx context.Context, route *v1alpha1.Route, - configMap map[string]*v1alpha1.Configuration, revMap map[string]*v1alpha1.Revision) ([]RevisionRoute, error) { - logger := logging.FromContext(ctx) - // grab a client that's specific to RouteRule. - ns := route.Namespace - routeClient := c.ServingClientSet.ConfigV1alpha2().RouteRules(ns) - if routeClient == nil { - logger.Errorf("Failed to create resource client") - return nil, fmt.Errorf("Couldn't get a routeClient") - } - - revisionRoutes, inactiveRev, err := c.computeRevisionRoutes(ctx, route, configMap, revMap) - if err != nil { - logger.Errorf("Failed to get routes for %s : %q", route.Name, err) - return nil, err - } - if len(revisionRoutes) == 0 { - logger.Errorf("No routes were found for the service %q", route.Name) - return nil, nil - } - - // TODO: remove this once https://github.com/istio/istio/issues/5204 is fixed. - emptyRoutes, err := c.computeEmptyRevisionRoutes(ctx, route, configMap, revMap) - if err != nil { - logger.Errorf("Failed to get empty routes for %s : %q", route.Name, err) - return nil, err - } - revisionRoutes = append(revisionRoutes, emptyRoutes...) - // Create route rule for the route domain - routeRuleName := controller.GetRouteRuleName(route, nil) - routeRules, err := routeClient.Get(routeRuleName, metav1.GetOptions{}) - if err != nil { - if !apierrs.IsNotFound(err) { - return nil, err - } - routeRules = MakeIstioRoutes(route, nil, ns, revisionRoutes, c.routeDomain(route), inactiveRev) - if _, err := routeClient.Create(routeRules); err != nil { - c.Recorder.Eventf(route, corev1.EventTypeWarning, "CreationFailed", "Failed to create Istio route rule %q: %s", routeRules.Name, err) - return nil, err - } - c.Recorder.Eventf(route, corev1.EventTypeNormal, "Created", "Created Istio route rule %q", routeRules.Name) - } else { - routeRules.Spec = makeIstioRouteSpec(route, nil, ns, revisionRoutes, c.routeDomain(route), inactiveRev) - if _, err := routeClient.Update(routeRules); err != nil { - c.Recorder.Eventf(route, corev1.EventTypeWarning, "UpdateFailed", "Failed to update Istio route rule %q: %s", routeRules.Name, err) - return nil, err - } - c.Recorder.Eventf(route, corev1.EventTypeNormal, "Updated", "Updated Istio route rule %q", routeRules.Name) - } - - // Create route rule for named traffic targets - for _, tt := range route.Spec.Traffic { - if tt.Name == "" { - continue - } - routeRuleName := controller.GetRouteRuleName(route, &tt) - routeRules, err := routeClient.Get(routeRuleName, metav1.GetOptions{}) - if err != nil { - if !apierrs.IsNotFound(err) { - return nil, err - } - routeRules = MakeIstioRoutes(route, &tt, ns, revisionRoutes, c.routeDomain(route), inactiveRev) - if _, err := routeClient.Create(routeRules); err != nil { - c.Recorder.Eventf(route, corev1.EventTypeWarning, "CreationFailed", "Failed to create Istio route rule %q: %s", routeRules.Name, err) - return nil, err - } - c.Recorder.Eventf(route, corev1.EventTypeNormal, "Created", "Created Istio route rule %q", routeRules.Name) - } else { - routeRules.Spec = makeIstioRouteSpec(route, &tt, ns, revisionRoutes, c.routeDomain(route), inactiveRev) - if _, err := routeClient.Update(routeRules); err != nil { - return nil, err - } - c.Recorder.Eventf(route, corev1.EventTypeNormal, "Updated", "Updated Istio route rule %q", routeRules.Name) - } - } - if err := c.removeOutdatedRouteRules(ctx, route); err != nil { - return nil, err - } - return revisionRoutes, nil -} - -func (c *Controller) updateStatus(ctx context.Context, route *v1alpha1.Route) (*v1alpha1.Route, error) { - logger := logging.FromContext(ctx) - - routeClient := c.ServingClientSet.ServingV1alpha1().Routes(route.Namespace) - existing, err := routeClient.Get(route.Name, metav1.GetOptions{}) - if err != nil { - logger.Warn("Failed to update route status", zap.Error(err)) - c.Recorder.Eventf(route, corev1.EventTypeWarning, "UpdateFailed", "Failed to get current status for route %q: %v", route.Name, err) - return nil, err - } - - // If there's nothing to update, just return. - if reflect.DeepEqual(existing.Status, route.Status) { - return existing, nil - } - - existing.Status = route.Status - // TODO: for CRD there's no updatestatus, so use normal update. - updated, err := routeClient.Update(existing) - if err != nil { - logger.Warn("Failed to update route status", zap.Error(err)) - c.Recorder.Eventf(route, corev1.EventTypeWarning, "UpdateFailed", "Failed to update status for route %q: %v", route.Name, err) - return nil, err - } - - c.Recorder.Eventf(route, corev1.EventTypeNormal, "Updated", "Updated status for route %q", route.Name) - return updated, nil -} - -// consolidateTrafficTargets will consolidate all duplicate revisions -// and configurations. If the traffic target names are unique, the traffic -// targets will not be consolidated. -func (c *Controller) consolidateTrafficTargets(ctx context.Context, route *v1alpha1.Route) { - type trafficTarget struct { - name string - revisionName string - configurationName string - } - - logger := logging.FromContext(ctx) - logger.Infof("Attempting to consolidate traffic targets") - trafficTargets := route.Spec.Traffic - trafficMap := make(map[trafficTarget]int) - var order []trafficTarget - - for _, t := range trafficTargets { - tt := trafficTarget{ - name: t.Name, - revisionName: t.RevisionName, - configurationName: t.ConfigurationName, - } - if trafficMap[tt] != 0 { - logger.Infof( - "Found duplicate traffic targets (name: %s, revision: %s, configuration:%s), consolidating traffic", - tt.name, - tt.revisionName, - tt.configurationName, - ) - trafficMap[tt] += t.Percent - } else { - trafficMap[tt] = t.Percent - // The order to walk the map (Go randomizes otherwise) - order = append(order, tt) - } - } - - consolidatedTraffic := []v1alpha1.TrafficTarget{} - for _, tt := range order { - p := trafficMap[tt] - consolidatedTraffic = append( - consolidatedTraffic, - v1alpha1.TrafficTarget{ - Name: tt.name, - ConfigurationName: tt.configurationName, - RevisionName: tt.revisionName, - Percent: p, - }, - ) - } - route.Spec.Traffic = consolidatedTraffic -} - -func (c *Controller) removeOutdatedRouteRules(ctx context.Context, u *v1alpha1.Route) error { +// If traffic is configured we update the RouteStatus with AllTrafficAssigned = True. Otherwise we mark +// AllTrafficAssigned = False, with a message referring to one of the missing target. +// +// In all cases we will add annotations to the referred targets. This is so that when they become routable we can know +// (through a listener) and attempt traffic configuration again. +func (c *Controller) configureTrafficAndUpdateRouteStatus(ctx context.Context, r *v1alpha1.Route) (*v1alpha1.Route, error) { + r.Status.Domain = c.routeDomain(r) logger := logging.FromContext(ctx) - ns := u.Namespace - routeClient := c.ServingClientSet.ConfigV1alpha2().RouteRules(ns) - if routeClient == nil { - logger.Error("Failed to create resource client") - return errors.New("Couldn't get a routeClient") - } - - routeRuleList, err := routeClient.List(metav1.ListOptions{ - LabelSelector: fmt.Sprintf("route=%s", u.Name), - }) - if err != nil { - return err - } - - routeRuleNames := map[string]struct{}{} - routeRuleNames[controller.GetRouteRuleName(u, nil)] = struct{}{} - for _, tt := range u.Spec.Traffic { - if tt.Name == "" { - continue - } - routeRuleNames[controller.GetRouteRuleName(u, &tt)] = struct{}{} - } - - for _, r := range routeRuleList.Items { - if _, ok := routeRuleNames[r.Name]; ok { - continue - } - logger.Infof("Deleting outdated route: %s", r.Name) - if err := routeClient.Delete(r.Name, nil); err != nil { - if !apierrs.IsNotFound(err) { - return err - } - } - } - return nil + t, targetErr := traffic.BuildTrafficConfiguration( + c.ServingClientSet.ServingV1alpha1().Configurations(r.Namespace), + c.ServingClientSet.ServingV1alpha1().Revisions(r.Namespace), + r) + // Even if targetErr != n`il, we still need to finish updating the labels so that the updates to + // these targets can be propagated back. In case of error updating the labels, we will return + // the error and not targetErr. + if err := c.syncLabels(ctx, r, t); err != nil { + return r, err + } + // Now, after updating the labels, we can return trafficErr here. + if targetErr != nil { + return r, targetErr + } + logger.Info("All referred targets are routable. Creating Istio VirtualService.") + if err := c.reconcileVirtualService(ctx, r, istio.MakeVirtualService(r, t)); err != nil { + return r, err + } + logger.Info("VirtualService created, marking AllTrafficAssigned with traffic information.") + r.Status.Traffic = t.GetTrafficTargets() + r.Status.MarkTrafficAssigned() + return r, nil } func (c *Controller) SyncConfiguration(config *v1alpha1.Configuration) { @@ -865,13 +218,13 @@ func (c *Controller) SyncConfiguration(config *v1alpha1.Configuration) { c.Logger.Infof("Configuration %s is not ready", configName) return } - + // Check whether is configuration is referred by a route. routeName, ok := config.Labels[serving.RouteLabelKey] if !ok { c.Logger.Infof("Configuration %s does not have label %s", configName, serving.RouteLabelKey) return } - + // Configuration is referred by a Route. Update such Route. logger := loggerWithRouteInfo(c.Logger, ns, routeName) ctx := logging.WithLogger(context.TODO(), logger) route, err := c.routeLister.Routes(ns).Get(routeName) @@ -879,57 +232,41 @@ func (c *Controller) SyncConfiguration(config *v1alpha1.Configuration) { logger.Error("Error fetching route upon configuration becoming ready", zap.Error(err)) return } - - // Don't modify the informers copy + // Don't modify the informers copy. route = route.DeepCopy() - if _, err := c.syncTrafficTargets(ctx, route); err != nil { + if _, err := c.configureTrafficAndUpdateRouteStatus(ctx, route); err != nil { logger.Error("Error updating route upon configuration becoming ready", zap.Error(err)) } c.updateStatus(ctx, route) } -func (c *Controller) SyncIngress(ingress *v1beta1.Ingress) { - or := metav1.GetControllerOf(ingress) - if or == nil || or.Kind != "Route" { - // If ingress isn't owned by a route, no route update is required. - return - } - routeName := or.Name +///////////////////////////////////////// +// Misc helpers. +///////////////////////////////////////// +// loggerWithRouteInfo enriches the logs with route name and namespace. +func loggerWithRouteInfo(logger *zap.SugaredLogger, ns string, name string) *zap.SugaredLogger { + return logger.With(zap.String(logkey.Namespace, ns), zap.String(logkey.Route, name)) +} - if len(ingress.Status.LoadBalancer.Ingress) == 0 { - // Route isn't ready if having no load-balancer ingress. - return - } - for _, i := range ingress.Status.LoadBalancer.Ingress { - if i.IP == "" { - // Route isn't ready if some load balancer ingress doesn't have an IP. - return - } - } - ns := ingress.Namespace - routeClient := c.ServingClientSet.ServingV1alpha1().Routes(ns) - route, err := routeClient.Get(routeName, metav1.GetOptions{}) - if err != nil { - c.Logger.Error( - "Error fetching route upon ingress becoming", - zap.Error(err), - zap.String(logkey.Namespace, ns), - zap.String(logkey.Route, routeName)) - return - } - if route.Status.IsReady() { - return - } - route.Status.MarkIngressReady() +func (c *Controller) getDomainConfig() *DomainConfig { + c.domainConfigMutex.Lock() + defer c.domainConfigMutex.Unlock() + return c.domainConfig +} - logger := loggerWithRouteInfo(c.Logger, ns, routeName) - ctx := logging.WithLogger(context.TODO(), logger) - if _, err = c.updateStatus(ctx, route); err != nil { - c.Logger.Error( - "Error updating readiness of route upon ingress becoming", - zap.Error(err), - zap.String(logkey.Namespace, ns), - zap.String(logkey.Route, routeName)) +func (c *Controller) routeDomain(route *v1alpha1.Route) string { + domain := c.getDomainConfig().LookupDomainForLabels(route.ObjectMeta.Labels) + return fmt.Sprintf("%s.%s.%s", route.Name, route.Namespace, domain) +} + +func (c *Controller) receiveDomainConfig(configMap *corev1.ConfigMap) { + newDomainConfig, err := NewDomainConfigFromConfigMap(configMap) + if err != nil { + c.Logger.Error("Failed to parse the new config map. Previous config map will be used.", + zap.Error(err)) return } + c.domainConfigMutex.Lock() + defer c.domainConfigMutex.Unlock() + c.domainConfig = newDomainConfig } diff --git a/pkg/controller/route/route_test.go b/pkg/controller/route/route_test.go index 5a473cd2cddd..2033b38dbb70 100644 --- a/pkg/controller/route/route_test.go +++ b/pkg/controller/route/route_test.go @@ -26,7 +26,6 @@ package route import ( "context" "fmt" - "regexp" "strings" "testing" "time" @@ -42,7 +41,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/josephburnett/k8sflag/pkg/k8sflag" - "github.com/knative/serving/pkg/apis/istio/v1alpha2" + "github.com/knative/serving/pkg/apis/istio/v1alpha3" "github.com/knative/serving/pkg/apis/serving" "github.com/knative/serving/pkg/apis/serving/v1alpha1" fakeclientset "github.com/knative/serving/pkg/client/clientset/versioned/fake" @@ -56,6 +55,7 @@ import ( kubeinformers "k8s.io/client-go/informers" fakekubeclientset "k8s.io/client-go/kubernetes/fake" + "github.com/knative/serving/pkg/controller/route/istio" . "github.com/knative/serving/pkg/controller/testing" ) @@ -135,7 +135,7 @@ func getTestConfiguration() *v1alpha1.Configuration { } func getTestRevisionForConfig(config *v1alpha1.Configuration) *v1alpha1.Revision { - return &v1alpha1.Revision{ + rev := &v1alpha1.Revision{ ObjectMeta: metav1.ObjectMeta{ SelfLink: "/apis/serving/v1alpha1/namespaces/test/revisions/p-deadbeef", Name: "p-deadbeef", @@ -149,13 +149,18 @@ func getTestRevisionForConfig(config *v1alpha1.Configuration) *v1alpha1.Revision ServiceName: "p-deadbeef-service", }, } + rev.Status.MarkResourcesAvailable() + rev.Status.MarkContainerHealthy() + return rev } -func getActivatorDestinationWeight(w int) v1alpha2.DestinationWeight { - return v1alpha2.DestinationWeight{ - Destination: v1alpha2.IstioService{ - Name: ctrl.GetServingK8SActivatorServiceName(), - Namespace: pkg.GetServingSystemNamespace(), +func getActivatorDestinationWeight(w int) v1alpha3.DestinationWeight { + return v1alpha3.DestinationWeight{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", ctrl.GetServingK8SActivatorServiceName(), pkg.GetServingSystemNamespace()), + Port: v1alpha3.PortSelector{ + Number: 80, + }, }, Weight: w, } @@ -197,7 +202,6 @@ func newTestController(servingObjects ...runtime.Object) ( }, servingInformer.Serving().V1alpha1().Routes(), servingInformer.Serving().V1alpha1().Configurations(), - kubeInformer.Extensions().V1beta1().Ingresses(), k8sflag.Bool("enable-scale-to-zero", false), ) @@ -211,8 +215,7 @@ func TestCreateRouteCreatesStuff(t *testing.T) { // Look for the events. Events are delivered asynchronously so we need to use // hooks here. Each hook tests for a specific event. h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Created service "test-route-service"`)) - h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Created Ingress "test-route-ingress"`)) - h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Created Istio route rule "test-route-istio"`)) + h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Created VirtualService "test-route-istio"`)) h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Updated status for route "test-route"`)) // A standalone revision @@ -248,31 +251,15 @@ func TestCreateRouteCreatesStuff(t *testing.T) { t.Errorf("Unexpected service ports diff (-want +got): %v", diff) } - // Look for the ingress. - expectedIngressName := fmt.Sprintf("%s-ingress", route.Name) - ingress, err := kubeClient.ExtensionsV1beta1().Ingresses(testNamespace).Get(expectedIngressName, metav1.GetOptions{}) - if err != nil { - t.Errorf("error getting ingress: %v", err) - } - - expectedDomainPrefix := fmt.Sprintf("%s.%s.", route.Name, route.Namespace) - expectedWildcardDomainPrefix := fmt.Sprintf("*.%s", expectedDomainPrefix) - if !strings.HasPrefix(ingress.Spec.Rules[0].Host, expectedDomainPrefix) { - t.Errorf("Ingress host %q does not have prefix %q", ingress.Spec.Rules[0].Host, expectedDomainPrefix) - } - if !strings.HasPrefix(ingress.Spec.Rules[1].Host, expectedWildcardDomainPrefix) { - t.Errorf("Ingress host %q does not have prefix %q", ingress.Spec.Rules[1].Host, expectedWildcardDomainPrefix) - } - - // Look for the route rule. - routerule, err := servingClient.ConfigV1alpha2().RouteRules(testNamespace).Get(fmt.Sprintf("%s-istio", route.Name), metav1.GetOptions{}) + // Look for the virtual service. + vs, err := servingClient.NetworkingV1alpha3().VirtualServices(testNamespace).Get(ctrl.GetVirtualServiceName(route), metav1.GetOptions{}) if err != nil { - t.Fatalf("error getting routerule: %v", err) + t.Fatalf("error getting VirtualService: %v", err) } // Check labels expectedLabels := map[string]string{"route": route.Name} - if diff := cmp.Diff(expectedLabels, routerule.Labels); diff != "" { + if diff := cmp.Diff(expectedLabels, vs.Labels); diff != "" { t.Errorf("Unexpected label diff (-want +got): %v", diff) } @@ -283,39 +270,44 @@ func TestCreateRouteCreatesStuff(t *testing.T) { Name: route.Name, }} - if diff := cmp.Diff(expectedRefs, routerule.OwnerReferences, cmpopts.IgnoreFields(expectedRefs[0], "Controller", "BlockOwnerDeletion")); diff != "" { + if diff := cmp.Diff(expectedRefs, vs.OwnerReferences, cmpopts.IgnoreFields(expectedRefs[0], "Controller", "BlockOwnerDeletion")); diff != "" { t.Errorf("Unexpected rule owner refs diff (-want +got): %v", diff) } - - expectedRouteSpec := v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: "test-route-service", - Namespace: testNamespace, + domain := strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, ".") + clusterDomain := "test-route-service.test.svc.cluster.local" + expectedSpec := v1alpha3.VirtualServiceSpec{ + // We want to connect to two Gateways: the Route's ingress + // Gateway, and the 'mesh' Gateway. The former provides + // access from outside of the cluster, and the latter provides + // access for services from inside the cluster. + Gateways: []string{ + ctrl.GetServingK8SGatewayFullname(), + "mesh", }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta( - strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, "."), - ), - }, - }, - }, + Hosts: []string{ + "*." + domain, + domain, + clusterDomain, }, - Route: []v1alpha2.DestinationWeight{{ - Destination: v1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 100, - }, getActivatorDestinationWeight(0)}, + Http: []v1alpha3.HTTPRoute{{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: domain}, + }, { + Authority: &v1alpha3.StringMatch{Exact: clusterDomain}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: "test-rev-service.test.svc.cluster.local", + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, + }}, } - if diff := cmp.Diff(expectedRouteSpec, routerule.Spec); diff != "" { + if diff := cmp.Diff(expectedSpec, vs.Spec); diff != "" { t.Errorf("Unexpected rule spec diff (-want +got): %s", diff) } - if err := h.WaitForHooks(time.Second * 3); err != nil { t.Error(err) } @@ -330,8 +322,7 @@ func TestCreateRouteForOneReserveRevision(t *testing.T) { // Look for the events. Events are delivered asynchronously so we need to use // hooks here. Each hook tests for a specific event. h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Created service "test-route-service"`)) - h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Created Ingress "test-route-ingress"`)) - h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Created Istio route rule "test-route-istio"`)) + h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Created VirtualService "test-route-istio"`)) h.OnCreate(&kubeClient.Fake, "events", ExpectNormalEventDelivery(t, `Updated status for route "test-route"`)) // An inactive revision @@ -357,14 +348,14 @@ func TestCreateRouteForOneReserveRevision(t *testing.T) { controller.updateRouteEvent(KeyOrDie(route)) // Look for the route rule with activator as the destination. - routerule, err := servingClient.ConfigV1alpha2().RouteRules(testNamespace).Get(fmt.Sprintf("%s-istio", route.Name), metav1.GetOptions{}) + vs, err := servingClient.NetworkingV1alpha3().VirtualServices(testNamespace).Get(ctrl.GetVirtualServiceName(route), metav1.GetOptions{}) if err != nil { - t.Fatalf("error getting routerule: %v", err) + t.Fatalf("error getting VirtualService: %v", err) } // Check labels expectedLabels := map[string]string{"route": route.Name} - if diff := cmp.Diff(expectedLabels, routerule.Labels); diff != "" { + if diff := cmp.Diff(expectedLabels, vs.Labels); diff != "" { t.Errorf("Unexpected label diff (-want +got): %v", diff) } @@ -375,35 +366,40 @@ func TestCreateRouteForOneReserveRevision(t *testing.T) { Name: route.Name, }} - if diff := cmp.Diff(expectedRefs, routerule.OwnerReferences, cmpopts.IgnoreFields(expectedRefs[0], "Controller", "BlockOwnerDeletion")); diff != "" { + if diff := cmp.Diff(expectedRefs, vs.OwnerReferences, cmpopts.IgnoreFields(expectedRefs[0], "Controller", "BlockOwnerDeletion")); diff != "" { t.Errorf("Unexpected rule owner refs diff (-want +got): %v", diff) } - - appendHeaders := make(map[string]string) - appendHeaders[ctrl.GetRevisionHeaderName()] = "test-rev" - appendHeaders[ctrl.GetRevisionHeaderNamespace()] = testNamespace - appendHeaders["x-envoy-upstream-rq-timeout-ms"] = requestTimeoutMs - expectedRouteSpec := v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: "test-route-service", - Namespace: testNamespace, + domain := strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, ".") + clusterDomain := "test-route-service.test.svc.cluster.local" + expectedSpec := v1alpha3.VirtualServiceSpec{ + // We want to connect to two Gateways: the Route's ingress + // Gateway, and the 'mesh' Gateway. The former provides + // access from outside of the cluster, and the latter provides + // access for services from inside the cluster. + Gateways: []string{ + ctrl.GetServingK8SGatewayFullname(), + "mesh", }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta( - strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, "."), - ), - }, - }, - }, + Hosts: []string{ + "*." + domain, + domain, + clusterDomain, }, - Route: []v1alpha2.DestinationWeight{getActivatorDestinationWeight(100)}, - AppendHeaders: appendHeaders, + Http: []v1alpha3.HTTPRoute{{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: domain}, + }, { + Authority: &v1alpha3.StringMatch{Exact: clusterDomain}, + }}, + Route: []v1alpha3.DestinationWeight{getActivatorDestinationWeight(100)}, + AppendHeaders: map[string]string{ + ctrl.GetRevisionHeaderName(): "test-rev", + ctrl.GetRevisionHeaderNamespace(): testNamespace, + istio.EnvoyTimeoutHeader: istio.DefaultEnvoyTimeoutMs, + }, + }}, } - - if diff := cmp.Diff(expectedRouteSpec, routerule.Spec); diff != "" { + if diff := cmp.Diff(expectedSpec, vs.Spec); diff != "" { t.Errorf("Unexpected rule spec diff (-want +got): %s", diff) } @@ -412,88 +408,6 @@ func TestCreateRouteForOneReserveRevision(t *testing.T) { } } -func TestCreateRouteFromConfigsWithMultipleRevs(t *testing.T) { - _, servingClient, controller, _, servingInformer, _ := newTestController() - - // A configuration and associated revision. Normally the revision would be - // created by the configuration controller. - config := getTestConfiguration() - latestReadyRev := getTestRevisionForConfig(config) - otherRev := &v1alpha1.Revision{ - ObjectMeta: metav1.ObjectMeta{ - SelfLink: "/apis/serving/v1alpha1/namespaces/test/revisions/p-livebeef", - Name: "p-livebeef", - Namespace: testNamespace, - Labels: map[string]string{ - serving.ConfigurationLabelKey: config.Name, - }, - }, - Spec: *config.Spec.RevisionTemplate.Spec.DeepCopy(), - Status: v1alpha1.RevisionStatus{ - ServiceName: "p-livebeef-service", - }, - } - config.Status.LatestReadyRevisionName = latestReadyRev.Name - servingClient.ServingV1alpha1().Configurations(testNamespace).Create(config) - // Since updateRouteEvent looks in the lister, we need to add it to the informer - servingInformer.Serving().V1alpha1().Configurations().Informer().GetIndexer().Add(config) - servingClient.ServingV1alpha1().Revisions(testNamespace).Create(latestReadyRev) - servingClient.ServingV1alpha1().Revisions(testNamespace).Create(otherRev) - - // A route targeting both the config and standalone revision - route := getTestRouteWithTrafficTargets( - []v1alpha1.TrafficTarget{{ - ConfigurationName: config.Name, - Percent: 100, - }}, - ) - servingClient.ServingV1alpha1().Routes(testNamespace).Create(route) - // Since updateRouteEvent looks in the lister, we need to add it to the informer - servingInformer.Serving().V1alpha1().Routes().Informer().GetIndexer().Add(route) - - controller.updateRouteEvent(KeyOrDie(route)) - - routerule, err := servingClient.ConfigV1alpha2().RouteRules(testNamespace).Get(fmt.Sprintf("%s-istio", route.Name), metav1.GetOptions{}) - if err != nil { - t.Fatalf("error getting routerule: %v", err) - } - - expectedRouteSpec := v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: fmt.Sprintf("%s-service", route.Name), - Namespace: testNamespace, - }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta( - strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, "."), - ), - }, - }, - }, - }, - Route: []v1alpha2.DestinationWeight{{ - Destination: v1alpha2.IstioService{ - Name: fmt.Sprintf("%s-service", latestReadyRev.Name), - Namespace: testNamespace, - }, - Weight: 100, - }, getActivatorDestinationWeight(0), { - Destination: v1alpha2.IstioService{ - Name: fmt.Sprintf("%s-service", otherRev.Name), - Namespace: testNamespace, - }, - Weight: 0, - }}, - } - - if diff := cmp.Diff(expectedRouteSpec, routerule.Spec); diff != "" { - t.Errorf("Unexpected rule spec diff (-want +got): %v", diff) - } -} - func TestCreateRouteWithMultipleTargets(t *testing.T) { _, servingClient, controller, _, servingInformer, _ := newTestController() // A standalone revision @@ -526,46 +440,51 @@ func TestCreateRouteWithMultipleTargets(t *testing.T) { controller.updateRouteEvent(KeyOrDie(route)) - routerule, err := servingClient.ConfigV1alpha2().RouteRules(testNamespace).Get(fmt.Sprintf("%s-istio", route.Name), metav1.GetOptions{}) + vs, err := servingClient.NetworkingV1alpha3().VirtualServices(testNamespace).Get(ctrl.GetVirtualServiceName(route), metav1.GetOptions{}) if err != nil { - t.Fatalf("error getting routerule: %v", err) - } - - expectedRouteSpec := v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: fmt.Sprintf("%s-service", route.Name), - Namespace: testNamespace, + t.Fatalf("error getting VirtualService: %v", err) + } + + domain := strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, ".") + clusterDomain := "test-route-service.test.svc.cluster.local" + expectedSpec := v1alpha3.VirtualServiceSpec{ + // We want to connect to two Gateways: the Route's ingress + // Gateway, and the 'mesh' Gateway. The former provides + // access from outside of the cluster, and the latter provides + // access for services from inside the cluster. + Gateways: []string{ + ctrl.GetServingK8SGatewayFullname(), + "mesh", }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta( - strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, "."), - ), - }, - }, - }, + Hosts: []string{ + "*." + domain, + domain, + clusterDomain, }, - Route: []v1alpha2.DestinationWeight{{ - Destination: v1alpha2.IstioService{ - Name: fmt.Sprintf("%s-service", cfgrev.Name), - Namespace: testNamespace, - }, - Weight: 90, - }, { - Destination: v1alpha2.IstioService{ - Name: fmt.Sprintf("%s-service", rev.Name), - Namespace: testNamespace, - }, - Weight: 10, - }, getActivatorDestinationWeight(0)}, + Http: []v1alpha3.HTTPRoute{{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: domain}, + }, { + Authority: &v1alpha3.StringMatch{Exact: clusterDomain}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s-service.test.svc.cluster.local", cfgrev.Name), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 90, + }, { + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s-service.test.svc.cluster.local", rev.Name), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 10, + }}, + }}, } - - if diff := cmp.Diff(expectedRouteSpec, routerule.Spec); diff != "" { + if diff := cmp.Diff(expectedSpec, vs.Spec); diff != "" { t.Errorf("Unexpected rule spec diff (-want +got): %v", diff) } - } // Test one out of multiple target revisions is in Reserve serving state. @@ -608,42 +527,48 @@ func TestCreateRouteWithOneTargetReserve(t *testing.T) { controller.updateRouteEvent(KeyOrDie(route)) - routerule, err := servingClient.ConfigV1alpha2().RouteRules(testNamespace).Get(fmt.Sprintf("%s-istio", route.Name), metav1.GetOptions{}) + vs, err := servingClient.NetworkingV1alpha3().VirtualServices(testNamespace).Get(ctrl.GetVirtualServiceName(route), metav1.GetOptions{}) if err != nil { - t.Fatalf("error getting routerule: %v", err) - } - - appendHeaders := make(map[string]string) - appendHeaders[ctrl.GetRevisionHeaderName()] = "test-rev" - appendHeaders[ctrl.GetRevisionHeaderNamespace()] = testNamespace - appendHeaders["x-envoy-upstream-rq-timeout-ms"] = requestTimeoutMs - expectedRouteSpec := v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: fmt.Sprintf("%s-service", route.Name), - Namespace: testNamespace, + t.Fatalf("error getting VirtualService: %v", err) + } + + domain := strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, ".") + clusterDomain := "test-route-service.test.svc.cluster.local" + expectedSpec := v1alpha3.VirtualServiceSpec{ + // We want to connect to two Gateways: the Route's ingress + // Gateway, and the 'mesh' Gateway. The former provides + // access from outside of the cluster, and the latter provides + // access for services from inside the cluster. + Gateways: []string{ + ctrl.GetServingK8SGatewayFullname(), + "mesh", }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta( - strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, "."), - ), - }, - }, - }, + Hosts: []string{ + "*." + domain, + domain, + clusterDomain, }, - Route: []v1alpha2.DestinationWeight{{ - Destination: v1alpha2.IstioService{ - Name: fmt.Sprintf("%s-service", cfgrev.Name), - Namespace: testNamespace, + Http: []v1alpha3.HTTPRoute{{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: domain}, + }, { + Authority: &v1alpha3.StringMatch{Exact: clusterDomain}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s-service.%s.svc.cluster.local", cfgrev.Name, testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 90, + }, getActivatorDestinationWeight(10)}, + AppendHeaders: map[string]string{ + ctrl.GetRevisionHeaderName(): "test-rev", + ctrl.GetRevisionHeaderNamespace(): testNamespace, + istio.EnvoyTimeoutHeader: istio.DefaultEnvoyTimeoutMs, }, - Weight: 90, - }, getActivatorDestinationWeight(10)}, - AppendHeaders: appendHeaders, + }}, } - - if diff := cmp.Diff(expectedRouteSpec, routerule.Spec); diff != "" { + if diff := cmp.Diff(expectedSpec, vs.Spec); diff != "" { t.Errorf("Unexpected rule spec diff (-want +got): %v", diff) } @@ -700,56 +625,68 @@ func TestCreateRouteWithDuplicateTargets(t *testing.T) { controller.updateRouteEvent(KeyOrDie(route)) - routerule, err := servingClient.ConfigV1alpha2().RouteRules(testNamespace).Get(fmt.Sprintf("%s-istio", route.Name), metav1.GetOptions{}) + vs, err := servingClient.NetworkingV1alpha3().VirtualServices(testNamespace).Get(ctrl.GetVirtualServiceName(route), metav1.GetOptions{}) if err != nil { - t.Fatalf("error getting routerule: %v", err) - } - - expectedRouteSpec := v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: "test-route-service", - Namespace: testNamespace, + t.Fatalf("error getting VirtualService: %v", err) + } + + domain := strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, ".") + clusterDomain := "test-route-service.test.svc.cluster.local" + expectedSpec := v1alpha3.VirtualServiceSpec{ + // We want to connect to two Gateways: the Route's ingress + // Gateway, and the 'mesh' Gateway. The former provides + // access from outside of the cluster, and the latter provides + // access for services from inside the cluster. + Gateways: []string{ + ctrl.GetServingK8SGatewayFullname(), + "mesh", }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta( - strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, "."), - ), - }, - }, - }, + Hosts: []string{ + "*." + domain, + domain, + clusterDomain, }, - Route: []v1alpha2.DestinationWeight{{ - Destination: v1alpha2.IstioService{ - Name: "p-deadbeef-service", - Namespace: testNamespace, - }, - Weight: 50, - }, { - Destination: v1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 15, + Http: []v1alpha3.HTTPRoute{{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: domain}, + }, { + Authority: &v1alpha3.StringMatch{Exact: clusterDomain}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", "p-deadbeef-service", testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 50, + }, { + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", "test-rev-service", testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 50, + }}, }, { - Destination: v1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 20, + Match: []v1alpha3.HTTPMatchRequest{{Authority: &v1alpha3.StringMatch{Exact: "test-revision-1." + domain}}}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", "test-rev-service", testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, }, { - Destination: v1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 15, - }, - getActivatorDestinationWeight(0)}, + Match: []v1alpha3.HTTPMatchRequest{{Authority: &v1alpha3.StringMatch{Exact: "test-revision-2." + domain}}}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", "test-rev-service", testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, + }}, } - - if diff := cmp.Diff(expectedRouteSpec, routerule.Spec); diff != "" { + if diff := cmp.Diff(expectedSpec, vs.Spec); diff != "" { + fmt.Printf("%+v\n", vs.Spec) t.Errorf("Unexpected rule spec diff (-want +got): %v", diff) } } @@ -790,167 +727,68 @@ func TestCreateRouteWithNamedTargets(t *testing.T) { controller.updateRouteEvent(KeyOrDie(route)) - domain := fmt.Sprintf("%s.%s.%s", route.Name, route.Namespace, defaultDomainSuffix) - - expectRouteSpec := func(t *testing.T, name string, expectedSpec v1alpha2.RouteRuleSpec) { - routerule, err := servingClient.ConfigV1alpha2().RouteRules(testNamespace).Get(name, metav1.GetOptions{}) - if err != nil { - t.Fatalf("error getting routerule: %v", err) - } - if diff := cmp.Diff(expectedSpec, routerule.Spec); diff != "" { - t.Errorf("Unexpected routerule spec diff (-want +got): %v", diff) - } - } - - // Expects authority header to be the domain suffix - expectRouteSpec(t, "test-route-istio", v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: "test-route-service", - Namespace: testNamespace, - }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta(domain), - }, - }, - }, + vs, err := servingClient.NetworkingV1alpha3().VirtualServices(testNamespace).Get(ctrl.GetVirtualServiceName(route), metav1.GetOptions{}) + if err != nil { + t.Fatalf("error getting virtualservice: %v", err) + } + domain := strings.Join([]string{route.Name, route.Namespace, defaultDomainSuffix}, ".") + clusterDomain := "test-route-service.test.svc.cluster.local" + expectedSpec := v1alpha3.VirtualServiceSpec{ + // We want to connect to two Gateways: the Route's ingress + // Gateway, and the 'mesh' Gateway. The former provides + // access from outside of the cluster, and the latter provides + // access for services from inside the cluster. + Gateways: []string{ + ctrl.GetServingK8SGatewayFullname(), + "mesh", }, - Route: []v1alpha2.DestinationWeight{{ - Destination: v1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 50, - }, { - Destination: v1alpha2.IstioService{ - Name: "p-deadbeef-service", - Namespace: testNamespace, - }, - Weight: 50, - }, getActivatorDestinationWeight(0)}, - }) - - // Expects authority header to have the traffic target name prefixed to the - // domain suffix. Also weights 100% of the traffic to the specified traffic - // target's revision. - expectRouteSpec(t, "test-route-foo-istio", v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: "test-route-service", - Namespace: testNamespace, + Hosts: []string{ + "*." + domain, + domain, + clusterDomain, }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta( - strings.Join([]string{"foo", domain}, "."), - ), - }, + Http: []v1alpha3.HTTPRoute{{ + Match: []v1alpha3.HTTPMatchRequest{{ + Authority: &v1alpha3.StringMatch{Exact: domain}, + }, { + Authority: &v1alpha3.StringMatch{Exact: clusterDomain}, + }}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", "test-rev-service", testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, }, - }, - }, - Route: []v1alpha2.DestinationWeight{{ - Destination: v1alpha2.IstioService{ - Name: "test-rev-service", - Namespace: testNamespace, - }, - Weight: 100, - }}, - }) - - // Expects authority header to have the traffic target name prefixed to the - // domain suffix. Also weights 100% of the traffic to the specified traffic - // target's revision. - expectRouteSpec(t, "test-route-bar-istio", v1alpha2.RouteRuleSpec{ - Destination: v1alpha2.IstioService{ - Name: "test-route-service", - Namespace: testNamespace, - }, - Match: v1alpha2.Match{ - Request: v1alpha2.MatchRequest{ - Headers: v1alpha2.Headers{ - Authority: v1alpha2.MatchString{ - Regex: regexp.QuoteMeta( - strings.Join([]string{"bar", domain}, "."), - ), - }, + Weight: 50, + }, { + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", "p-deadbeef-service", testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, }, - }, - }, - Route: []v1alpha2.DestinationWeight{{ - Destination: v1alpha2.IstioService{ - Name: "p-deadbeef-service", - Namespace: testNamespace, - }, - Weight: 100, - }}, - }) -} - -func TestCreateRouteDeletesOutdatedRouteRules(t *testing.T) { - _, servingClient, controller, _, _, _ := newTestController() - config := getTestConfiguration() - rev := getTestRevisionForConfig(config) - route := getTestRouteWithTrafficTargets( - []v1alpha1.TrafficTarget{{ - ConfigurationName: config.Name, - Percent: 50, + Weight: 50, + }}, }, { - ConfigurationName: config.Name, - Percent: 100, - Name: "foo", + Match: []v1alpha3.HTTPMatchRequest{{Authority: &v1alpha3.StringMatch{Exact: "bar." + domain}}}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", "p-deadbeef-service", testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, + }, { + Match: []v1alpha3.HTTPMatchRequest{{Authority: &v1alpha3.StringMatch{Exact: "foo." + domain}}}, + Route: []v1alpha3.DestinationWeight{{ + Destination: v1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", "test-rev-service", testNamespace), + Port: v1alpha3.PortSelector{Number: 80}, + }, + Weight: 100, + }}, }}, - ) - extraRouteRule := &v1alpha2.RouteRule{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-route-extra-istio", - Namespace: route.Namespace, - Labels: map[string]string{ - "route": route.Name, - }, - }, } - - // A route rule without the expected serving route label. - independentRouteRule := &v1alpha2.RouteRule{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-route-independent-istio", - Namespace: route.Namespace, - }, - } - - config.Status.LatestCreatedRevisionName = rev.Name - config.Labels = map[string]string{serving.RouteLabelKey: route.Name} - - servingClient.ServingV1alpha1().Configurations("test").Create(config) - servingClient.ServingV1alpha1().Revisions("test").Create(rev) - servingClient.ConfigV1alpha2().RouteRules(route.Namespace).Create(extraRouteRule) - servingClient.ConfigV1alpha2().RouteRules(route.Namespace).Create(independentRouteRule) - - // Ensure extraRouteRule was created - if _, err := servingClient.ConfigV1alpha2().RouteRules(route.Namespace).Get(extraRouteRule.Name, metav1.GetOptions{}); err != nil { - t.Errorf("Unexpected error occured. Expected route rule %s to exist.", extraRouteRule.Name) - } - if _, err := servingClient.ConfigV1alpha2().RouteRules(route.Namespace).Get(independentRouteRule.Name, metav1.GetOptions{}); err != nil { - t.Errorf("Unexpected error occured. Expected route rule %s to exist.", independentRouteRule.Name) - } - servingClient.ServingV1alpha1().Routes("test").Create(route) - - if err := controller.removeOutdatedRouteRules(testCtx, route); err != nil { - t.Errorf("Unexpected error occurred removing outdated route rules: %s", err) - } - - expectedErrMsg := fmt.Sprintf("routerules.config.istio.io \"%s\" not found", extraRouteRule.Name) - // expect extraRouteRule to have been deleted - _, err := servingClient.ConfigV1alpha2().RouteRules(route.Namespace).Get(extraRouteRule.Name, metav1.GetOptions{}) - if wanted, got := expectedErrMsg, err.Error(); wanted != got { - t.Errorf("Unexpected error: %q expected: %q", got, wanted) - } - // expect independentRouteRule not to have been deleted - if _, err := servingClient.ConfigV1alpha2().RouteRules(route.Namespace).Get(independentRouteRule.Name, metav1.GetOptions{}); err != nil { - t.Errorf("Error occurred fetching route rule: %s. Expected route rule to exist.", independentRouteRule.Name) + if diff := cmp.Diff(expectedSpec, vs.Spec); diff != "" { + fmt.Printf("%+v\n", vs.Spec) + t.Errorf("Unexpected rule spec diff (-want +got): %v", diff) } } @@ -1184,23 +1022,12 @@ func TestDeleteLabelOfConfigurationWhenUnconfigured(t *testing.T) { if err != nil { t.Fatalf("error getting revision: %v", err) } - - expectedLabels = map[string]string{ - serving.ConfigurationLabelKey: config.Name, - } - - // Check labels in revision, should be empty. - if diff := cmp.Diff(expectedLabels, rev.Labels); diff != "" { - t.Errorf("Unexpected label in revision diff (-want +got): %v", diff) - } - } func TestUpdateRouteDomainWhenRouteLabelChanges(t *testing.T) { - kubeClient, servingClient, controller, _, servingInformer, _ := newTestController() + _, servingClient, controller, _, servingInformer, _ := newTestController() route := getTestRouteWithTrafficTargets([]v1alpha1.TrafficTarget{}) routeClient := servingClient.ServingV1alpha1().Routes(route.Namespace) - ingressClient := kubeClient.ExtensionsV1beta1().Ingresses(route.Namespace) // Create a route. servingInformer.Serving().V1alpha1().Routes().Informer().GetIndexer().Add(route) @@ -1230,18 +1057,6 @@ func TestUpdateRouteDomainWhenRouteLabelChanges(t *testing.T) { if route.Status.Domain != expectedDomain { t.Errorf("For labels %v, expected domain %q but saw %q", expectation.labels, expectedDomain, route.Status.Domain) } - - // Confirms that the ingress is updated in tandem with the route. - ingress, _ := ingressClient.Get(ctrl.GetServingK8SIngressName(route), metav1.GetOptions{}) - - expectedHost := route.Status.Domain - expectedWildcardHost := fmt.Sprintf("*.%s", route.Status.Domain) - if ingress.Spec.Rules[0].Host != expectedHost { - t.Errorf("For labels %v, expected ingress host %q but saw %q", expectation.labels, expectedHost, ingress.Spec.Rules[0].Host) - } - if ingress.Spec.Rules[1].Host != expectedWildcardHost { - t.Errorf("For labels %v, expected ingress host %q but saw %q", expectation.labels, expectedWildcardHost, ingress.Spec.Rules[1].Host) - } } } @@ -1299,15 +1114,12 @@ func TestUpdateRouteWhenConfigurationChanges(t *testing.T) { if err != nil { t.Fatalf("Couldn't get route: %v", err) } - // Now the configuration has a LatestReadyRevisionName, so its revision should // be targeted expectedTrafficTargets = []v1alpha1.TrafficTarget{{ - RevisionName: rev.Name, - Percent: 100, - }, { - Name: ctrl.GetServingK8SActivatorServiceName(), - Percent: 0, + ConfigurationName: config.Name, + RevisionName: rev.Name, + Percent: 100, }} if diff := cmp.Diff(expectedTrafficTargets, route.Status.Traffic); diff != "" { t.Errorf("Unexpected traffic target diff (-want +got): %v", diff) @@ -1358,72 +1170,10 @@ func TestAddConfigurationEventNotUpdateAnythingIfHasNoLatestReady(t *testing.T) controller.SyncConfiguration(config) } -// Test route when we do not use activator, and then use activator. -func TestUpdateIngressEventUpdateRouteStatus(t *testing.T) { - kubeClient, servingClient, controller, _, servingInformer, _ := newTestController() - - // A standalone revision - rev := getTestRevision("test-rev") - servingClient.ServingV1alpha1().Revisions(testNamespace).Create(rev) - - route := getTestRouteWithTrafficTargets( - []v1alpha1.TrafficTarget{{ - RevisionName: rev.Name, - Percent: 100, - }}, - ) - // Create a route. - routeClient := servingClient.ServingV1alpha1().Routes(route.Namespace) - routeClient.Create(route) - // Since updateRouteEvent looks in the lister, we need to add it to the informer - servingInformer.Serving().V1alpha1().Routes().Informer().GetIndexer().Add(route) - - controller.updateRouteEvent(KeyOrDie(route)) - - // Before ingress has an IP address, route isn't marked as Ready. - ingressClient := kubeClient.ExtensionsV1beta1().Ingresses(route.Namespace) - ingress, _ := ingressClient.Get(ctrl.GetServingK8SIngressName(route), metav1.GetOptions{}) - controller.SyncIngress(ingress) - - newRoute, _ := routeClient.Get(route.Name, metav1.GetOptions{}) - for _, ct := range []v1alpha1.RouteConditionType{"Ready"} { - got := newRoute.Status.GetCondition(ct) - want := &v1alpha1.RouteCondition{ - Type: ct, - Status: corev1.ConditionUnknown, - LastTransitionTime: got.LastTransitionTime, - } - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("Unexpected config conditions diff (-want +got): %v", diff) - } - } - - // Update the Ingress IP. - ingress.Status.LoadBalancer.Ingress = []corev1.LoadBalancerIngress{{ - IP: "127.0.0.1", - }} - controller.SyncIngress(ingress) - - // Verify now that Route.Status.Conditions is set correctly. - newRoute, _ = routeClient.Get(route.Name, metav1.GetOptions{}) - for _, ct := range []v1alpha1.RouteConditionType{"Ready"} { - got := newRoute.Status.GetCondition(ct) - want := &v1alpha1.RouteCondition{ - Type: ct, - Status: corev1.ConditionTrue, - LastTransitionTime: got.LastTransitionTime, - } - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("Unexpected config conditions diff (-want +got): %v", diff) - } - } -} - func TestUpdateDomainConfigMap(t *testing.T) { - kubeClient, servingClient, controller, _, servingInformer, _ := newTestController() + _, servingClient, controller, _, servingInformer, _ := newTestController() route := getTestRouteWithTrafficTargets([]v1alpha1.TrafficTarget{}) routeClient := servingClient.ServingV1alpha1().Routes(route.Namespace) - ingressClient := kubeClient.Extensions().Ingresses(route.Namespace) // Create a route. servingInformer.Serving().V1alpha1().Routes().Informer().GetIndexer().Add(route) @@ -1498,14 +1248,5 @@ func TestUpdateDomainConfigMap(t *testing.T) { if route.Status.Domain != expectedDomain { t.Errorf("Expected domain %q but saw %q", expectedDomain, route.Status.Domain) } - ingress, _ := ingressClient.Get(ctrl.GetServingK8SIngressName(route), metav1.GetOptions{}) - expectedHost := route.Status.Domain - expectedWildcardHost := fmt.Sprintf("*.%s", route.Status.Domain) - if ingress.Spec.Rules[0].Host != expectedHost { - t.Errorf("Expected ingress host %q but saw %q", expectedHost, ingress.Spec.Rules[0].Host) - } - if ingress.Spec.Rules[1].Host != expectedWildcardHost { - t.Errorf("Expected ingress host %q but saw %q", expectedWildcardHost, ingress.Spec.Rules[1].Host) - } } } diff --git a/pkg/controller/route/traffic/doc.go b/pkg/controller/route/traffic/doc.go new file mode 100644 index 000000000000..2e8a65389f4e --- /dev/null +++ b/pkg/controller/route/traffic/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 2018 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 + + https://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. +*/ + +/* +This package contains code that translate our TrafficTarget to an +intermediate format that has less semantic. In particular it deals +with flattening the TrafficTarget to the revision level -- it also +does the grouping of traffic target into their traffic groups. +*/ + +package traffic diff --git a/pkg/controller/route/traffic/traffic.go b/pkg/controller/route/traffic/traffic.go new file mode 100644 index 000000000000..b54da337703a --- /dev/null +++ b/pkg/controller/route/traffic/traffic.go @@ -0,0 +1,227 @@ +/* +Copyright 2018 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 + + https://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 traffic + +import ( + "fmt" + + "github.com/knative/serving/pkg/apis/serving" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + client "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// A RevisionTarget adds the Active/Inactive state of a Revision to a flattened TrafficTarget. +type RevisionTarget struct { + v1alpha1.TrafficTarget + Active bool +} + +// TrafficConfig encapsulates details of our traffic so that we don't need to make API calls, or use details of the +// route beyond its ObjectMeta to make routing changes. +type TrafficConfig struct { + // The traffic targets, flattened to the Revision-level. + Targets map[string][]RevisionTarget + + // The referred Configurations and Revisions. + Configurations map[string]*v1alpha1.Configuration + Revisions map[string]*v1alpha1.Revision +} + +// BuildTrafficConfiguration consolidates and flattens the Route.Spec.Traffic to the Revision-level. It also provides a +// complete lists of Configurations and Revisions referred by the Route, directly or indirectly. These referred targets +// are keyed by name for easy access. +// +// In the case that some target is missing, it sets the Route.Status to TrafficNotAssigned with a reference to the +// missing target, and return one of the error. However, it still returns a complete list of referred existing targets. +func BuildTrafficConfiguration(configClient client.ConfigurationInterface, revClient client.RevisionInterface, u *v1alpha1.Route) (*TrafficConfig, error) { + builder := newBuilder(configClient, revClient) + var lastErr error + for _, tt := range u.Spec.Traffic { + if tt.RevisionName != "" { + // We don't fail fast because we want to compile a list of + // all referred target even in the case of missing + // targets. + if err := builder.addRevisionTarget(&tt); err != nil { + u.Status.MarkTrafficNotAssigned("Revision", tt.RevisionName) + lastErr = err + } + } else if tt.ConfigurationName != "" { + // We don't fail fast because we want to compile a list of + // all referred target even in the case of missing + // targets. + if err := builder.addConfigurationTarget(&tt); err != nil { + u.Status.MarkTrafficNotAssigned("Configuration", tt.ConfigurationName) + lastErr = err + } + } + } + if lastErr != nil { + builder.targets = nil + } + // We still need to return all the referred targets, even if there + // are some missing targets. + return builder.build(), lastErr +} + +// GetTrafficTargets returns a list of TrafficTarget. +func (t *TrafficConfig) GetTrafficTargets() []v1alpha1.TrafficTarget { + results := []v1alpha1.TrafficTarget{} + for _, tt := range t.Targets[""] { + results = append(results, tt.TrafficTarget) + } + return results +} + +type trafficConfigBuilder struct { + configClient client.ConfigurationInterface + revClient client.RevisionInterface + + // targets is a grouping of traffic targets serving the same origin. + targets map[string][]RevisionTarget + // configurations contains all the referred Configuration, keyed by their name. + configurations map[string]*v1alpha1.Configuration + // revisions contains all the referred Revision, keyed by their name. + revisions map[string]*v1alpha1.Revision +} + +func newBuilder(configClient client.ConfigurationInterface, revClient client.RevisionInterface) *trafficConfigBuilder { + return &trafficConfigBuilder{ + configClient: configClient, + revClient: revClient, + targets: make(map[string][]RevisionTarget), + + configurations: make(map[string]*v1alpha1.Configuration), + revisions: make(map[string]*v1alpha1.Revision), + } +} + +func (t *trafficConfigBuilder) getConfiguration(name string) (*v1alpha1.Configuration, error) { + if _, ok := t.configurations[name]; !ok { + config, err := t.configClient.Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + t.configurations[name] = config + } + return t.configurations[name], nil +} + +func (t *trafficConfigBuilder) getRevision(name string) (*v1alpha1.Revision, error) { + if _, ok := t.revisions[name]; !ok { + rev, err := t.revClient.Get(name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + t.revisions[name] = rev + } + return t.revisions[name], nil +} + +// addConfigurationTarget flattens a traffic target to the Revision level, by looking up for the LatestReadyRevisionName +// on the referred Configuration. It adds both to the lists of directly referred targets. +func (t *trafficConfigBuilder) addConfigurationTarget(tt *v1alpha1.TrafficTarget) error { + config, err := t.getConfiguration(tt.ConfigurationName) + if err != nil { + return err + } + if config.Status.LatestReadyRevisionName == "" { + return fmt.Errorf("Configuration %q is not ready", config.Name) + } + rev, err := t.getRevision(config.Status.LatestReadyRevisionName) + if err != nil { + return err + } + target := RevisionTarget{ + TrafficTarget: *tt, + Active: !rev.Status.IsActivationRequired(), + } + target.TrafficTarget.RevisionName = rev.Name + t.addTarget(target) + return nil +} + +func (t *trafficConfigBuilder) addRevisionTarget(tt *v1alpha1.TrafficTarget) error { + rev, err := t.getRevision(tt.RevisionName) + if err != nil { + return err + } + if !rev.Status.IsRoutable() { + return fmt.Errorf("Revision %q is not routable", rev.Name) + } + target := RevisionTarget{ + TrafficTarget: *tt, + Active: !rev.Status.IsActivationRequired(), + } + t.revisions[tt.RevisionName] = rev + if configName, ok := rev.Labels[serving.ConfigurationLabelKey]; ok { + target.TrafficTarget.ConfigurationName = configName + if _, err := t.getConfiguration(configName); err != nil { + return err + } + } + t.addTarget(target) + return nil +} + +func (t *trafficConfigBuilder) addTarget(target RevisionTarget) { + name := target.TrafficTarget.Name + t.targets[""] = append(t.targets[""], target) + if name != "" { + t.targets[name] = append(t.targets[name], target) + } +} + +func consolidate(targets []RevisionTarget) []RevisionTarget { + byName := make(map[string]RevisionTarget) + names := []string{} + for _, tt := range targets { + name := tt.TrafficTarget.RevisionName + cur, ok := byName[name] + if !ok { + byName[name] = tt + names = append(names, name) + } else { + cur.TrafficTarget.Percent += tt.TrafficTarget.Percent + byName[name] = cur + } + } + consolidated := []RevisionTarget{} + for _, name := range names { + consolidated = append(consolidated, byName[name]) + } + if len(consolidated) == 1 { + consolidated[0].TrafficTarget.Percent = 100 + } + return consolidated +} + +func consolidateAll(targets map[string][]RevisionTarget) map[string][]RevisionTarget { + consolidated := make(map[string][]RevisionTarget) + for name, tts := range targets { + consolidated[name] = consolidate(tts) + } + return consolidated +} + +func (t *trafficConfigBuilder) build() *TrafficConfig { + return &TrafficConfig{ + Targets: consolidateAll(t.targets), + Configurations: t.configurations, + Revisions: t.revisions, + } +} diff --git a/pkg/controller/route/traffic/traffic_test.go b/pkg/controller/route/traffic/traffic_test.go new file mode 100644 index 000000000000..bc8b9427ceb9 --- /dev/null +++ b/pkg/controller/route/traffic/traffic_test.go @@ -0,0 +1,561 @@ +/* +Copyright 2018 The Knative Author + +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 + + https://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 traffic + +import ( + "context" + "fmt" + "os" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/knative/serving/pkg/apis/serving" + "github.com/knative/serving/pkg/apis/serving/v1alpha1" + fakeclientset "github.com/knative/serving/pkg/client/clientset/versioned/fake" + clientv1alpha1 "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1" + "github.com/knative/serving/pkg/logging" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + testNamespace string = "test" + defaultDomainSuffix string = "test-domain.dev" + prodDomainSuffix string = "prod-domain.com" +) + +var ( + testLogger = zap.NewNop().Sugar() + testCtx = logging.WithLogger(context.Background(), testLogger) +) + +// A simple fixed Configuration/Revision layout for testing. +var ( + // These are objects never inserted. + missingConfig *v1alpha1.Configuration + missingRev *v1alpha1.Revision + + // unreadyConfig only has unreadyRevision, and it's not ready. + unreadyConfig *v1alpha1.Configuration + unreadyRev *v1alpha1.Revision + + // inactiveConfig only has inactiveRevision, and it's not active. + inactiveConfig *v1alpha1.Configuration + inactiveRev *v1alpha1.Revision + + // goodConfig has two good revisions: goodOldRev and goodNewRev + goodConfig *v1alpha1.Configuration + goodOldRev *v1alpha1.Revision + goodNewRev *v1alpha1.Revision + + // niceConfig has two good revisions: niceOldRev and niceNewRev + niceConfig *v1alpha1.Configuration + niceOldRev *v1alpha1.Revision + niceNewRev *v1alpha1.Revision + + configClient clientv1alpha1.ConfigurationInterface + revClient clientv1alpha1.RevisionInterface +) + +func setUp() { + unreadyConfig, unreadyRev = getTestUnreadyConfig("unready") + inactiveConfig, inactiveRev = getTestInactiveConfig("inactive") + goodConfig, goodOldRev, goodNewRev = getTestReadyConfig("good") + niceConfig, niceOldRev, niceNewRev = getTestReadyConfig("nice") + elaClient := fakeclientset.NewSimpleClientset( + unreadyConfig, unreadyRev, inactiveConfig, inactiveRev, goodConfig, goodOldRev, goodNewRev, niceConfig, niceOldRev, niceNewRev) + configClient = elaClient.ServingV1alpha1().Configurations(testNamespace) + revClient = elaClient.ServingV1alpha1().Revisions(testNamespace) + missingConfig, missingRev = getTestUnreadyConfig("missing") +} + +// The vanilla use case of 100% directing to latest ready revision of a single configuration. +func TestBuildTrafficConfiguration_Vanilla(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + ConfigurationName: goodConfig.Name, + Percent: 100, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + Percent: 100, + }, + Active: true, + }}, + }, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig}, + Revisions: map[string]*v1alpha1.Revision{goodNewRev.Name: goodNewRev}, + } + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error %v", err) + } else if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +// The vanilla use case of 100% directing to latest revision of an inactive configuration. +func TestBuildTrafficConfiguration_VanillaScaledToZero(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + ConfigurationName: inactiveConfig.Name, + Percent: 100, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: inactiveConfig.Name, + RevisionName: inactiveRev.Name, + Percent: 100, + }, + Active: false, + }}, + }, + Configurations: map[string]*v1alpha1.Configuration{inactiveConfig.Name: inactiveConfig}, + Revisions: map[string]*v1alpha1.Revision{inactiveRev.Name: inactiveRev}, + } + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error: %v", err) + } else if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +// Transitioning from one good config to another by splitting traffic. +func TestBuildTrafficConfiguration_TwoConfigs(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + ConfigurationName: niceConfig.Name, + Percent: 90, + }, { + ConfigurationName: goodConfig.Name, + Percent: 10, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: niceConfig.Name, + RevisionName: niceNewRev.Name, + Percent: 90, + }, + Active: true}, { + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + Percent: 10, + }, + Active: true, + }}, + }, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig, niceConfig.Name: niceConfig}, + Revisions: map[string]*v1alpha1.Revision{goodNewRev.Name: goodNewRev, niceNewRev.Name: niceNewRev}, + } + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error %v", err) + } else if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +// Splitting traffic between a fixed revision and the latest revision (canary). +func TestBuildTrafficConfiguration_Canary(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + RevisionName: goodOldRev.Name, + Percent: 90, + }, { + ConfigurationName: goodConfig.Name, + Percent: 10, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodOldRev.Name, + Percent: 90, + }, + Active: true, + }, { + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + Percent: 10, + }, + Active: true, + }}, + }, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig}, + Revisions: map[string]*v1alpha1.Revision{goodOldRev.Name: goodOldRev, goodNewRev.Name: goodNewRev}, + } + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error %v", err) + } else if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +// Splitting traffic between latest revision and a fixed revision which is also latest. +func TestBuildTrafficConfiguration_Consolidated(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + RevisionName: goodNewRev.Name, + Percent: 90, + }, { + ConfigurationName: goodConfig.Name, + Percent: 10, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + Percent: 100, + }, + Active: true, + }}, + }, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig}, + Revisions: map[string]*v1alpha1.Revision{goodNewRev.Name: goodNewRev}, + } + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error %v", err) + } else if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +// Splitting traffic between a two fixed revisions. +func TestBuildTrafficConfiguration_TwoFixedRevisions(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + RevisionName: goodOldRev.Name, + Percent: 90, + }, { + RevisionName: goodNewRev.Name, + Percent: 10, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodOldRev.Name, + Percent: 90, + }, + Active: true, + }, { + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + Percent: 10, + }, + Active: true, + }}, + }, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig}, + Revisions: map[string]*v1alpha1.Revision{goodNewRev.Name: goodNewRev, goodOldRev.Name: goodOldRev}, + } + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error %v", err) + } else if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +// Splitting traffic between a two fixed revisions of two configurations. +func TestBuildTrafficConfiguration_TwoFixedRevisionsFromTwoConfigurations(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + RevisionName: goodNewRev.Name, + Percent: 40, + }, { + RevisionName: niceNewRev.Name, + Percent: 60, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + Percent: 40, + }, + Active: true, + }, { + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: niceConfig.Name, + RevisionName: niceNewRev.Name, + Percent: 60, + }, + Active: true, + }}, + }, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig, niceConfig.Name: niceConfig}, + Revisions: map[string]*v1alpha1.Revision{goodNewRev.Name: goodNewRev, niceNewRev.Name: niceNewRev}, + } + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error %v", err) + } else if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +// One fixed, two named targets for newer stuffs. +func TestBuildTrafficConfiguration_Preliminary(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + RevisionName: goodOldRev.Name, + Percent: 100, + }, { + Name: "beta", + RevisionName: goodNewRev.Name, + }, { + Name: "alpha", + ConfigurationName: niceConfig.Name, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{ + "": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + ConfigurationName: goodConfig.Name, + RevisionName: goodOldRev.Name, + Percent: 100, + }, + Active: true}, { + TrafficTarget: v1alpha1.TrafficTarget{ + Name: "beta", + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + }, + Active: true}, { + TrafficTarget: v1alpha1.TrafficTarget{ + Name: "alpha", + ConfigurationName: niceConfig.Name, + RevisionName: niceNewRev.Name, + }, + Active: true, + }}, + "beta": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + Name: "beta", + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + Percent: 100, + }, + Active: true}}, + "alpha": {{ + TrafficTarget: v1alpha1.TrafficTarget{ + Name: "alpha", + ConfigurationName: niceConfig.Name, + RevisionName: niceNewRev.Name, + Percent: 100, + }, + Active: true, + }}, + }, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig, niceConfig.Name: niceConfig}, + Revisions: map[string]*v1alpha1.Revision{goodOldRev.Name: goodOldRev, goodNewRev.Name: goodNewRev, niceNewRev.Name: niceNewRev}, + } + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error %v", err) + } else if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +func TestBuildTrafficConfiguration_MissingConfig(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + RevisionName: goodOldRev.Name, + Percent: 100, + }, { + Name: "beta", + RevisionName: goodNewRev.Name, + }, { + Name: "alpha", + ConfigurationName: missingConfig.Name, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{}, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig}, + Revisions: map[string]*v1alpha1.Revision{goodOldRev.Name: goodOldRev, goodNewRev.Name: goodNewRev}, + } + r := getTestRouteWithTrafficTargets(tts) + tc, err := BuildTrafficConfiguration(configClient, revClient, r) + if err == nil || !apierrs.IsNotFound(err) { + t.Errorf("Expected not found, saw %v", err) + } + if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } + if c := r.Status.GetCondition(v1alpha1.RouteConditionAllTrafficAssigned); c == nil || c.Status != corev1.ConditionFalse { + t.Errorf("Expected ConditionFalse, saw %v", c) + } else { + if c.Reason != "ConfigurationMissing" { + t.Errorf("Expected ConfigurationMissing, saw %q", c.Reason) + } + if !strings.Contains(c.Message, missingConfig.Name) { + t.Errorf("Expected to see %q in message, saw %q", missingConfig.Name, c.Message) + } + } +} + +func TestBuildTrafficConfiguration_MissingRevision(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + RevisionName: missingRev.Name, + Percent: 50, + }, { + RevisionName: goodNewRev.Name, + Percent: 50, + }} + expected := &TrafficConfig{ + Targets: map[string][]RevisionTarget{}, + Configurations: map[string]*v1alpha1.Configuration{goodConfig.Name: goodConfig}, + Revisions: map[string]*v1alpha1.Revision{goodNewRev.Name: goodNewRev}, + } + r := getTestRouteWithTrafficTargets(tts) + tc, err := BuildTrafficConfiguration(configClient, revClient, r) + if err == nil || !apierrs.IsNotFound(err) { + t.Errorf("Expected not found, saw %v", err) + } + if diff := cmp.Diff(expected, tc); diff != "" { + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } + if c := r.Status.GetCondition(v1alpha1.RouteConditionAllTrafficAssigned); c == nil || c.Status != corev1.ConditionFalse { + t.Errorf("Expected ConditionFalse, saw %v", c) + } else { + if c.Reason != "RevisionMissing" { + t.Errorf("Expected ConfigurationMissing, saw %v", c.Reason) + } + if !strings.Contains(c.Message, missingRev.Name) { + t.Errorf("Expected to see %q in message, saw %q", missingRev.Name, c.Message) + } + } +} + +func TestRoundTripping(t *testing.T) { + tts := []v1alpha1.TrafficTarget{{ + RevisionName: goodOldRev.Name, + Percent: 100, + }, { + Name: "beta", + RevisionName: goodNewRev.Name, + }, { + Name: "alpha", + ConfigurationName: niceConfig.Name, + }} + expected := []v1alpha1.TrafficTarget{{ + ConfigurationName: goodConfig.Name, + RevisionName: goodOldRev.Name, + Percent: 100, + }, { + Name: "beta", + ConfigurationName: goodConfig.Name, + RevisionName: goodNewRev.Name, + }, { + Name: "alpha", + ConfigurationName: niceConfig.Name, + RevisionName: niceNewRev.Name, + }} + if tc, err := BuildTrafficConfiguration(configClient, revClient, getTestRouteWithTrafficTargets(tts)); err != nil { + t.Errorf("Unexpected error %v", err) + } else if diff := cmp.Diff(expected, tc.GetTrafficTargets()); diff != "" { + fmt.Printf("%+v\n", tc.GetTrafficTargets()) + t.Errorf("Unexpected traffic diff (-want +got): %v", diff) + } +} + +func getTestConfig(name string) *v1alpha1.Configuration { + return &v1alpha1.Configuration{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Spec: v1alpha1.ConfigurationSpec{ + // This is a workaround for generation initialization + Generation: 1, + RevisionTemplate: v1alpha1.RevisionTemplateSpec{ + Spec: v1alpha1.RevisionSpec{ + Container: corev1.Container{ + Image: "test-image", + }, + }, + }, + }, + } +} + +func getTestRevForConfig(config *v1alpha1.Configuration, name string) *v1alpha1.Revision { + return &v1alpha1.Revision{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + Labels: map[string]string{ + serving.ConfigurationLabelKey: config.Name, + }, + }, + Spec: *config.Spec.RevisionTemplate.Spec.DeepCopy(), + } +} + +func getTestRouteWithTrafficTargets(traffic []v1alpha1.TrafficTarget) *v1alpha1.Route { + return &v1alpha1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-route", + Namespace: testNamespace, + Labels: map[string]string{ + "route": "test-route", + }, + }, + Spec: v1alpha1.RouteSpec{ + Traffic: traffic, + }, + } +} + +func getTestUnreadyConfig(name string) (*v1alpha1.Configuration, *v1alpha1.Revision) { + config := getTestConfig(name + "-config") + rev := getTestRevForConfig(config, name+"-revision") + config.Status.SetLatestCreatedRevisionName(rev.Name) + return config, rev +} + +func getTestInactiveConfig(name string) (*v1alpha1.Configuration, *v1alpha1.Revision) { + config := getTestConfig(name + "-config") + rev := getTestRevForConfig(config, name+"-revision") + config.Status.SetLatestReadyRevisionName(rev.Name) + rev.Status.MarkInactive() + return config, rev +} + +func getTestReadyConfig(name string) (*v1alpha1.Configuration, *v1alpha1.Revision, *v1alpha1.Revision) { + config := getTestConfig(name + "-config") + rev1 := getTestRevForConfig(config, name+"-revision-1") + rev1.Status.MarkResourcesAvailable() + rev1.Status.MarkContainerHealthy() + rev2 := getTestRevForConfig(config, name+"-revision-2") + rev2.Status.MarkResourcesAvailable() + rev2.Status.MarkContainerHealthy() + config.Status.SetLatestReadyRevisionName(rev2.Name) + return config, rev1, rev2 +} + +func TestMain(m *testing.M) { + setUp() + code := m.Run() + os.Exit(code) +} diff --git a/sample/autoscale/README.md b/sample/autoscale/README.md index e822f7038067..f5d0e56edc79 100644 --- a/sample/autoscale/README.md +++ b/sample/autoscale/README.md @@ -36,7 +36,7 @@ Export your Ingress IP as SERVICE_IP. export SERVICE_HOST=`kubectl get route autoscale-route -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -export SERVICE_IP=`kubectl get ingress autoscale-route-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` ``` Request the largest prime less than 40,000,000 from the autoscale app. Note that it consumes about 1 cpu/sec. diff --git a/sample/buildpack-app/README.md b/sample/buildpack-app/README.md index 67f8f25764c0..c049015151a6 100644 --- a/sample/buildpack-app/README.md +++ b/sample/buildpack-app/README.md @@ -67,7 +67,7 @@ Once the `ADDRESS` gets assigned to the cluster, you can run: export SERVICE_HOST=`kubectl get route buildpack-sample-app -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -$ export SERVICE_IP=`kubectl get ingress buildpack-sample-app-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` # Curl the Ingress IP "as-if" DNS were properly configured. $ curl --header "Host: $SERVICE_HOST" http://${SERVICE_IP}/ diff --git a/sample/buildpack-function/README.md b/sample/buildpack-function/README.md index c9ea941363e4..456294405c51 100644 --- a/sample/buildpack-function/README.md +++ b/sample/buildpack-function/README.md @@ -66,7 +66,8 @@ Once the `ADDRESS` gets assigned to the cluster, you can run: $ export SERVICE_HOST=`kubectl get route buildpack-function -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -$ export SERVICE_IP=`kubectl get ingress buildpack-function-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +$ export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` + # Curl the Ingress IP "as-if" DNS were properly configured. $ curl http://${SERVICE_IP}/ -H "Host: $SERVICE_HOST" -H "Content-Type: application/json" -d "33" diff --git a/sample/grpc-ping/README.md b/sample/grpc-ping/README.md index 33ab2b783b7f..a2b9b0037a80 100644 --- a/sample/grpc-ping/README.md +++ b/sample/grpc-ping/README.md @@ -41,7 +41,7 @@ kubectl apply -f sample/grpc-ping/sample.yaml export SERVICE_HOST=`kubectl get route grpc-ping -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -export SERVICE_IP=`kubectl get ingress grpc-ping-knative-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` ``` 1. Use the client to send message streams to the gRPC server diff --git a/sample/helloworld/README.md b/sample/helloworld/README.md index 682d324bdd23..894d7ac3a323 100644 --- a/sample/helloworld/README.md +++ b/sample/helloworld/README.md @@ -69,14 +69,14 @@ Once the `ADDRESS` gets assigned to the cluster, you can run: export SERVICE_HOST=`kubectl get route route-example -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -export SERVICE_IP=`kubectl get ingress route-example-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` ``` If your cluster is running outside a cloud provider (for example on Minikube), your ingress will never get an address. In that case, use the istio `hostIP` and `nodePort` as the service IP: ```shell -export SERVICE_IP=$(kubectl get po -l istio=ingress -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc istio-ingress -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') +export SERVICE_IP=$(kubectl get po -l knative=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') ``` Now curl the service IP as if DNS were properly configured: diff --git a/sample/private-repos/README.md b/sample/private-repos/README.md index 6481305908de..0427a6619a29 100644 --- a/sample/private-repos/README.md +++ b/sample/private-repos/README.md @@ -161,15 +161,14 @@ of the ingress endpoint: export SERVICE_HOST=`kubectl get route private-repos \ -o jsonpath="{.status.domain}"` -export SERVICE_IP=`kubectl get ing private-repos-ingress \ - -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` ``` If your cluster is running outside a cloud provider (for example on Minikube), your ingress will never get an address. In that case, use the istio `hostIP` and `nodePort` as the service IP: ```shell -export SERVICE_IP=$(kubectl get po -l istio=ingress -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc istio-ingress -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') +export SERVICE_IP=$(kubectl get po -l knative=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') ``` Now curl the service IP as if DNS were properly configured: diff --git a/sample/pythonsimple/README.md b/sample/pythonsimple/README.md index 888394d14718..6c465bf06840 100644 --- a/sample/pythonsimple/README.md +++ b/sample/pythonsimple/README.md @@ -66,14 +66,14 @@ Once the `ADDRESS` gets assigned to the cluster, you can run: export SERVICE_HOST=`kubectl get route route-python-example -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -export SERVICE_IP=`kubectl get ingress route-python-example-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` ``` If your cluster is running outside a cloud provider (for example on Minikube), your ingress will never get an address. In that case, use the istio `hostIP` and `nodePort` as the service IP: ```shell -export SERVICE_IP=$(kubectl get po -l istio=ingress -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc istio-ingress -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') +export SERVICE_IP=$(kubectl get po -l knative=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') ``` Now curl the service IP as if DNS were properly configured: diff --git a/sample/service/README.md b/sample/service/README.md index 40e5d050f4eb..3e00e7f93c5e 100644 --- a/sample/service/README.md +++ b/sample/service/README.md @@ -74,14 +74,14 @@ Once the `ADDRESS` gets assigned to the cluster, you can run: export SERVICE_HOST=`kubectl get route service-example -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -export SERVICE_IP=`kubectl get ingress service-example-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` ``` If your cluster is running outside a cloud provider (for example on Minikube), your ingress will never get an address. In that case, use the istio `hostIP` and `nodePort` as the service IP: ```shell -export SERVICE_IP=$(kubectl get po -l istio=ingress -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc istio-ingress -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') +export SERVICE_IP=$(kubectl get po -l knative=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') ``` Now curl the service IP as if DNS were properly configured: diff --git a/sample/stock-rest-app/README.md b/sample/stock-rest-app/README.md index 495369367f36..75c47a5a776c 100644 --- a/sample/stock-rest-app/README.md +++ b/sample/stock-rest-app/README.md @@ -69,14 +69,14 @@ Once the `ADDRESS` gets assigned to the cluster, you can run: export SERVICE_HOST=`kubectl get route stock-route-example -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -export SERVICE_IP=`kubectl get ingress stock-route-example-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` ``` If your cluster is running outside a cloud provider (for example on Minikube), your ingress will never get an address. In that case, use the istio `hostIP` and `nodePort` as the service IP: ```shell -export SERVICE_IP=$(kubectl get po -l istio=ingress -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc istio-ingress -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') +export SERVICE_IP=$(kubectl get po -l knative=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') ``` Now curl the service IP as if DNS were properly configured: diff --git a/sample/telemetrysample/README.md b/sample/telemetrysample/README.md index 0697b271e6ea..b7b5504bc373 100644 --- a/sample/telemetrysample/README.md +++ b/sample/telemetrysample/README.md @@ -64,7 +64,7 @@ Once the `ADDRESS` gets assigned to the cluster, you can run: export SERVICE_HOST=`kubectl get route telemetrysample-route -o jsonpath="{.status.domain}"` # Put the Ingress IP into an environment variable. -export SERVICE_IP=`kubectl get ingress telemetrysample-route-ingress -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` # Curl the Ingress IP "as-if" DNS were properly configured. curl --header "Host:$SERVICE_HOST" http://${SERVICE_IP} diff --git a/sample/thumbnailer/README.md b/sample/thumbnailer/README.md index e120a0b7ac5b..46dc764a1951 100644 --- a/sample/thumbnailer/README.md +++ b/sample/thumbnailer/README.md @@ -132,15 +132,14 @@ The Knative Serving ingress service will automatically be assigned an IP so let' # Put the Ingress Host name into an environment variable. export SERVICE_HOST=`kubectl get route thumb -o jsonpath="{.status.domain}"` -export SERVICE_IP=`kubectl get ing thumb-ingress \ - -o jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` +export SERVICE_IP=`kubectl get svc knative-ingressgateway -n istio-system -o jsonpath="{.status.loadBalancer.ingress[*].ip}"` ``` If your cluster is running outside a cloud provider (for example on Minikube), your ingress will never get an address. In that case, use the istio `hostIP` and `nodePort` as the service IP: ```shell -export SERVICE_IP=$(kubectl get po -l istio=ingress -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc istio-ingress -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') +export SERVICE_IP=$(kubectl get po -l knative=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc knative-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') ``` > To make the JSON service responses more readable consider installing [jq](https://stedolan.github.io/jq/), makes JSON pretty diff --git a/test/README.md b/test/README.md index aff4e213ace9..c5a27c5e6afd 100644 --- a/test/README.md +++ b/test/README.md @@ -196,8 +196,9 @@ docs](/DEVELOPMENT.md#getting-started), Routes created in the test will use the domain `example.com`, unless the route has label `app=prod` in which case they will use the domain `prod-domain.com`. Since these domains will not be resolvable to deployments in your test cluster, in order to make a request -against the endpoint, the test use the IP assigned to the istio `*-ingress` -and spoof the `Host` in the header. +against the endpoint, the test use the IP assigned to the service +`knative-ingressgateway` in the namespace `istio-system` and spoof the `Host` in +the header. If you have configured your cluster to use a resolvable domain, you can use the `--resolvabledomain` flag to indicate that the test should make requests directly against diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh index 43539ccff4e6..bf21ddba9c66 100755 --- a/test/e2e-tests.sh +++ b/test/e2e-tests.sh @@ -126,8 +126,6 @@ function dump_stack_info() { kubectl get configurations -o yaml --all-namespaces echo ">>> Revisions:" kubectl get revisions -o yaml --all-namespaces - echo ">>> Ingress:" - kubectl get ingress --all-namespaces echo ">>> Knative Serving controller log:" kubectl logs $(get_knative_pod controller) -n knative-serving echo "***************************************" @@ -139,9 +137,11 @@ function dump_stack_info() { function run_e2e_tests() { header "Running tests in $1" kubectl create namespace $2 + kubectl label namespace $2 istio-injection=enabled --overwrite local options="" (( EMIT_METRICS )) && options="-emitmetrics" report_go_test -v -tags=e2e -count=1 ./test/$1 -dockerrepo gcr.io/elafros-e2e-tests/$3 ${options} + local result=$? [[ ${result} -ne 0 ]] && dump_stack_info return ${result} @@ -291,6 +291,7 @@ set +o errexit set +o pipefail wait_until_pods_running knative-serving +wait_until_pods_running istio-system abort_if_failed # Run the tests diff --git a/test/e2e/autoscale_test.go b/test/e2e/autoscale_test.go index d81890254a2b..73ec1205ac58 100644 --- a/test/e2e/autoscale_test.go +++ b/test/e2e/autoscale_test.go @@ -113,9 +113,9 @@ func setup(t *testing.T, logger *zap.SugaredLogger) *test.Clients { return clients } -func tearDown(clients *test.Clients, names test.ResourceNames) { +func tearDown(clients *test.Clients, names test.ResourceNames, logger *zap.SugaredLogger) { setScaleToZeroThreshold(clients, initialScaleToZeroThreshold) - TearDown(clients, names) + TearDown(clients, names, logger) } func TestAutoscaleUpDownUp(t *testing.T) { @@ -134,8 +134,8 @@ func TestAutoscaleUpDownUp(t *testing.T) { if err != nil { t.Fatalf("Failed to create Route and Configuration: %v", err) } - test.CleanupOnInterrupt(func() { tearDown(clients, names) }, logger) - defer tearDown(clients, names) + test.CleanupOnInterrupt(func() { tearDown(clients, names, logger) }, logger) + defer tearDown(clients, names, logger) logger.Infof(`When the Revision can have traffic routed to it, the Route is marked as Ready.`) diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index 476cb8ae30f8..270ba0aab427 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -2,8 +2,10 @@ package e2e import ( "testing" - "go.uber.org/zap" + "time" + "github.com/knative/serving/test" + "go.uber.org/zap" // Mysteriously required to support GCP auth (required by k8s libs). // Apparently just importing it is enough. @_@ side effects @_@. // https://github.com/kubernetes/client-go/issues/242 @@ -31,10 +33,19 @@ func Setup(t *testing.T) *test.Clients { } // TearDown will delete created names using clients. -func TearDown(clients *test.Clients, names test.ResourceNames) { +func TearDown(clients *test.Clients, names test.ResourceNames, logger *zap.SugaredLogger) { if clients != nil { clients.Delete([]string{names.Route}, []string{names.Config}) } + + // There seems to be an Istio bug where if we delete / create + // VirtualServices too quickly we will hit pro-longed "No health + // upstream" causing timeouts. Adding this small sleep to + // sidestep the issue. + // + // TODO(#1376): Fix this when upstream fix is released. + logger.Info("Sleeping for 20 seconds after Route deletion to avoid hitting issue in #1376") + time.Sleep(20 * time.Second) } // CreateRouteAndConfig will create Route and Config objects using clients. diff --git a/test/e2e/errorcondition_test.go b/test/e2e/errorcondition_test.go index dfdb0f808b2b..41d074256273 100644 --- a/test/e2e/errorcondition_test.go +++ b/test/e2e/errorcondition_test.go @@ -54,8 +54,8 @@ func TestContainerErrorMsg(t *testing.T) { if err != nil { t.Fatalf("Failed to create Route and Configuration: %v", err) } - defer TearDown(clients, names) - test.CleanupOnInterrupt(func() { TearDown(clients, names) }, logger) + defer TearDown(clients, names, logger) + test.CleanupOnInterrupt(func() { TearDown(clients, names, logger) }, logger) manifestUnknown := string(remote.ManifestUnknownErrorCode) logger.Infof("When the imagepath is invalid, the Configuration should have error status.") diff --git a/test/e2e/helloworld_shell_test.go b/test/e2e/helloworld_shell_test.go index 2eb454c09203..1e9bc6176883 100644 --- a/test/e2e/helloworld_shell_test.go +++ b/test/e2e/helloworld_shell_test.go @@ -25,15 +25,17 @@ import ( "strings" "testing" "time" + "github.com/knative/serving/test" + "go.uber.org/zap" ) const ( - appYaml = "test_images/helloworld/helloworld.yaml" + appYaml = "test_images/helloworld/helloworld.yaml" yamlImagePlaceholder = "github.com/knative/serving/test_images/helloworld" - ingressTimeout = 5 * time.Minute - servingTimeout = 2 * time.Minute - checkInterval = 2 * time.Second + ingressTimeout = 5 * time.Minute + servingTimeout = 2 * time.Minute + checkInterval = 2 * time.Second ) func noStderrShell(name string, arg ...string) string { @@ -44,9 +46,17 @@ func noStderrShell(name string, arg ...string) string { return string(out) } -func cleanup(yamlFilename string) { +func cleanup(yamlFilename string, logger *zap.SugaredLogger) { exec.Command("kubectl", "delete", "-f", yamlFilename).Run() os.Remove(yamlFilename) + // There seems to be an Istio bug where if we delete / create + // VirtualServices too quickly we will hit pro-longed "No health + // upstream" causing timeouts. Adding this small sleep to + // sidestep the issue. + // + // TODO(#1376): Fix this when upstream fix is released. + logger.Info("Sleeping for 20 seconds after Route deletion to avoid hitting issue in #1376") + time.Sleep(20 * time.Second) } func TestHelloWorldFromShell(t *testing.T) { @@ -63,8 +73,8 @@ func TestHelloWorldFromShell(t *testing.T) { t.Fatalf("Failed to create temporary manifest: %v", err) } newYamlFilename := newYaml.Name() - defer cleanup(newYamlFilename) - test.CleanupOnInterrupt(func() { cleanup(newYamlFilename) },logger) + defer cleanup(newYamlFilename, logger) + test.CleanupOnInterrupt(func() { cleanup(newYamlFilename, logger) }, logger) // Populate manifets file with the real path to the container content, err := ioutil.ReadFile(appYaml) @@ -95,11 +105,12 @@ func TestHelloWorldFromShell(t *testing.T) { timeout := ingressTimeout for (serviceIP == "" || serviceHost == "") && timeout >= 0 { serviceHost = noStderrShell("kubectl", "get", "route", "route-example", "-o", "jsonpath={.status.domain}") - serviceIP = noStderrShell("kubectl", "get", "ingress", "route-example-ingress", "-o", "jsonpath={.status.loadBalancer.ingress[*]['ip']}") + serviceIP = noStderrShell("kubectl", "get", "svc", "knative-ingressgateway", "-n", "istio-system", + "-o", "jsonpath={.status.loadBalancer.ingress[*]['ip']}") time.Sleep(checkInterval) timeout = timeout - checkInterval } - if (serviceIP == "" || serviceHost == "") { + if serviceIP == "" || serviceHost == "" { // serviceHost or serviceIP might contain a useful error, dump them. t.Fatalf("Ingress not found (IP='%s', host='%s')", serviceIP, serviceHost) } @@ -109,8 +120,8 @@ func TestHelloWorldFromShell(t *testing.T) { outputString := "" timeout = servingTimeout - for (outputString != helloWorldExpectedOutput && timeout >= 0) { - output, err := exec.Command("curl", "--header", "Host:" + serviceHost, "http://" + serviceIP).Output() + for outputString != helloWorldExpectedOutput && timeout >= 0 { + output, err := exec.Command("curl", "--header", "Host:"+serviceHost, "http://"+serviceIP).Output() errorString := "none" time.Sleep(checkInterval) timeout = timeout - checkInterval diff --git a/test/e2e/helloworld_test.go b/test/e2e/helloworld_test.go index 01a0facbf399..d3ee0d636d2a 100644 --- a/test/e2e/helloworld_test.go +++ b/test/e2e/helloworld_test.go @@ -51,8 +51,8 @@ func TestHelloWorld(t *testing.T) { if err != nil { t.Fatalf("Failed to create Route and Configuration: %v", err) } - test.CleanupOnInterrupt(func() { TearDown(clients, names) }, logger) - defer TearDown(clients, names) + test.CleanupOnInterrupt(func() { TearDown(clients, names, logger) }, logger) + defer TearDown(clients, names, logger) logger.Infof("When the Revision can have traffic routed to it, the Route is marked as Ready.") err = test.WaitForRouteState(clients.Routes, names.Route, func(r *v1alpha1.Route) (bool, error) { diff --git a/test/request.go b/test/request.go index cc5993c6a493..0e04307ac862 100644 --- a/test/request.go +++ b/test/request.go @@ -87,8 +87,9 @@ func WaitForEndpointState(kubeClientset *kubernetes.Clientset, logger *zap.Sugar // (the domainSuffix) is not resolvable, we need to retrieve the IP of the endpoint and // spoof the Host in our requests. if !resolvableDomain { - ingressName := routeName + "-ingress" - ingress, err := kubeClientset.ExtensionsV1beta1().Ingresses(namespaceName).Get(ingressName, metav1.GetOptions{}) + ingressName := "knative-ingressgateway" + ingressNamespace := "istio-system" + ingress, err := kubeClientset.CoreV1().Services(ingressNamespace).Get(ingressName, metav1.GetOptions{}) if err != nil { return err }