Skip to content

Commit

Permalink
Add service subselector support in vs/vsr
Browse files Browse the repository at this point in the history
  • Loading branch information
Dean-Coakley committed Oct 4, 2019
1 parent 423956b commit 898cca0
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 71 deletions.
1 change: 1 addition & 0 deletions deployments/helm-chart/templates/rbac.yaml
Expand Up @@ -41,6 +41,7 @@ rules:
- pods
verbs:
- list
- watch
- apiGroups:
- ""
resources:
Expand Down
1 change: 1 addition & 0 deletions deployments/rbac/rbac.yaml
Expand Up @@ -36,6 +36,7 @@ rules:
- pods
verbs:
- list
- watch
- apiGroups:
- ""
resources:
Expand Down
3 changes: 3 additions & 0 deletions docs/virtualserver-and-virtualserverroute.md
Expand Up @@ -181,6 +181,8 @@ The upstream defines a destination for the routing configuration. For example:
```yaml
name: tea
service: tea-svc
subselector:
version: canary
port: 80
lb-method: round_robin
fail-timeout: 10s
Expand All @@ -204,6 +206,7 @@ tls:
| ----- | ----------- | ---- | -------- |
| `name` | The name of the upstream. Must be a valid DNS label as defined in RFC 1035. For example, `hello` and `upstream-123` are valid. The name must be unique among all upstreams of the resource. | `string` | Yes |
| `service` | The name of a [service](https://kubernetes.io/docs/concepts/services-networking/service/). The service must belong to the same namespace as the resource. If the service doesn't exist, NGINX will assume the service has zero endpoints and return a `502` response for requests for this upstream. For NGINX Plus only, services of type [ExternalName](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) are also supported (check the [prerequisites](../examples/externalname-services#prerequisites)). | `string` | Yes |
| `subselector` | Selects the pods within the service using label keys and values. By default, all pods of the service are selected. Note: the specified labels are expected to be present in the pods when they are created. If the pod labels are updated, the Ingress Controller will not see that change until the number of the pods is changed. | `map[string]string` | No |
| `port` | The port of the service. If the service doesn't define that port, NGINX will assume the service has zero endpoints and return a `502` response for requests for this upstream. The port must fall into the range `1..65553`. | `uint16` | Yes |
| `lb-method` | The load [balancing method](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/#choosing-a-load-balancing-method). To use the round-robin method, specify `round_robin`. The default is specified in the `lb-method` ConfigMap key. | `string` | No |
| `fail-timeout` | The time during which the specified number of unsuccessful attempts to communicate with an upstream server should happen to consider the server unavailable. See the [fail_timeout](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#fail_timeout) parameter of the server directive. The default is set in the `fail-timeout` ConfigMap key. | `string` | No |
Expand Down
12 changes: 8 additions & 4 deletions internal/configs/virtualserver.go
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/golang/glog"
"github.com/nginxinc/kubernetes-ingress/internal/nginx"
api_v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"

"github.com/nginxinc/kubernetes-ingress/internal/configs/version2"
Expand Down Expand Up @@ -46,7 +47,10 @@ func (vsx *VirtualServerEx) String() string {
}

// GenerateEndpointsKey generates a key for the Endpoints map in VirtualServerEx.
func GenerateEndpointsKey(serviceNamespace string, serviceName string, port uint16) string {
func GenerateEndpointsKey(serviceNamespace string, serviceName string, subselector map[string]string, port uint16) string {
if len(subselector) > 0 {
return fmt.Sprintf("%s/%s_%s:%d", serviceNamespace, serviceName, labels.Set(subselector).String(), port)
}
return fmt.Sprintf("%s/%s:%d", serviceNamespace, serviceName, port)
}

Expand Down Expand Up @@ -137,7 +141,7 @@ func newVirtualServerConfigurator(cfgParams *ConfigParams, isPlus bool, isResolv
}

func (vsc *virtualServerConfigurator) generateEndpointsForUpstream(owner runtime.Object, namespace string, upstream conf_v1alpha1.Upstream, virtualServerEx *VirtualServerEx) []string {
endpointsKey := GenerateEndpointsKey(namespace, upstream.Service, upstream.Port)
endpointsKey := GenerateEndpointsKey(namespace, upstream.Service, upstream.Subselector, upstream.Port)
externalNameSvcKey := GenerateExternalNameSvcKey(namespace, upstream.Service)
endpoints := virtualServerEx.Endpoints[endpointsKey]
if !vsc.isPlus && len(endpoints) == 0 {
Expand Down Expand Up @@ -756,7 +760,7 @@ func createUpstreamsForPlus(virtualServerEx *VirtualServerEx, baseCfgParams *Con
upstreamName := upstreamNamer.GetNameForUpstream(u.Name)
upstreamNamespace := virtualServerEx.VirtualServer.Namespace

endpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Service, u.Port)
endpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Service, u.Subselector, u.Port)
endpoints := virtualServerEx.Endpoints[endpointsKey]

ups := vsc.generateUpstream(virtualServerEx.VirtualServer, upstreamName, u, isExternalNameSvc, endpoints)
Expand All @@ -775,7 +779,7 @@ func createUpstreamsForPlus(virtualServerEx *VirtualServerEx, baseCfgParams *Con
upstreamName := upstreamNamer.GetNameForUpstream(u.Name)
upstreamNamespace := vsr.Namespace

endpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Service, u.Port)
endpointsKey := GenerateEndpointsKey(upstreamNamespace, u.Service, u.Subselector, u.Port)
endpoints := virtualServerEx.Endpoints[endpointsKey]

ups := vsc.generateUpstream(vsr, upstreamName, u, isExternalNameSvc, endpoints)
Expand Down
169 changes: 161 additions & 8 deletions internal/configs/virtualserver_test.go
Expand Up @@ -50,11 +50,26 @@ func TestGenerateEndpointsKey(t *testing.T) {
serviceName := "test"
var port uint16 = 80

expected := "default/test:80"
tests := []struct {
subselector map[string]string
expected string
}{
{
subselector: nil,
expected: "default/test:80",
},
{
subselector: map[string]string{"version": "v1"},
expected: "default/test_version=v1:80",
},
}

for _, test := range tests {
result := GenerateEndpointsKey(serviceNamespace, serviceName, test.subselector, port)
if result != test.expected {
t.Errorf("GenerateEndpointsKey() returned %q but expected %q", result, test.expected)
}

result := GenerateEndpointsKey(serviceNamespace, serviceName, port)
if result != expected {
t.Errorf("GenerateEndpointsKey() returned %q but expected %q", result, expected)
}
}

Expand Down Expand Up @@ -174,26 +189,46 @@ func TestGenerateVirtualServerConfig(t *testing.T) {
Service: "tea-svc",
Port: 80,
},
{
Name: "tea-latest",
Service: "tea-svc",
Subselector: map[string]string{"version": "v1"},
Port: 80,
},
},
Routes: []conf_v1alpha1.Route{
{
Path: "/tea",
Upstream: "tea",
},
{
Path: "/tea-latest",
Upstream: "tea-latest",
},
{
Path: "/coffee",
Route: "default/coffee",
},
{
Path: "/subtea",
Route: "default/subtea",
},
},
},
},
Endpoints: map[string][]string{
"default/tea-svc:80": {
"10.0.0.20:80",
},
"default/coffee-svc:80": {
"default/tea-svc_version=v1:80": {
"10.0.0.30:80",
},
"default/coffee-svc:80": {
"10.0.0.40:80",
},
"default/sub-tea-svc_version=v1:80": {
"10.0.0.50:80",
},
},
VirtualServerRoutes: []*conf_v1alpha1.VirtualServerRoute{
{
Expand All @@ -218,6 +253,29 @@ func TestGenerateVirtualServerConfig(t *testing.T) {
},
},
},
{
ObjectMeta: meta_v1.ObjectMeta{
Name: "subtea",
Namespace: "default",
},
Spec: conf_v1alpha1.VirtualServerRouteSpec{
Host: "cafe.example.com",
Upstreams: []conf_v1alpha1.Upstream{
{
Name: "subtea",
Service: "sub-tea-svc",
Port: 80,
Subselector: map[string]string{"version": "v1"},
},
},
Subroutes: []conf_v1alpha1.Route{
{
Path: "/subtea",
Upstream: "subtea",
},
},
},
},
},
}

Expand All @@ -244,14 +302,32 @@ func TestGenerateVirtualServerConfig(t *testing.T) {
Keepalive: 16,
},
{
Name: "vs_default_cafe_vsr_default_coffee_coffee",
Name: "vs_default_cafe_tea-latest",
Servers: []version2.UpstreamServer{
{
Address: "10.0.0.30:80",
},
},
Keepalive: 16,
},
{
Name: "vs_default_cafe_vsr_default_coffee_coffee",
Servers: []version2.UpstreamServer{
{
Address: "10.0.0.40:80",
},
},
Keepalive: 16,
},
{
Name: "vs_default_cafe_vsr_default_subtea_subtea",
Servers: []version2.UpstreamServer{
{
Address: "10.0.0.50:80",
},
},
Keepalive: 16,
},
},
Server: version2.Server{
ServerName: "cafe.example.com",
Expand All @@ -272,6 +348,14 @@ func TestGenerateVirtualServerConfig(t *testing.T) {
ProxyNextUpstreamTries: 0,
HasKeepalive: true,
},
{
Path: "/tea-latest",
ProxyPass: "http://vs_default_cafe_tea-latest",
ProxyNextUpstream: "error timeout",
ProxyNextUpstreamTimeout: "0s",
ProxyNextUpstreamTries: 0,
HasKeepalive: true,
},
{
Path: "/coffee",
ProxyPass: "http://vs_default_cafe_vsr_default_coffee_coffee",
Expand All @@ -280,6 +364,14 @@ func TestGenerateVirtualServerConfig(t *testing.T) {
ProxyNextUpstreamTries: 0,
HasKeepalive: true,
},
{
Path: "/subtea",
ProxyPass: "http://vs_default_cafe_vsr_default_subtea_subtea",
ProxyNextUpstream: "error timeout",
ProxyNextUpstreamTimeout: "0s",
ProxyNextUpstreamTries: 0,
HasKeepalive: true,
},
},
},
}
Expand Down Expand Up @@ -1134,6 +1226,12 @@ func TestCreateUpstreamServersForPlus(t *testing.T) {
Service: "test-svc",
Port: 80,
},
{
Name: "subselector-test",
Service: "test-svc",
Subselector: map[string]string{"it": "works"},
Port: 80,
},
{
Name: "external",
Service: "external-svc",
Expand Down Expand Up @@ -1161,9 +1259,12 @@ func TestCreateUpstreamServersForPlus(t *testing.T) {
"10.0.0.20:80",
},
"default/test-svc:80": {},
"default/coffee-svc:80": {
"default/test-svc_it=works:80": {
"10.0.0.30:80",
},
"default/coffee-svc:80": {
"10.0.0.40:80",
},
"default/external-svc:80": {
"example.com:80",
},
Expand Down Expand Up @@ -1211,13 +1312,21 @@ func TestCreateUpstreamServersForPlus(t *testing.T) {
Servers: nil,
},
{
Name: "vs_default_cafe_vsr_default_coffee_coffee",
Name: "vs_default_cafe_subselector-test",
Servers: []version2.UpstreamServer{
{
Address: "10.0.0.30:80",
},
},
},
{
Name: "vs_default_cafe_vsr_default_coffee_coffee",
Servers: []version2.UpstreamServer{
{
Address: "10.0.0.40:80",
},
},
},
}

result := createUpstreamsForPlus(&virtualServerEx, &ConfigParams{})
Expand Down Expand Up @@ -2039,6 +2148,50 @@ func TestGenerateEndpointsForUpstream(t *testing.T) {
expected: nil,
msg: "Service with no endpoints",
},
{
upstream: conf_v1alpha1.Upstream{
Service: name,
Port: 8080,
Subselector: map[string]string{"version": "test"},
},
vsEx: &VirtualServerEx{
VirtualServer: &conf_v1alpha1.VirtualServer{
ObjectMeta: meta_v1.ObjectMeta{
Name: name,
Namespace: namespace,
},
},
Endpoints: map[string][]string{
"test-namespace/test_version=test:8080": {"192.168.10.10:8080"},
},
},
isPlus: false,
isResolverConfigured: false,
expected: []string{"192.168.10.10:8080"},
msg: "Upstream with subselector, with a matching endpoint",
},
{
upstream: conf_v1alpha1.Upstream{
Service: name,
Port: 8080,
Subselector: map[string]string{"version": "test"},
},
vsEx: &VirtualServerEx{
VirtualServer: &conf_v1alpha1.VirtualServer{
ObjectMeta: meta_v1.ObjectMeta{
Name: name,
Namespace: namespace,
},
},
Endpoints: map[string][]string{
"test-namespace/test:8080": {"192.168.10.10:8080"},
},
},
isPlus: false,
isResolverConfigured: false,
expected: []string{nginx502Server},
msg: "Upstream with subselector, without a matching endpoint",
},
}

for _, test := range tests {
Expand Down

0 comments on commit 898cca0

Please sign in to comment.