git mv Ingress ingress
This commit is contained in:
parent
34b949c134
commit
3da4e74e5a
2185 changed files with 754743 additions and 0 deletions
438
controllers/gce/loadbalancers/fakes.go
Normal file
438
controllers/gce/loadbalancers/fakes.go
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 loadbalancers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
)
|
||||
|
||||
var testIPManager = testIP{}
|
||||
|
||||
type testIP struct {
|
||||
start int
|
||||
}
|
||||
|
||||
func (t *testIP) ip() string {
|
||||
t.start++
|
||||
return fmt.Sprintf("0.0.0.%v", t.start)
|
||||
}
|
||||
|
||||
// Loadbalancer fakes
|
||||
|
||||
// FakeLoadBalancers is a type that fakes out the loadbalancer interface.
|
||||
type FakeLoadBalancers struct {
|
||||
Fw []*compute.ForwardingRule
|
||||
Um []*compute.UrlMap
|
||||
Tp []*compute.TargetHttpProxy
|
||||
Tps []*compute.TargetHttpsProxy
|
||||
IP []*compute.Address
|
||||
Certs []*compute.SslCertificate
|
||||
name string
|
||||
}
|
||||
|
||||
// TODO: There is some duplication between these functions and the name mungers in
|
||||
// loadbalancer file.
|
||||
func (f *FakeLoadBalancers) fwName(https bool) string {
|
||||
if https {
|
||||
return fmt.Sprintf("%v-%v", httpsForwardingRulePrefix, f.name)
|
||||
}
|
||||
return fmt.Sprintf("%v-%v", forwardingRulePrefix, f.name)
|
||||
}
|
||||
|
||||
func (f *FakeLoadBalancers) umName() string {
|
||||
return fmt.Sprintf("%v-%v", urlMapPrefix, f.name)
|
||||
}
|
||||
|
||||
func (f *FakeLoadBalancers) tpName(https bool) string {
|
||||
if https {
|
||||
return fmt.Sprintf("%v-%v", targetHTTPSProxyPrefix, f.name)
|
||||
}
|
||||
return fmt.Sprintf("%v-%v", targetProxyPrefix, f.name)
|
||||
}
|
||||
|
||||
// String is the string method for FakeLoadBalancers.
|
||||
func (f *FakeLoadBalancers) String() string {
|
||||
msg := fmt.Sprintf(
|
||||
"Loadbalancer %v,\nforwarding rules:\n", f.name)
|
||||
for _, fw := range f.Fw {
|
||||
msg += fmt.Sprintf("\t%v\n", fw.Name)
|
||||
}
|
||||
msg += fmt.Sprintf("Target proxies\n")
|
||||
for _, tp := range f.Tp {
|
||||
msg += fmt.Sprintf("\t%v\n", tp.Name)
|
||||
}
|
||||
msg += fmt.Sprintf("UrlMaps\n")
|
||||
for _, um := range f.Um {
|
||||
msg += fmt.Sprintf("%v\n", um.Name)
|
||||
msg += fmt.Sprintf("\tHost Rules:\n")
|
||||
for _, hostRule := range um.HostRules {
|
||||
msg += fmt.Sprintf("\t\t%v\n", hostRule)
|
||||
}
|
||||
msg += fmt.Sprintf("\tPath Matcher:\n")
|
||||
for _, pathMatcher := range um.PathMatchers {
|
||||
msg += fmt.Sprintf("\t\t%v\n", pathMatcher.Name)
|
||||
for _, pathRule := range pathMatcher.PathRules {
|
||||
msg += fmt.Sprintf("\t\t\t%+v\n", pathRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Forwarding Rule fakes
|
||||
|
||||
// GetGlobalForwardingRule returns a fake forwarding rule.
|
||||
func (f *FakeLoadBalancers) GetGlobalForwardingRule(name string) (*compute.ForwardingRule, error) {
|
||||
for i := range f.Fw {
|
||||
if f.Fw[i].Name == name {
|
||||
return f.Fw[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Forwarding rule %v not found", name)
|
||||
}
|
||||
|
||||
// CreateGlobalForwardingRule fakes forwarding rule creation.
|
||||
func (f *FakeLoadBalancers) CreateGlobalForwardingRule(proxyLink, ip, name, portRange string) (*compute.ForwardingRule, error) {
|
||||
if ip == "" {
|
||||
ip = fmt.Sprintf(testIPManager.ip())
|
||||
}
|
||||
rule := &compute.ForwardingRule{
|
||||
Name: name,
|
||||
IPAddress: ip,
|
||||
Target: proxyLink,
|
||||
PortRange: portRange,
|
||||
IPProtocol: "TCP",
|
||||
SelfLink: name,
|
||||
}
|
||||
f.Fw = append(f.Fw, rule)
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
// SetProxyForGlobalForwardingRule fakes setting a global forwarding rule.
|
||||
func (f *FakeLoadBalancers) SetProxyForGlobalForwardingRule(fw *compute.ForwardingRule, proxyLink string) error {
|
||||
for i := range f.Fw {
|
||||
if f.Fw[i].Name == fw.Name {
|
||||
f.Fw[i].Target = proxyLink
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGlobalForwardingRule fakes deleting a global forwarding rule.
|
||||
func (f *FakeLoadBalancers) DeleteGlobalForwardingRule(name string) error {
|
||||
fw := []*compute.ForwardingRule{}
|
||||
for i := range f.Fw {
|
||||
if f.Fw[i].Name != name {
|
||||
fw = append(fw, f.Fw[i])
|
||||
}
|
||||
}
|
||||
f.Fw = fw
|
||||
return nil
|
||||
}
|
||||
|
||||
// UrlMaps fakes
|
||||
|
||||
// GetUrlMap fakes getting url maps from the cloud.
|
||||
func (f *FakeLoadBalancers) GetUrlMap(name string) (*compute.UrlMap, error) {
|
||||
for i := range f.Um {
|
||||
if f.Um[i].Name == name {
|
||||
return f.Um[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Url Map %v not found", name)
|
||||
}
|
||||
|
||||
// CreateUrlMap fakes url-map creation.
|
||||
func (f *FakeLoadBalancers) CreateUrlMap(backend *compute.BackendService, name string) (*compute.UrlMap, error) {
|
||||
urlMap := &compute.UrlMap{
|
||||
Name: name,
|
||||
DefaultService: backend.SelfLink,
|
||||
SelfLink: f.umName(),
|
||||
}
|
||||
f.Um = append(f.Um, urlMap)
|
||||
return urlMap, nil
|
||||
}
|
||||
|
||||
// UpdateUrlMap fakes updating url-maps.
|
||||
func (f *FakeLoadBalancers) UpdateUrlMap(urlMap *compute.UrlMap) (*compute.UrlMap, error) {
|
||||
for i := range f.Um {
|
||||
if f.Um[i].Name == urlMap.Name {
|
||||
f.Um[i] = urlMap
|
||||
return urlMap, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeleteUrlMap fakes url-map deletion.
|
||||
func (f *FakeLoadBalancers) DeleteUrlMap(name string) error {
|
||||
um := []*compute.UrlMap{}
|
||||
for i := range f.Um {
|
||||
if f.Um[i].Name != name {
|
||||
um = append(um, f.Um[i])
|
||||
}
|
||||
}
|
||||
f.Um = um
|
||||
return nil
|
||||
}
|
||||
|
||||
// TargetProxies fakes
|
||||
|
||||
// GetTargetHttpProxy fakes getting target http proxies from the cloud.
|
||||
func (f *FakeLoadBalancers) GetTargetHttpProxy(name string) (*compute.TargetHttpProxy, error) {
|
||||
for i := range f.Tp {
|
||||
if f.Tp[i].Name == name {
|
||||
return f.Tp[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Targetproxy %v not found", name)
|
||||
}
|
||||
|
||||
// CreateTargetHttpProxy fakes creating a target http proxy.
|
||||
func (f *FakeLoadBalancers) CreateTargetHttpProxy(urlMap *compute.UrlMap, name string) (*compute.TargetHttpProxy, error) {
|
||||
proxy := &compute.TargetHttpProxy{
|
||||
Name: name,
|
||||
UrlMap: urlMap.SelfLink,
|
||||
SelfLink: name,
|
||||
}
|
||||
f.Tp = append(f.Tp, proxy)
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
// DeleteTargetHttpProxy fakes deleting a target http proxy.
|
||||
func (f *FakeLoadBalancers) DeleteTargetHttpProxy(name string) error {
|
||||
tp := []*compute.TargetHttpProxy{}
|
||||
for i := range f.Tp {
|
||||
if f.Tp[i].Name != name {
|
||||
tp = append(tp, f.Tp[i])
|
||||
}
|
||||
}
|
||||
f.Tp = tp
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUrlMapForTargetHttpProxy fakes setting an url-map for a target http proxy.
|
||||
func (f *FakeLoadBalancers) SetUrlMapForTargetHttpProxy(proxy *compute.TargetHttpProxy, urlMap *compute.UrlMap) error {
|
||||
for i := range f.Tp {
|
||||
if f.Tp[i].Name == proxy.Name {
|
||||
f.Tp[i].UrlMap = urlMap.SelfLink
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TargetHttpsProxy fakes
|
||||
|
||||
// GetTargetHttpsProxy fakes getting target http proxies from the cloud.
|
||||
func (f *FakeLoadBalancers) GetTargetHttpsProxy(name string) (*compute.TargetHttpsProxy, error) {
|
||||
for i := range f.Tps {
|
||||
if f.Tps[i].Name == name {
|
||||
return f.Tps[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Targetproxy %v not found", name)
|
||||
}
|
||||
|
||||
// CreateTargetHttpsProxy fakes creating a target http proxy.
|
||||
func (f *FakeLoadBalancers) CreateTargetHttpsProxy(urlMap *compute.UrlMap, cert *compute.SslCertificate, name string) (*compute.TargetHttpsProxy, error) {
|
||||
proxy := &compute.TargetHttpsProxy{
|
||||
Name: name,
|
||||
UrlMap: urlMap.SelfLink,
|
||||
SslCertificates: []string{cert.SelfLink},
|
||||
SelfLink: name,
|
||||
}
|
||||
f.Tps = append(f.Tps, proxy)
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
// DeleteTargetHttpsProxy fakes deleting a target http proxy.
|
||||
func (f *FakeLoadBalancers) DeleteTargetHttpsProxy(name string) error {
|
||||
tp := []*compute.TargetHttpsProxy{}
|
||||
for i := range f.Tps {
|
||||
if f.Tps[i].Name != name {
|
||||
tp = append(tp, f.Tps[i])
|
||||
}
|
||||
}
|
||||
f.Tps = tp
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUrlMapForTargetHttpsProxy fakes setting an url-map for a target http proxy.
|
||||
func (f *FakeLoadBalancers) SetUrlMapForTargetHttpsProxy(proxy *compute.TargetHttpsProxy, urlMap *compute.UrlMap) error {
|
||||
for i := range f.Tps {
|
||||
if f.Tps[i].Name == proxy.Name {
|
||||
f.Tps[i].UrlMap = urlMap.SelfLink
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSslCertificateForTargetHttpsProxy fakes out setting certificates.
|
||||
func (f *FakeLoadBalancers) SetSslCertificateForTargetHttpsProxy(proxy *compute.TargetHttpsProxy, SSLCert *compute.SslCertificate) error {
|
||||
found := false
|
||||
for i := range f.Tps {
|
||||
if f.Tps[i].Name == proxy.Name {
|
||||
f.Tps[i].SslCertificates = []string{SSLCert.SelfLink}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("Failed to find proxy %v", proxy.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UrlMap fakes
|
||||
|
||||
// CheckURLMap checks the URL map.
|
||||
func (f *FakeLoadBalancers) CheckURLMap(t *testing.T, l7 *L7, expectedMap map[string]utils.FakeIngressRuleValueMap) {
|
||||
um, err := f.GetUrlMap(l7.um.Name)
|
||||
if err != nil || um == nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
// Check the default backend
|
||||
var d string
|
||||
if h, ok := expectedMap[utils.DefaultBackendKey]; ok {
|
||||
if d, ok = h[utils.DefaultBackendKey]; ok {
|
||||
delete(h, utils.DefaultBackendKey)
|
||||
}
|
||||
delete(expectedMap, utils.DefaultBackendKey)
|
||||
}
|
||||
// The urlmap should have a default backend, and each path matcher.
|
||||
if d != "" && l7.um.DefaultService != d {
|
||||
t.Fatalf("Expected default backend %v found %v",
|
||||
d, l7.um.DefaultService)
|
||||
}
|
||||
|
||||
for _, matcher := range l7.um.PathMatchers {
|
||||
var hostname string
|
||||
// There's a 1:1 mapping between pathmatchers and hosts
|
||||
for _, hostRule := range l7.um.HostRules {
|
||||
if matcher.Name == hostRule.PathMatcher {
|
||||
if len(hostRule.Hosts) != 1 {
|
||||
t.Fatalf("Unexpected hosts in hostrules %+v", hostRule)
|
||||
}
|
||||
if d != "" && matcher.DefaultService != d {
|
||||
t.Fatalf("Expected default backend %v found %v",
|
||||
d, matcher.DefaultService)
|
||||
}
|
||||
hostname = hostRule.Hosts[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
// These are all pathrules for a single host, found above
|
||||
for _, rule := range matcher.PathRules {
|
||||
if len(rule.Paths) != 1 {
|
||||
t.Fatalf("Unexpected rule in pathrules %+v", rule)
|
||||
}
|
||||
pathRule := rule.Paths[0]
|
||||
if hostMap, ok := expectedMap[hostname]; !ok {
|
||||
t.Fatalf("Expected map for host %v: %v", hostname, hostMap)
|
||||
} else if svc, ok := expectedMap[hostname][pathRule]; !ok {
|
||||
t.Fatalf("Expected rule %v in host map", pathRule)
|
||||
} else if svc != rule.Service {
|
||||
t.Fatalf("Expected service %v found %v", svc, rule.Service)
|
||||
}
|
||||
delete(expectedMap[hostname], pathRule)
|
||||
if len(expectedMap[hostname]) == 0 {
|
||||
delete(expectedMap, hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(expectedMap) != 0 {
|
||||
t.Fatalf("Untranslated entries %+v", expectedMap)
|
||||
}
|
||||
}
|
||||
|
||||
// Static IP fakes
|
||||
|
||||
// ReserveGlobalStaticIP fakes out static IP reservation.
|
||||
func (f *FakeLoadBalancers) ReserveGlobalStaticIP(name, IPAddress string) (*compute.Address, error) {
|
||||
ip := &compute.Address{
|
||||
Name: name,
|
||||
Address: IPAddress,
|
||||
}
|
||||
f.IP = append(f.IP, ip)
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
// GetGlobalStaticIP fakes out static IP retrieval.
|
||||
func (f *FakeLoadBalancers) GetGlobalStaticIP(name string) (*compute.Address, error) {
|
||||
for i := range f.IP {
|
||||
if f.IP[i].Name == name {
|
||||
return f.IP[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Static IP %v not found", name)
|
||||
}
|
||||
|
||||
// DeleteGlobalStaticIP fakes out static IP deletion.
|
||||
func (f *FakeLoadBalancers) DeleteGlobalStaticIP(name string) error {
|
||||
ip := []*compute.Address{}
|
||||
for i := range f.IP {
|
||||
if f.IP[i].Name != name {
|
||||
ip = append(ip, f.IP[i])
|
||||
}
|
||||
}
|
||||
f.IP = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
// SslCertificate fakes
|
||||
|
||||
// GetSslCertificate fakes out getting ssl certs.
|
||||
func (f *FakeLoadBalancers) GetSslCertificate(name string) (*compute.SslCertificate, error) {
|
||||
for i := range f.Certs {
|
||||
if f.Certs[i].Name == name {
|
||||
return f.Certs[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Cert %v not found", name)
|
||||
}
|
||||
|
||||
// CreateSslCertificate fakes out certificate creation.
|
||||
func (f *FakeLoadBalancers) CreateSslCertificate(cert *compute.SslCertificate) (*compute.SslCertificate, error) {
|
||||
cert.SelfLink = cert.Name
|
||||
f.Certs = append(f.Certs, cert)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// DeleteSslCertificate fakes out certificate deletion.
|
||||
func (f *FakeLoadBalancers) DeleteSslCertificate(name string) error {
|
||||
certs := []*compute.SslCertificate{}
|
||||
for i := range f.Certs {
|
||||
if f.Certs[i].Name != name {
|
||||
certs = append(certs, f.Certs[i])
|
||||
}
|
||||
}
|
||||
f.Certs = certs
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFakeLoadBalancers creates a fake cloud client. Name is the name
|
||||
// inserted into the selfLink of the associated resources for testing.
|
||||
// eg: forwardingRule.SelfLink == k8-fw-name.
|
||||
func NewFakeLoadBalancers(name string) *FakeLoadBalancers {
|
||||
return &FakeLoadBalancers{
|
||||
Fw: []*compute.ForwardingRule{},
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
74
controllers/gce/loadbalancers/interfaces.go
Normal file
74
controllers/gce/loadbalancers/interfaces.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 loadbalancers
|
||||
|
||||
import (
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
// LoadBalancers is an interface for managing all the gce resources needed by L7
|
||||
// loadbalancers. We don't have individual pools for each of these resources
|
||||
// because none of them are usable (or acquirable) stand-alone, unlinke backends
|
||||
// and instance groups. The dependency graph:
|
||||
// ForwardingRule -> UrlMaps -> TargetProxies
|
||||
type LoadBalancers interface {
|
||||
// Forwarding Rules
|
||||
GetGlobalForwardingRule(name string) (*compute.ForwardingRule, error)
|
||||
CreateGlobalForwardingRule(proxyLink, ip, name, portRange string) (*compute.ForwardingRule, error)
|
||||
DeleteGlobalForwardingRule(name string) error
|
||||
SetProxyForGlobalForwardingRule(fw *compute.ForwardingRule, proxy string) error
|
||||
|
||||
// UrlMaps
|
||||
GetUrlMap(name string) (*compute.UrlMap, error)
|
||||
CreateUrlMap(backend *compute.BackendService, name string) (*compute.UrlMap, error)
|
||||
UpdateUrlMap(urlMap *compute.UrlMap) (*compute.UrlMap, error)
|
||||
DeleteUrlMap(name string) error
|
||||
|
||||
// TargetProxies
|
||||
GetTargetHttpProxy(name string) (*compute.TargetHttpProxy, error)
|
||||
CreateTargetHttpProxy(urlMap *compute.UrlMap, name string) (*compute.TargetHttpProxy, error)
|
||||
DeleteTargetHttpProxy(name string) error
|
||||
SetUrlMapForTargetHttpProxy(proxy *compute.TargetHttpProxy, urlMap *compute.UrlMap) error
|
||||
|
||||
// TargetHttpsProxies
|
||||
GetTargetHttpsProxy(name string) (*compute.TargetHttpsProxy, error)
|
||||
CreateTargetHttpsProxy(urlMap *compute.UrlMap, SSLCerts *compute.SslCertificate, name string) (*compute.TargetHttpsProxy, error)
|
||||
DeleteTargetHttpsProxy(name string) error
|
||||
SetUrlMapForTargetHttpsProxy(proxy *compute.TargetHttpsProxy, urlMap *compute.UrlMap) error
|
||||
SetSslCertificateForTargetHttpsProxy(proxy *compute.TargetHttpsProxy, SSLCerts *compute.SslCertificate) error
|
||||
|
||||
// SslCertificates
|
||||
GetSslCertificate(name string) (*compute.SslCertificate, error)
|
||||
CreateSslCertificate(certs *compute.SslCertificate) (*compute.SslCertificate, error)
|
||||
DeleteSslCertificate(name string) error
|
||||
|
||||
// Static IP
|
||||
ReserveGlobalStaticIP(name, IPAddress string) (*compute.Address, error)
|
||||
GetGlobalStaticIP(name string) (*compute.Address, error)
|
||||
DeleteGlobalStaticIP(name string) error
|
||||
}
|
||||
|
||||
// LoadBalancerPool is an interface to manage the cloud resources associated
|
||||
// with a gce loadbalancer.
|
||||
type LoadBalancerPool interface {
|
||||
Get(name string) (*L7, error)
|
||||
Add(ri *L7RuntimeInfo) error
|
||||
Delete(name string) error
|
||||
Sync(ri []*L7RuntimeInfo) error
|
||||
GC(names []string) error
|
||||
Shutdown() error
|
||||
}
|
||||
789
controllers/gce/loadbalancers/loadbalancers.go
Normal file
789
controllers/gce/loadbalancers/loadbalancers.go
Normal file
|
|
@ -0,0 +1,789 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 loadbalancers
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/backends"
|
||||
"k8s.io/contrib/ingress/controllers/gce/storage"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// The gce api uses the name of a path rule to match a host rule.
|
||||
hostRulePrefix = "host"
|
||||
|
||||
// DefaultHost is the host used if none is specified. It is a valid value
|
||||
// for the "Host" field recognized by GCE.
|
||||
DefaultHost = "*"
|
||||
|
||||
// DefaultPath is the path used if none is specified. It is a valid path
|
||||
// recognized by GCE.
|
||||
DefaultPath = "/*"
|
||||
|
||||
// A single target proxy/urlmap/forwarding rule is created per loadbalancer.
|
||||
// Tagged with the namespace/name of the Ingress.
|
||||
targetProxyPrefix = "k8s-tp"
|
||||
targetHTTPSProxyPrefix = "k8s-tps"
|
||||
sslCertPrefix = "k8s-ssl"
|
||||
forwardingRulePrefix = "k8s-fw"
|
||||
httpsForwardingRulePrefix = "k8s-fws"
|
||||
urlMapPrefix = "k8s-um"
|
||||
httpDefaultPortRange = "80-80"
|
||||
httpsDefaultPortRange = "443-443"
|
||||
)
|
||||
|
||||
// L7s implements LoadBalancerPool.
|
||||
type L7s struct {
|
||||
cloud LoadBalancers
|
||||
snapshotter storage.Snapshotter
|
||||
// TODO: Remove this field and always ask the BackendPool using the NodePort.
|
||||
glbcDefaultBackend *compute.BackendService
|
||||
defaultBackendPool backends.BackendPool
|
||||
defaultBackendNodePort int64
|
||||
namer utils.Namer
|
||||
}
|
||||
|
||||
// NewLoadBalancerPool returns a new loadbalancer pool.
|
||||
// - cloud: implements LoadBalancers. Used to sync L7 loadbalancer resources
|
||||
// with the cloud.
|
||||
// - defaultBackendPool: a BackendPool used to manage the GCE BackendService for
|
||||
// the default backend.
|
||||
// - defaultBackendNodePort: The nodePort of the Kubernetes service representing
|
||||
// the default backend.
|
||||
func NewLoadBalancerPool(
|
||||
cloud LoadBalancers,
|
||||
defaultBackendPool backends.BackendPool,
|
||||
defaultBackendNodePort int64, namer utils.Namer) LoadBalancerPool {
|
||||
return &L7s{cloud, storage.NewInMemoryPool(), nil, defaultBackendPool, defaultBackendNodePort, namer}
|
||||
}
|
||||
|
||||
func (l *L7s) create(ri *L7RuntimeInfo) (*L7, error) {
|
||||
// Lazily create a default backend so we don't tax users who don't care
|
||||
// about Ingress by consuming 1 of their 3 GCE BackendServices. This
|
||||
// BackendService is deleted when there are no more Ingresses, either
|
||||
// through Sync or Shutdown.
|
||||
if l.glbcDefaultBackend == nil {
|
||||
err := l.defaultBackendPool.Add(l.defaultBackendNodePort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.glbcDefaultBackend, err = l.defaultBackendPool.Get(l.defaultBackendNodePort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &L7{
|
||||
runtimeInfo: ri,
|
||||
Name: l.namer.LBName(ri.Name),
|
||||
cloud: l.cloud,
|
||||
glbcDefaultBackend: l.glbcDefaultBackend,
|
||||
namer: l.namer,
|
||||
sslCert: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get returns the loadbalancer by name.
|
||||
func (l *L7s) Get(name string) (*L7, error) {
|
||||
name = l.namer.LBName(name)
|
||||
lb, exists := l.snapshotter.Get(name)
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("Loadbalancer %v not in pool", name)
|
||||
}
|
||||
return lb.(*L7), nil
|
||||
}
|
||||
|
||||
// Add gets or creates a loadbalancer.
|
||||
// If the loadbalancer already exists, it checks that its edges are valid.
|
||||
func (l *L7s) Add(ri *L7RuntimeInfo) (err error) {
|
||||
name := l.namer.LBName(ri.Name)
|
||||
|
||||
lb, _ := l.Get(name)
|
||||
if lb == nil {
|
||||
glog.Infof("Creating l7 %v", name)
|
||||
lb, err = l.create(ri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Add the lb to the pool, in case we create an UrlMap but run out
|
||||
// of quota in creating the ForwardingRule we still need to cleanup
|
||||
// the UrlMap during GC.
|
||||
defer l.snapshotter.Add(name, lb)
|
||||
|
||||
// Why edge hop for the create?
|
||||
// The loadbalancer is a fictitious resource, it doesn't exist in gce. To
|
||||
// make it exist we need to create a collection of gce resources, done
|
||||
// through the edge hop.
|
||||
if err := lb.edgeHop(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a loadbalancer by name.
|
||||
func (l *L7s) Delete(name string) error {
|
||||
name = l.namer.LBName(name)
|
||||
lb, err := l.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infof("Deleting lb %v", name)
|
||||
if err := lb.Cleanup(); err != nil {
|
||||
return err
|
||||
}
|
||||
l.snapshotter.Delete(name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sync loadbalancers with the given runtime info from the controller.
|
||||
func (l *L7s) Sync(lbs []*L7RuntimeInfo) error {
|
||||
glog.V(3).Infof("Creating loadbalancers %+v", lbs)
|
||||
|
||||
// The default backend is completely managed by the l7 pool.
|
||||
// This includes recreating it if it's deleted, or fixing broken links.
|
||||
if err := l.defaultBackendPool.Sync([]int64{l.defaultBackendNodePort}); err != nil {
|
||||
return err
|
||||
}
|
||||
// create new loadbalancers, perform an edge hop for existing
|
||||
for _, ri := range lbs {
|
||||
if err := l.Add(ri); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Tear down the default backend when there are no more loadbalancers
|
||||
// because the cluster could go down anytime and we'd leak it otherwise.
|
||||
if len(lbs) == 0 {
|
||||
if err := l.defaultBackendPool.Delete(l.defaultBackendNodePort); err != nil {
|
||||
return err
|
||||
}
|
||||
l.glbcDefaultBackend = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GC garbage collects loadbalancers not in the input list.
|
||||
func (l *L7s) GC(names []string) error {
|
||||
knownLoadBalancers := sets.NewString()
|
||||
for _, n := range names {
|
||||
knownLoadBalancers.Insert(l.namer.LBName(n))
|
||||
}
|
||||
pool := l.snapshotter.Snapshot()
|
||||
|
||||
// Delete unknown loadbalancers
|
||||
for name := range pool {
|
||||
if knownLoadBalancers.Has(name) {
|
||||
continue
|
||||
}
|
||||
glog.V(3).Infof("GCing loadbalancer %v", name)
|
||||
if err := l.Delete(name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown logs whether or not the pool is empty.
|
||||
func (l *L7s) Shutdown() error {
|
||||
if err := l.GC([]string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.defaultBackendPool.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infof("Loadbalancer pool shutdown.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// TLSCerts encapsulates .pem encoded TLS information.
|
||||
type TLSCerts struct {
|
||||
// Key is private key.
|
||||
Key string
|
||||
// Cert is a public key.
|
||||
Cert string
|
||||
// Chain is a certificate chain.
|
||||
Chain string
|
||||
}
|
||||
|
||||
// L7RuntimeInfo is info passed to this module from the controller runtime.
|
||||
type L7RuntimeInfo struct {
|
||||
// Name is the name of a loadbalancer.
|
||||
Name string
|
||||
// IP is the desired ip of the loadbalancer, eg from a staticIP.
|
||||
IP string
|
||||
// TLS are the tls certs to use in termination.
|
||||
TLS *TLSCerts
|
||||
// AllowHTTP will not setup :80, if TLS is nil and AllowHTTP is set,
|
||||
// no loadbalancer is created.
|
||||
AllowHTTP bool
|
||||
}
|
||||
|
||||
// L7 represents a single L7 loadbalancer.
|
||||
type L7 struct {
|
||||
Name string
|
||||
// runtimeInfo is non-cloudprovider information passed from the controller.
|
||||
runtimeInfo *L7RuntimeInfo
|
||||
// cloud is an interface to manage loadbalancers in the GCE cloud.
|
||||
cloud LoadBalancers
|
||||
// um is the UrlMap associated with this L7.
|
||||
um *compute.UrlMap
|
||||
// tp is the TargetHTTPProxy associated with this L7.
|
||||
tp *compute.TargetHttpProxy
|
||||
// tps is the TargetHTTPSProxy associated with this L7.
|
||||
tps *compute.TargetHttpsProxy
|
||||
// fw is the GlobalForwardingRule that points to the TargetHTTPProxy.
|
||||
fw *compute.ForwardingRule
|
||||
// fws is the GlobalForwardingRule that points to the TargetHTTPSProxy.
|
||||
fws *compute.ForwardingRule
|
||||
// ip is the static-ip associated with both GlobalForwardingRules.
|
||||
ip *compute.Address
|
||||
// sslCert is the ssl cert associated with the targetHTTPSProxy.
|
||||
// TODO: Make this a custom type that contains crt+key
|
||||
sslCert *compute.SslCertificate
|
||||
// glbcDefaultBacked is the backend to use if no path rules match.
|
||||
// TODO: Expose this to users.
|
||||
glbcDefaultBackend *compute.BackendService
|
||||
// namer is used to compute names of the various sub-components of an L7.
|
||||
namer utils.Namer
|
||||
}
|
||||
|
||||
func (l *L7) checkUrlMap(backend *compute.BackendService) (err error) {
|
||||
if l.glbcDefaultBackend == nil {
|
||||
return fmt.Errorf("Cannot create urlmap without default backend.")
|
||||
}
|
||||
urlMapName := l.namer.Truncate(fmt.Sprintf("%v-%v", urlMapPrefix, l.Name))
|
||||
urlMap, _ := l.cloud.GetUrlMap(urlMapName)
|
||||
if urlMap != nil {
|
||||
glog.V(3).Infof("Url map %v already exists", urlMap.Name)
|
||||
l.um = urlMap
|
||||
return nil
|
||||
}
|
||||
|
||||
glog.Infof("Creating url map %v for backend %v", urlMapName, l.glbcDefaultBackend.Name)
|
||||
urlMap, err = l.cloud.CreateUrlMap(l.glbcDefaultBackend, urlMapName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.um = urlMap
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) checkProxy() (err error) {
|
||||
if l.um == nil {
|
||||
return fmt.Errorf("Cannot create proxy without urlmap.")
|
||||
}
|
||||
proxyName := l.namer.Truncate(fmt.Sprintf("%v-%v", targetProxyPrefix, l.Name))
|
||||
proxy, _ := l.cloud.GetTargetHttpProxy(proxyName)
|
||||
if proxy == nil {
|
||||
glog.Infof("Creating new http proxy for urlmap %v", l.um.Name)
|
||||
proxy, err = l.cloud.CreateTargetHttpProxy(l.um, proxyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.tp = proxy
|
||||
return nil
|
||||
}
|
||||
if !utils.CompareLinks(proxy.UrlMap, l.um.SelfLink) {
|
||||
glog.Infof("Proxy %v has the wrong url map, setting %v overwriting %v",
|
||||
proxy.Name, l.um, proxy.UrlMap)
|
||||
if err := l.cloud.SetUrlMapForTargetHttpProxy(proxy, l.um); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.tp = proxy
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) checkSSLCert() (err error) {
|
||||
// TODO: Currently, GCE only supports a single certificate per static IP
|
||||
// so we don't need to bother with disambiguation. Naming the cert after
|
||||
// the loadbalancer is a simplification.
|
||||
certName := l.namer.Truncate(fmt.Sprintf("%v-%v", sslCertPrefix, l.Name))
|
||||
cert, _ := l.cloud.GetSslCertificate(certName)
|
||||
if cert == nil {
|
||||
glog.Infof("Creating new sslCertificates %v for %v", l.Name, certName)
|
||||
cert, err = l.cloud.CreateSslCertificate(&compute.SslCertificate{
|
||||
Name: certName,
|
||||
Certificate: l.runtimeInfo.TLS.Cert,
|
||||
PrivateKey: l.runtimeInfo.TLS.Key,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.sslCert = cert
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) checkHttpsProxy() (err error) {
|
||||
if l.sslCert == nil {
|
||||
glog.V(3).Infof("No SSL certificates for %v, will not create HTTPS proxy.", l.Name)
|
||||
return nil
|
||||
}
|
||||
if l.um == nil {
|
||||
return fmt.Errorf("No UrlMap for %v, will not create HTTPS proxy.", l.Name)
|
||||
}
|
||||
proxyName := l.namer.Truncate(fmt.Sprintf("%v-%v", targetHTTPSProxyPrefix, l.Name))
|
||||
proxy, _ := l.cloud.GetTargetHttpsProxy(proxyName)
|
||||
if proxy == nil {
|
||||
glog.Infof("Creating new https proxy for urlmap %v", l.um.Name)
|
||||
proxy, err = l.cloud.CreateTargetHttpsProxy(l.um, l.sslCert, proxyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.tps = proxy
|
||||
return nil
|
||||
}
|
||||
if !utils.CompareLinks(proxy.UrlMap, l.um.SelfLink) {
|
||||
glog.Infof("Https proxy %v has the wrong url map, setting %v overwriting %v",
|
||||
proxy.Name, l.um, proxy.UrlMap)
|
||||
if err := l.cloud.SetUrlMapForTargetHttpsProxy(proxy, l.um); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cert := proxy.SslCertificates[0]
|
||||
if !utils.CompareLinks(cert, l.sslCert.SelfLink) {
|
||||
glog.Infof("Https proxy %v has the wrong ssl certs, setting %v overwriting %v",
|
||||
proxy.Name, l.sslCert.SelfLink, cert)
|
||||
if err := l.cloud.SetSslCertificateForTargetHttpsProxy(proxy, l.sslCert); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
glog.V(3).Infof("Created target https proxy %v", proxy.Name)
|
||||
l.tps = proxy
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) checkForwardingRule(name, proxyLink, ip, portRange string) (fw *compute.ForwardingRule, err error) {
|
||||
fw, _ = l.cloud.GetGlobalForwardingRule(name)
|
||||
if fw != nil && (ip != "" && fw.IPAddress != ip || fw.PortRange != portRange) {
|
||||
glog.Warningf("Recreating forwarding rule %v(%v), so it has %v(%v)",
|
||||
fw.IPAddress, fw.PortRange, ip, portRange)
|
||||
if err := l.cloud.DeleteGlobalForwardingRule(name); err != nil {
|
||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fw = nil
|
||||
}
|
||||
if fw == nil {
|
||||
parts := strings.Split(proxyLink, "/")
|
||||
glog.Infof("Creating forwarding rule for proxy %v and ip %v:%v", parts[len(parts)-1:], ip, portRange)
|
||||
fw, err = l.cloud.CreateGlobalForwardingRule(proxyLink, ip, name, portRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// TODO: If the port range and protocol don't match, recreate the rule
|
||||
if utils.CompareLinks(fw.Target, proxyLink) {
|
||||
glog.V(3).Infof("Forwarding rule %v already exists", fw.Name)
|
||||
} else {
|
||||
glog.Infof("Forwarding rule %v has the wrong proxy, setting %v overwriting %v",
|
||||
fw.Name, fw.Target, proxyLink)
|
||||
if err := l.cloud.SetProxyForGlobalForwardingRule(fw, proxyLink); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return fw, nil
|
||||
}
|
||||
|
||||
func (l *L7) checkHttpForwardingRule() (err error) {
|
||||
if l.tp == nil {
|
||||
return fmt.Errorf("Cannot create forwarding rule without proxy.")
|
||||
}
|
||||
var address string
|
||||
if l.ip != nil {
|
||||
address = l.ip.Address
|
||||
}
|
||||
name := l.namer.Truncate(fmt.Sprintf("%v-%v", forwardingRulePrefix, l.Name))
|
||||
fw, err := l.checkForwardingRule(name, l.tp.SelfLink, address, httpDefaultPortRange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.fw = fw
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) checkHttpsForwardingRule() (err error) {
|
||||
if l.tps == nil {
|
||||
glog.V(3).Infof("No https target proxy for %v, not created https forwarding rule", l.Name)
|
||||
return nil
|
||||
}
|
||||
var address string
|
||||
if l.ip != nil {
|
||||
address = l.ip.Address
|
||||
}
|
||||
name := l.namer.Truncate(fmt.Sprintf("%v-%v", httpsForwardingRulePrefix, l.Name))
|
||||
fws, err := l.checkForwardingRule(name, l.tps.SelfLink, address, httpsDefaultPortRange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.fws = fws
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) checkStaticIP() (err error) {
|
||||
if l.fw == nil || l.fw.IPAddress == "" {
|
||||
return fmt.Errorf("Will not create static IP without a forwarding rule.")
|
||||
}
|
||||
staticIPName := l.namer.Truncate(fmt.Sprintf("%v-%v", forwardingRulePrefix, l.Name))
|
||||
ip, _ := l.cloud.GetGlobalStaticIP(staticIPName)
|
||||
if ip == nil {
|
||||
glog.Infof("Creating static ip %v", staticIPName)
|
||||
ip, err = l.cloud.ReserveGlobalStaticIP(staticIPName, l.fw.IPAddress)
|
||||
if err != nil {
|
||||
if utils.IsHTTPErrorCode(err, http.StatusConflict) ||
|
||||
utils.IsHTTPErrorCode(err, http.StatusBadRequest) {
|
||||
glog.V(3).Infof("IP %v(%v) is already reserved, assuming it is OK to use.",
|
||||
l.fw.IPAddress, staticIPName)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.ip = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) edgeHop() error {
|
||||
if err := l.checkUrlMap(l.glbcDefaultBackend); err != nil {
|
||||
return err
|
||||
}
|
||||
if l.runtimeInfo.AllowHTTP {
|
||||
if err := l.edgeHopHttp(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Defer promoting an emphemral to a static IP till it's really needed.
|
||||
if l.runtimeInfo.AllowHTTP && l.runtimeInfo.TLS != nil {
|
||||
if err := l.checkStaticIP(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if l.runtimeInfo.TLS != nil {
|
||||
if err := l.edgeHopHttps(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) edgeHopHttp() error {
|
||||
if err := l.checkProxy(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.checkHttpForwardingRule(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *L7) edgeHopHttps() error {
|
||||
if err := l.checkSSLCert(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.checkHttpsProxy(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.checkHttpsForwardingRule(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIP returns the ip associated with the forwarding rule for this l7.
|
||||
func (l *L7) GetIP() string {
|
||||
if l.fw != nil {
|
||||
return l.fw.IPAddress
|
||||
}
|
||||
if l.fws != nil {
|
||||
return l.fws.IPAddress
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getNameForPathMatcher returns a name for a pathMatcher based on the given host rule.
|
||||
// The host rule can be a regex, the path matcher name used to associate the 2 cannot.
|
||||
func getNameForPathMatcher(hostRule string) string {
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(hostRule))
|
||||
return fmt.Sprintf("%v%v", hostRulePrefix, hex.EncodeToString(hasher.Sum(nil)))
|
||||
}
|
||||
|
||||
// UpdateUrlMap translates the given hostname: endpoint->port mapping into a gce url map.
|
||||
//
|
||||
// HostRule: Conceptually contains all PathRules for a given host.
|
||||
// PathMatcher: Associates a path rule with a host rule. Mostly an optimization.
|
||||
// PathRule: Maps a single path regex to a backend.
|
||||
//
|
||||
// The GCE url map allows multiple hosts to share url->backend mappings without duplication, eg:
|
||||
// Host: foo(PathMatcher1), bar(PathMatcher1,2)
|
||||
// PathMatcher1:
|
||||
// /a -> b1
|
||||
// /b -> b2
|
||||
// PathMatcher2:
|
||||
// /c -> b1
|
||||
// This leads to a lot of complexity in the common case, where all we want is a mapping of
|
||||
// host->{/path: backend}.
|
||||
//
|
||||
// Consider some alternatives:
|
||||
// 1. Using a single backend per PathMatcher:
|
||||
// Host: foo(PathMatcher1,3) bar(PathMatcher1,2,3)
|
||||
// PathMatcher1:
|
||||
// /a -> b1
|
||||
// PathMatcher2:
|
||||
// /c -> b1
|
||||
// PathMatcher3:
|
||||
// /b -> b2
|
||||
// 2. Using a single host per PathMatcher:
|
||||
// Host: foo(PathMatcher1)
|
||||
// PathMatcher1:
|
||||
// /a -> b1
|
||||
// /b -> b2
|
||||
// Host: bar(PathMatcher2)
|
||||
// PathMatcher2:
|
||||
// /a -> b1
|
||||
// /b -> b2
|
||||
// /c -> b1
|
||||
// In the context of kubernetes services, 2 makes more sense, because we
|
||||
// rarely want to lookup backends (service:nodeport). When a service is
|
||||
// deleted, we need to find all host PathMatchers that have the backend
|
||||
// and remove the mapping. When a new path is added to a host (happens
|
||||
// more frequently than service deletion) we just need to lookup the 1
|
||||
// pathmatcher of the host.
|
||||
func (l *L7) UpdateUrlMap(ingressRules utils.GCEURLMap) error {
|
||||
if l.um == nil {
|
||||
return fmt.Errorf("Cannot add url without an urlmap.")
|
||||
}
|
||||
glog.V(3).Infof("Updating urlmap for l7 %v", l.Name)
|
||||
|
||||
// All UrlMaps must have a default backend. If the Ingress has a default
|
||||
// backend, it applies to all host rules as well as to the urlmap itself.
|
||||
// If it doesn't the urlmap might have a stale default, so replace it with
|
||||
// glbc's default backend.
|
||||
defaultBackend := ingressRules.GetDefaultBackend()
|
||||
if defaultBackend != nil {
|
||||
l.um.DefaultService = defaultBackend.SelfLink
|
||||
} else {
|
||||
l.um.DefaultService = l.glbcDefaultBackend.SelfLink
|
||||
}
|
||||
glog.V(3).Infof("Updating url map %+v", ingressRules)
|
||||
|
||||
for hostname, urlToBackend := range ingressRules {
|
||||
// Find the hostrule
|
||||
// Find the path matcher
|
||||
// Add all given endpoint:backends to pathRules in path matcher
|
||||
var hostRule *compute.HostRule
|
||||
pmName := getNameForPathMatcher(hostname)
|
||||
for _, hr := range l.um.HostRules {
|
||||
// TODO: Hostnames must be exact match?
|
||||
if hr.Hosts[0] == hostname {
|
||||
hostRule = hr
|
||||
break
|
||||
}
|
||||
}
|
||||
if hostRule == nil {
|
||||
// This is a new host
|
||||
hostRule = &compute.HostRule{
|
||||
Hosts: []string{hostname},
|
||||
PathMatcher: pmName,
|
||||
}
|
||||
// Why not just clobber existing host rules?
|
||||
// Because we can have multiple loadbalancers point to a single
|
||||
// gce url map when we have IngressClaims.
|
||||
l.um.HostRules = append(l.um.HostRules, hostRule)
|
||||
}
|
||||
var pathMatcher *compute.PathMatcher
|
||||
for _, pm := range l.um.PathMatchers {
|
||||
if pm.Name == hostRule.PathMatcher {
|
||||
pathMatcher = pm
|
||||
break
|
||||
}
|
||||
}
|
||||
if pathMatcher == nil {
|
||||
// This is a dangling or new host
|
||||
pathMatcher = &compute.PathMatcher{Name: pmName}
|
||||
l.um.PathMatchers = append(l.um.PathMatchers, pathMatcher)
|
||||
}
|
||||
pathMatcher.DefaultService = l.um.DefaultService
|
||||
|
||||
// TODO: Every update replaces the entire path map. This will need to
|
||||
// change when we allow joining. Right now we call a single method
|
||||
// to verify current == desired and add new url mappings.
|
||||
pathMatcher.PathRules = []*compute.PathRule{}
|
||||
|
||||
// Longest prefix wins. For equal rules, first hit wins, i.e the second
|
||||
// /foo rule when the first is deleted.
|
||||
for expr, be := range urlToBackend {
|
||||
pathMatcher.PathRules = append(
|
||||
pathMatcher.PathRules, &compute.PathRule{Paths: []string{expr}, Service: be.SelfLink})
|
||||
}
|
||||
}
|
||||
um, err := l.cloud.UpdateUrlMap(l.um)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.um = um
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup deletes resources specific to this l7 in the right order.
|
||||
// forwarding rule -> target proxy -> url map
|
||||
// This leaves backends and health checks, which are shared across loadbalancers.
|
||||
func (l *L7) Cleanup() error {
|
||||
if l.fw != nil {
|
||||
glog.Infof("Deleting global forwarding rule %v", l.fw.Name)
|
||||
if err := l.cloud.DeleteGlobalForwardingRule(l.fw.Name); err != nil {
|
||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.fw = nil
|
||||
}
|
||||
if l.fws != nil {
|
||||
glog.Infof("Deleting global forwarding rule %v", l.fws.Name)
|
||||
if err := l.cloud.DeleteGlobalForwardingRule(l.fws.Name); err != nil {
|
||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
||||
return err
|
||||
}
|
||||
l.fws = nil
|
||||
}
|
||||
}
|
||||
if l.ip != nil {
|
||||
glog.Infof("Deleting static IP %v(%v)", l.ip.Name, l.ip.Address)
|
||||
if err := l.cloud.DeleteGlobalStaticIP(l.ip.Name); err != nil {
|
||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
||||
return err
|
||||
}
|
||||
l.ip = nil
|
||||
}
|
||||
}
|
||||
if l.tps != nil {
|
||||
glog.Infof("Deleting target https proxy %v", l.tps.Name)
|
||||
if err := l.cloud.DeleteTargetHttpsProxy(l.tps.Name); err != nil {
|
||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.tps = nil
|
||||
}
|
||||
if l.sslCert != nil {
|
||||
glog.Infof("Deleting sslcert %v", l.sslCert.Name)
|
||||
if err := l.cloud.DeleteSslCertificate(l.sslCert.Name); err != nil {
|
||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.sslCert = nil
|
||||
}
|
||||
if l.tp != nil {
|
||||
glog.Infof("Deleting target http proxy %v", l.tp.Name)
|
||||
if err := l.cloud.DeleteTargetHttpProxy(l.tp.Name); err != nil {
|
||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.tp = nil
|
||||
}
|
||||
if l.um != nil {
|
||||
glog.Infof("Deleting url map %v", l.um.Name)
|
||||
if err := l.cloud.DeleteUrlMap(l.um.Name); err != nil {
|
||||
if !utils.IsHTTPErrorCode(err, http.StatusNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.um = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getBackendNames returns the names of backends in this L7 urlmap.
|
||||
func (l *L7) getBackendNames() []string {
|
||||
if l.um == nil {
|
||||
return []string{}
|
||||
}
|
||||
beNames := sets.NewString()
|
||||
for _, pathMatcher := range l.um.PathMatchers {
|
||||
for _, pathRule := range pathMatcher.PathRules {
|
||||
// This is gross, but the urlmap only has links to backend services.
|
||||
parts := strings.Split(pathRule.Service, "/")
|
||||
name := parts[len(parts)-1]
|
||||
if name != "" {
|
||||
beNames.Insert(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
// The default Service recorded in the urlMap is a link to the backend.
|
||||
// Note that this can either be user specified, or the L7 controller's
|
||||
// global default.
|
||||
parts := strings.Split(l.um.DefaultService, "/")
|
||||
defaultBackendName := parts[len(parts)-1]
|
||||
if defaultBackendName != "" {
|
||||
beNames.Insert(defaultBackendName)
|
||||
}
|
||||
return beNames.List()
|
||||
}
|
||||
|
||||
// GetLBAnnotations returns the annotations of an l7. This includes it's current status.
|
||||
func GetLBAnnotations(l7 *L7, existing map[string]string, backendPool backends.BackendPool) map[string]string {
|
||||
if existing == nil {
|
||||
existing = map[string]string{}
|
||||
}
|
||||
backends := l7.getBackendNames()
|
||||
backendState := map[string]string{}
|
||||
for _, beName := range backends {
|
||||
backendState[beName] = backendPool.Status(beName)
|
||||
}
|
||||
jsonBackendState := "Unknown"
|
||||
b, err := json.Marshal(backendState)
|
||||
if err == nil {
|
||||
jsonBackendState = string(b)
|
||||
}
|
||||
existing[fmt.Sprintf("%v/url-map", utils.K8sAnnotationPrefix)] = l7.um.Name
|
||||
// Forwarding rule and target proxy might not exist if allowHTTP == false
|
||||
if l7.fw != nil {
|
||||
existing[fmt.Sprintf("%v/forwarding-rule", utils.K8sAnnotationPrefix)] = l7.fw.Name
|
||||
}
|
||||
if l7.tp != nil {
|
||||
existing[fmt.Sprintf("%v/target-proxy", utils.K8sAnnotationPrefix)] = l7.tp.Name
|
||||
}
|
||||
// HTTPs resources might not exist if TLS == nil
|
||||
if l7.fws != nil {
|
||||
existing[fmt.Sprintf("%v/https-forwarding-rule", utils.K8sAnnotationPrefix)] = l7.fws.Name
|
||||
}
|
||||
if l7.tps != nil {
|
||||
existing[fmt.Sprintf("%v/https-target-proxy", utils.K8sAnnotationPrefix)] = l7.tps.Name
|
||||
}
|
||||
if l7.ip != nil {
|
||||
existing[fmt.Sprintf("%v/static-ip", utils.K8sAnnotationPrefix)] = l7.ip.Name
|
||||
}
|
||||
// TODO: We really want to know *when* a backend flipped states.
|
||||
existing[fmt.Sprintf("%v/backends", utils.K8sAnnotationPrefix)] = jsonBackendState
|
||||
return existing
|
||||
}
|
||||
189
controllers/gce/loadbalancers/loadbalancers_test.go
Normal file
189
controllers/gce/loadbalancers/loadbalancers_test.go
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 loadbalancers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
compute "google.golang.org/api/compute/v1"
|
||||
"k8s.io/contrib/ingress/controllers/gce/backends"
|
||||
"k8s.io/contrib/ingress/controllers/gce/healthchecks"
|
||||
"k8s.io/contrib/ingress/controllers/gce/instances"
|
||||
"k8s.io/contrib/ingress/controllers/gce/utils"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
testDefaultBeNodePort = int64(3000)
|
||||
defaultZone = "default-zone"
|
||||
)
|
||||
|
||||
func newFakeLoadBalancerPool(f LoadBalancers, t *testing.T) LoadBalancerPool {
|
||||
fakeBackends := backends.NewFakeBackendServices()
|
||||
fakeIGs := instances.NewFakeInstanceGroups(sets.NewString())
|
||||
fakeHCs := healthchecks.NewFakeHealthChecks()
|
||||
namer := utils.Namer{}
|
||||
healthChecker := healthchecks.NewHealthChecker(fakeHCs, "/", namer)
|
||||
backendPool := backends.NewBackendPool(
|
||||
fakeBackends, healthChecker, instances.NewNodePool(fakeIGs, defaultZone), namer)
|
||||
return NewLoadBalancerPool(f, backendPool, testDefaultBeNodePort, namer)
|
||||
}
|
||||
|
||||
func TestCreateHTTPLoadBalancer(t *testing.T) {
|
||||
// This should NOT create the forwarding rule and target proxy
|
||||
// associated with the HTTPS branch of this loadbalancer.
|
||||
lbInfo := &L7RuntimeInfo{Name: "test", AllowHTTP: true}
|
||||
f := NewFakeLoadBalancers(lbInfo.Name)
|
||||
pool := newFakeLoadBalancerPool(f, t)
|
||||
pool.Add(lbInfo)
|
||||
l7, err := pool.Get(lbInfo.Name)
|
||||
if err != nil || l7 == nil {
|
||||
t.Fatalf("Expected l7 not created")
|
||||
}
|
||||
um, err := f.GetUrlMap(f.umName())
|
||||
if err != nil ||
|
||||
um.DefaultService != pool.(*L7s).glbcDefaultBackend.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
tp, err := f.GetTargetHttpProxy(f.tpName(false))
|
||||
if err != nil || tp.UrlMap != um.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
fw, err := f.GetGlobalForwardingRule(f.fwName(false))
|
||||
if err != nil || fw.Target != tp.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateHTTPSLoadBalancer(t *testing.T) {
|
||||
// This should NOT create the forwarding rule and target proxy
|
||||
// associated with the HTTP branch of this loadbalancer.
|
||||
lbInfo := &L7RuntimeInfo{
|
||||
Name: "test",
|
||||
AllowHTTP: false,
|
||||
TLS: &TLSCerts{Key: "key", Cert: "cert"},
|
||||
}
|
||||
f := NewFakeLoadBalancers(lbInfo.Name)
|
||||
pool := newFakeLoadBalancerPool(f, t)
|
||||
pool.Add(lbInfo)
|
||||
l7, err := pool.Get(lbInfo.Name)
|
||||
if err != nil || l7 == nil {
|
||||
t.Fatalf("Expected l7 not created")
|
||||
}
|
||||
um, err := f.GetUrlMap(f.umName())
|
||||
if err != nil ||
|
||||
um.DefaultService != pool.(*L7s).glbcDefaultBackend.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
tps, err := f.GetTargetHttpsProxy(f.tpName(true))
|
||||
if err != nil || tps.UrlMap != um.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
fws, err := f.GetGlobalForwardingRule(f.fwName(true))
|
||||
if err != nil || fws.Target != tps.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBothLoadBalancers(t *testing.T) {
|
||||
// This should create 2 forwarding rules and target proxies
|
||||
// but they should use the same urlmap, and have the same
|
||||
// static ip.
|
||||
lbInfo := &L7RuntimeInfo{
|
||||
Name: "test",
|
||||
AllowHTTP: true,
|
||||
TLS: &TLSCerts{Key: "key", Cert: "cert"},
|
||||
}
|
||||
f := NewFakeLoadBalancers(lbInfo.Name)
|
||||
pool := newFakeLoadBalancerPool(f, t)
|
||||
pool.Add(lbInfo)
|
||||
l7, err := pool.Get(lbInfo.Name)
|
||||
if err != nil || l7 == nil {
|
||||
t.Fatalf("Expected l7 not created")
|
||||
}
|
||||
um, err := f.GetUrlMap(f.umName())
|
||||
if err != nil ||
|
||||
um.DefaultService != pool.(*L7s).glbcDefaultBackend.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
tps, err := f.GetTargetHttpsProxy(f.tpName(true))
|
||||
if err != nil || tps.UrlMap != um.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
tp, err := f.GetTargetHttpProxy(f.tpName(false))
|
||||
if err != nil || tp.UrlMap != um.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
fws, err := f.GetGlobalForwardingRule(f.fwName(true))
|
||||
if err != nil || fws.Target != tps.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
fw, err := f.GetGlobalForwardingRule(f.fwName(false))
|
||||
if err != nil || fw.Target != tp.SelfLink {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
ip, err := f.GetGlobalStaticIP(f.fwName(false))
|
||||
if err != nil || ip.Address != fw.IPAddress || ip.Address != fws.IPAddress {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUrlMap(t *testing.T) {
|
||||
um1 := utils.GCEURLMap{
|
||||
"bar.example.com": {
|
||||
"/bar2": &compute.BackendService{SelfLink: "bar2svc"},
|
||||
},
|
||||
}
|
||||
um2 := utils.GCEURLMap{
|
||||
"foo.example.com": {
|
||||
"/foo1": &compute.BackendService{SelfLink: "foo1svc"},
|
||||
"/foo2": &compute.BackendService{SelfLink: "foo2svc"},
|
||||
},
|
||||
"bar.example.com": {
|
||||
"/bar1": &compute.BackendService{SelfLink: "bar1svc"},
|
||||
},
|
||||
}
|
||||
um2.PutDefaultBackend(&compute.BackendService{SelfLink: "default"})
|
||||
|
||||
lbInfo := &L7RuntimeInfo{Name: "test", AllowHTTP: true}
|
||||
f := NewFakeLoadBalancers(lbInfo.Name)
|
||||
pool := newFakeLoadBalancerPool(f, t)
|
||||
pool.Add(lbInfo)
|
||||
l7, err := pool.Get(lbInfo.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
for _, ir := range []utils.GCEURLMap{um1, um2} {
|
||||
if err := l7.UpdateUrlMap(ir); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
// The final map doesn't contain /bar2
|
||||
expectedMap := map[string]utils.FakeIngressRuleValueMap{
|
||||
utils.DefaultBackendKey: {
|
||||
utils.DefaultBackendKey: "default",
|
||||
},
|
||||
"foo.example.com": {
|
||||
"/foo1": "foo1svc",
|
||||
"/foo2": "foo2svc",
|
||||
},
|
||||
"bar.example.com": {
|
||||
"/bar1": "bar1svc",
|
||||
},
|
||||
}
|
||||
f.CheckURLMap(t, l7, expectedMap)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue