Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add service subselector support in vs/vsr #712

Merged
merged 7 commits into from
Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deployments/helm-chart/templates/rbac.yaml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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)
Dean-Coakley marked this conversation as resolved.
Show resolved Hide resolved
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
122 changes: 114 additions & 8 deletions internal/configs/virtualserver_test.go
Original file line number Diff line number Diff line change
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,12 +189,22 @@ 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",
Expand All @@ -191,9 +216,12 @@ func TestGenerateVirtualServerConfig(t *testing.T) {
"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",
},
},
VirtualServerRoutes: []*conf_v1alpha1.VirtualServerRoute{
{
Expand Down Expand Up @@ -244,14 +272,23 @@ 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,
},
},
Server: version2.Server{
ServerName: "cafe.example.com",
Expand All @@ -272,6 +309,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 Down Expand Up @@ -1134,6 +1179,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 +1212,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 +1265,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 +2101,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
Loading