Split implementations from generic code

This commit is contained in:
Manuel de Brito Fontes 2016-11-10 19:56:29 -03:00
parent d1e8a629ca
commit ed9a416b01
107 changed files with 5777 additions and 3546 deletions

View file

@ -0,0 +1,161 @@
/*
Copyright 2015 The Kubernetes 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 controller
import (
"fmt"
"strings"
"time"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/cache"
"github.com/golang/glog"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
ssl "k8s.io/ingress/core/pkg/net/ssl"
)
// syncSecret keeps in sync Secrets used by Ingress rules with files to allow
// being used in controllers.
func (ic *GenericController) syncSecret(k interface{}) error {
if ic.secretQueue.IsShuttingDown() {
return nil
}
if !ic.controllersInSync() {
time.Sleep(podStoreSyncedPollPeriod)
return fmt.Errorf("deferring sync till endpoints controller has synced")
}
// check if the default certificate is configured
key := fmt.Sprintf("default/%v", defServerName)
_, exists := ic.sslCertTracker.Get(key)
var cert *ingress.SSLCert
var err error
if !exists {
if ic.cfg.DefaultSSLCertificate != "" {
cert, err = ic.getPemCertificate(ic.cfg.DefaultSSLCertificate)
if err != nil {
return err
}
} else {
defCert, defKey := ssl.GetFakeSSLCert()
cert, err = ssl.AddOrUpdateCertAndKey("system-snake-oil-certificate", defCert, defKey, []byte{})
if err != nil {
return nil
}
}
cert.Name = defServerName
cert.Namespace = api.NamespaceDefault
ic.sslCertTracker.Add(key, cert)
}
key = k.(string)
// get secret
secObj, exists, err := ic.secrLister.Store.GetByKey(key)
if err != nil {
return fmt.Errorf("error getting secret %v: %v", key, err)
}
if !exists {
return fmt.Errorf("secret %v was not found", key)
}
sec := secObj.(*api.Secret)
if !ic.secrReferenced(sec.Name, sec.Namespace) {
glog.V(2).Infof("secret %v/%v is not used in Ingress rules. skipping ", sec.Namespace, sec.Name)
return nil
}
cert, err = ic.getPemCertificate(key)
if err != nil {
return err
}
// create certificates and add or update the item in the store
_, exists = ic.sslCertTracker.Get(key)
if exists {
ic.sslCertTracker.Update(key, cert)
return nil
}
ic.sslCertTracker.Add(key, cert)
return nil
}
func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLCert, error) {
secretInterface, exists, err := ic.secrLister.Store.GetByKey(secretName)
if err != nil {
return nil, fmt.Errorf("Error retriveing secret %v: %v", secretName, err)
}
if !exists {
return nil, fmt.Errorf("secret named %v does not exists", secretName)
}
secret := secretInterface.(*api.Secret)
cert, ok := secret.Data[api.TLSCertKey]
if !ok {
return nil, fmt.Errorf("secret named %v has no private key", secretName)
}
key, ok := secret.Data[api.TLSPrivateKeyKey]
if !ok {
return nil, fmt.Errorf("secret named %v has no cert", secretName)
}
ca := secret.Data["ca.crt"]
nsSecName := strings.Replace(secretName, "/", "-", -1)
s, err := ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
if err != nil {
return nil, err
}
s.Name = secret.Name
s.Namespace = secret.Namespace
return s, nil
}
// check if secret is referenced in this controller's config
func (ic *GenericController) secrReferenced(name, namespace string) bool {
for _, ingIf := range ic.ingLister.Store.List() {
ing := ingIf.(*extensions.Ingress)
str, err := parser.GetStringAnnotation("ingress.kubernetes.io/auth-tls-secret", ing)
if err == nil && str == fmt.Sprintf("%v/%v", namespace, name) {
return true
}
if ing.Namespace != namespace {
continue
}
for _, tls := range ing.Spec.TLS {
if tls.SecretName == name {
return true
}
}
}
return false
}
// sslCertTracker ...
type sslCertTracker struct {
cache.ThreadSafeStore
}
func newSSLCertTracker() *sslCertTracker {
return &sslCertTracker{
cache.NewThreadSafeStore(cache.Indexers{}, cache.Indices{}),
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,182 @@
package controller
import (
"flag"
"fmt"
"net/http"
"net/http/pprof"
"os"
"syscall"
"time"
"github.com/golang/glog"
"github.com/spf13/pflag"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/k8s"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/healthz"
kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
// NewIngressController returns a configured Ingress controller ready to start
func NewIngressController(backend ingress.Controller) Interface {
var (
flags = pflag.NewFlagSet("", pflag.ExitOnError)
defaultSvc = flags.String("default-backend-service", "",
`Service used to serve a 404 page for the default backend. Takes the form
namespace/name. The controller uses the first node port of this Service for
the default backend.`)
ingressClass = flags.String("ingress-class", "nginx",
`Name of the ingress class to route through this controller.`)
configMap = flags.String("configmap", "",
`Name of the ConfigMap that contains the custom configuration to use`)
publishSvc = flags.String("publish-service", "",
`Service fronting the ingress controllers. Takes the form
namespace/name. The controller will set the endpoint records on the
ingress objects to reflect those on the service.`)
tcpConfigMapName = flags.String("tcp-services-configmap", "",
`Name of the ConfigMap that contains the definition of the TCP services to expose.
The key in the map indicates the external port to be used. The value is the name of the
service with the format namespace/serviceName and the port of the service could be a
number of the name of the port.
The ports 80 and 443 are not allowed as external ports. This ports are reserved for nginx`)
udpConfigMapName = flags.String("udp-services-configmap", "",
`Name of the ConfigMap that contains the definition of the UDP services to expose.
The key in the map indicates the external port to be used. The value is the name of the
service with the format namespace/serviceName and the port of the service could be a
number of the name of the port.`)
resyncPeriod = flags.Duration("sync-period", 60*time.Second,
`Relist and confirm cloud resources this often.`)
watchNamespace = flags.String("watch-namespace", api.NamespaceAll,
`Namespace to watch for Ingress. Default is to watch all namespaces`)
healthzPort = flags.Int("healthz-port", 10254, "port for healthz endpoint.")
profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
defSSLCertificate = flags.String("default-ssl-certificate", "", `Name of the secret
that contains a SSL certificate to be used as default for a HTTPS catch-all server`)
defHealthzURL = flags.String("health-check-path", "/healthz", `Defines
the URL to be used as health check inside in the default server in NGINX.`)
)
flags.AddGoFlagSet(flag.CommandLine)
flags.Parse(os.Args)
clientConfig := kubectl_util.DefaultClientConfig(flags)
flag.Set("logtostderr", "true")
glog.Info(backend.Info())
if *ingressClass != "" {
glog.Infof("Watching for ingress class: %s", *ingressClass)
}
if *defaultSvc == "" {
glog.Fatalf("Please specify --default-backend-service")
}
kubeconfig, err := restclient.InClusterConfig()
if err != nil {
kubeconfig, err = clientConfig.ClientConfig()
if err != nil {
glog.Fatalf("error configuring the client: %v", err)
}
}
kubeClient, err := clientset.NewForConfig(kubeconfig)
if err != nil {
glog.Fatalf("failed to create client: %v", err)
}
_, err = k8s.IsValidService(kubeClient, *defaultSvc)
if err != nil {
glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
}
glog.Infof("validated %v as the default backend", *defaultSvc)
if *publishSvc != "" {
svc, err := k8s.IsValidService(kubeClient, *publishSvc)
if err != nil {
glog.Fatalf("no service with name %v found: %v", *publishSvc, err)
}
if len(svc.Status.LoadBalancer.Ingress) == 0 {
// We could poll here, but we instead just exit and rely on k8s to restart us
glog.Fatalf("service %s does not (yet) have ingress points", *publishSvc)
}
glog.Infof("service %v validated as source of Ingress status", *publishSvc)
}
if *configMap != "" {
_, _, err = k8s.ParseNameNS(*configMap)
if err != nil {
glog.Fatalf("configmap error: %v", err)
}
}
os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
config := &Configuration{
Client: kubeClient,
ResyncPeriod: *resyncPeriod,
DefaultService: *defaultSvc,
IngressClass: *ingressClass,
Namespace: *watchNamespace,
ConfigMapName: *configMap,
TCPConfigMapName: *tcpConfigMapName,
UDPConfigMapName: *udpConfigMapName,
DefaultSSLCertificate: *defSSLCertificate,
DefaultHealthzURL: *defHealthzURL,
PublishService: *publishSvc,
Backend: backend,
}
ic := newIngressController(config)
go registerHandlers(*profiling, *healthzPort, ic)
return ic
}
func registerHandlers(enableProfiling bool, port int, ic Interface) {
mux := http.NewServeMux()
healthz.InstallHandler(mux, ic)
mux.Handle("/metrics", prometheus.Handler())
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, ic.Info())
})
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
})
if enableProfiling {
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
server := &http.Server{
Addr: fmt.Sprintf(":%v", port),
Handler: mux,
}
glog.Fatal(server.ListenAndServe())
}

View file

@ -0,0 +1,60 @@
/*
Copyright 2015 The Kubernetes 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 controller
import "github.com/prometheus/client_golang/prometheus"
const (
ns = "ingress_controller"
operation = "count"
reloadLabel = "reloads"
)
func init() {
prometheus.MustRegister(reloadOperation)
prometheus.MustRegister(reloadOperationErrors)
reloadOperationErrors.WithLabelValues(reloadLabel).Set(0)
reloadOperation.WithLabelValues(reloadLabel).Set(0)
}
var (
reloadOperation = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: ns,
Name: "success",
Help: "Cumulative number of Ingress controller reload operations",
},
[]string{operation},
)
reloadOperationErrors = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: ns,
Name: "errors",
Help: "Cumulative number of Ingress controller errors during reload operations",
},
[]string{operation},
)
)
func incReloadCount() {
reloadOperation.WithLabelValues(reloadLabel).Inc()
}
func incReloadErrorCount() {
reloadOperationErrors.WithLabelValues(reloadLabel).Inc()
}

View file

@ -0,0 +1,105 @@
/*
Copyright 2015 The Kubernetes 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 controller
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"github.com/golang/glog"
"k8s.io/ingress/core/pkg/ingress/annotations/service"
"k8s.io/kubernetes/pkg/api"
podutil "k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/labels"
)
// checkSvcForUpdate verifies if one of the running pods for a service contains
// named port. If the annotation in the service does not exists or is not equals
// to the port mapping obtained from the pod the service must be updated to reflect
// the current state
func (ic *GenericController) checkSvcForUpdate(svc *api.Service) error {
// get the pods associated with the service
// TODO: switch this to a watch
pods, err := ic.cfg.Client.Pods(svc.Namespace).List(api.ListOptions{
LabelSelector: labels.Set(svc.Spec.Selector).AsSelector(),
})
if err != nil {
return fmt.Errorf("error searching service pods %v/%v: %v", svc.Namespace, svc.Name, err)
}
if len(pods.Items) == 0 {
return nil
}
namedPorts := map[string]string{}
// we need to check only one pod searching for named ports
pod := &pods.Items[0]
glog.V(4).Infof("checking pod %v/%v for named port information", pod.Namespace, pod.Name)
for i := range svc.Spec.Ports {
servicePort := &svc.Spec.Ports[i]
_, err := strconv.Atoi(servicePort.TargetPort.StrVal)
if err != nil {
portNum, err := podutil.FindPort(pod, servicePort)
if err != nil {
glog.V(4).Infof("failed to find port for service %s/%s: %v", portNum, svc.Namespace, svc.Name, err)
continue
}
if servicePort.TargetPort.StrVal == "" {
continue
}
namedPorts[servicePort.TargetPort.StrVal] = fmt.Sprintf("%v", portNum)
}
}
if svc.ObjectMeta.Annotations == nil {
svc.ObjectMeta.Annotations = map[string]string{}
}
curNamedPort := svc.ObjectMeta.Annotations[service.NamedPortAnnotation]
if len(namedPorts) > 0 && !reflect.DeepEqual(curNamedPort, namedPorts) {
data, _ := json.Marshal(namedPorts)
newSvc, err := ic.cfg.Client.Services(svc.Namespace).Get(svc.Name)
if err != nil {
return fmt.Errorf("error getting service %v/%v: %v", svc.Namespace, svc.Name, err)
}
if newSvc.ObjectMeta.Annotations == nil {
newSvc.ObjectMeta.Annotations = map[string]string{}
}
newSvc.ObjectMeta.Annotations[service.NamedPortAnnotation] = string(data)
glog.Infof("updating service %v with new named port mappings", svc.Name)
_, err = ic.cfg.Client.Services(svc.Namespace).Update(newSvc)
if err != nil {
return fmt.Errorf("error syncing service %v/%v: %v", svc.Namespace, svc.Name, err)
}
return nil
}
return nil
}

View file

@ -0,0 +1,95 @@
/*
Copyright 2015 The Kubernetes 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 controller
import (
"strings"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
)
// newDefaultServer return an UpstreamServer to be use as default server that returns 503.
func newDefaultServer() ingress.UpstreamServer {
return ingress.UpstreamServer{Address: "127.0.0.1", Port: "8181"}
}
// newUpstream creates an upstream without servers.
func newUpstream(name string) *ingress.Upstream {
return &ingress.Upstream{
Name: name,
Backends: []ingress.UpstreamServer{},
}
}
func isHostValid(host string, cert *ingress.SSLCert) bool {
if cert == nil {
return false
}
for _, cn := range cert.CN {
if matchHostnames(cn, host) {
return true
}
}
return false
}
func matchHostnames(pattern, host string) bool {
host = strings.TrimSuffix(host, ".")
pattern = strings.TrimSuffix(pattern, ".")
if len(pattern) == 0 || len(host) == 0 {
return false
}
patternParts := strings.Split(pattern, ".")
hostParts := strings.Split(host, ".")
if len(patternParts) != len(hostParts) {
return false
}
for i, patternPart := range patternParts {
if i == 0 && patternPart == "*" {
continue
}
if patternPart != hostParts[i] {
return false
}
}
return true
}
// IsValidClass returns true if the given Ingress either doesn't specify
// the ingress.class annotation, or it's set to the configured in the
// ingress controller.
func IsValidClass(ing *extensions.Ingress, class string) bool {
if class == "" {
return true
}
cc, _ := parser.GetStringAnnotation(ingressClassKey, ing)
if cc == "" {
return true
}
return cc == class
}

View file

@ -0,0 +1,50 @@
/*
Copyright 2015 The Kubernetes 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 controller
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
)
func TestIsValidClass(t *testing.T) {
ing := &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
}
b := IsValidClass(ing, "")
if !b {
t.Error("Expected a valid class (missing annotation)")
}
data := map[string]string{}
data[ingressClassKey] = "custom"
ing.SetAnnotations(data)
b = IsValidClass(ing, "custom")
if !b {
t.Errorf("Expected valid class but %v returned", b)
}
b = IsValidClass(ing, "nginx")
if b {
t.Errorf("Expected invalid class but %v returned", b)
}
}