vendor: remove dep and use vndr

Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
Antonio Murdaca 2017-06-06 09:19:04 +02:00
parent 16f44674a4
commit 148e72d81e
No known key found for this signature in database
GPG key ID: B2BEAD150DE936B9
16131 changed files with 73815 additions and 4235138 deletions

View file

@ -1,155 +0,0 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"controller.go",
"doc.go",
"import_known_versions.go",
"master.go",
"services.go",
],
tags = ["automanaged"],
deps = [
"//cmd/kube-apiserver/app/options:go_default_library",
"//pkg/api:go_default_library",
"//pkg/api/endpoints:go_default_library",
"//pkg/api/install:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apis/apps/install:go_default_library",
"//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/apis/authentication/install:go_default_library",
"//pkg/apis/authentication/v1beta1:go_default_library",
"//pkg/apis/authorization/install:go_default_library",
"//pkg/apis/authorization/v1beta1:go_default_library",
"//pkg/apis/autoscaling/install:go_default_library",
"//pkg/apis/autoscaling/v1:go_default_library",
"//pkg/apis/batch/install:go_default_library",
"//pkg/apis/batch/v1:go_default_library",
"//pkg/apis/certificates/install:go_default_library",
"//pkg/apis/certificates/v1beta1:go_default_library",
"//pkg/apis/componentconfig/install:go_default_library",
"//pkg/apis/extensions/install:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/apis/imagepolicy/install:go_default_library",
"//pkg/apis/policy/install:go_default_library",
"//pkg/apis/policy/v1beta1:go_default_library",
"//pkg/apis/rbac/install:go_default_library",
"//pkg/apis/rbac/v1alpha1:go_default_library",
"//pkg/apis/rbac/v1beta1:go_default_library",
"//pkg/apis/storage/install:go_default_library",
"//pkg/apis/storage/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/genericapiserver/registry/generic:go_default_library",
"//pkg/genericapiserver/registry/generic/registry:go_default_library",
"//pkg/genericapiserver/server:go_default_library",
"//pkg/kubelet/client:go_default_library",
"//pkg/master/thirdparty:go_default_library",
"//pkg/master/tunneler:go_default_library",
"//pkg/registry/apps/rest:go_default_library",
"//pkg/registry/authentication/rest:go_default_library",
"//pkg/registry/authorization/rest:go_default_library",
"//pkg/registry/autoscaling/rest:go_default_library",
"//pkg/registry/batch/rest:go_default_library",
"//pkg/registry/certificates/rest:go_default_library",
"//pkg/registry/core/rangeallocation:go_default_library",
"//pkg/registry/core/rest:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library",
"//pkg/registry/core/service/ipallocator/controller:go_default_library",
"//pkg/registry/core/service/portallocator/controller:go_default_library",
"//pkg/registry/extensions/rest:go_default_library",
"//pkg/registry/policy/rest:go_default_library",
"//pkg/registry/rbac/rest:go_default_library",
"//pkg/registry/storage/rest:go_default_library",
"//pkg/routes:go_default_library",
"//pkg/util/async:go_default_library",
"//pkg/util/intstr:go_default_library",
"//pkg/util/node:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/prometheus/client_golang/prometheus",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/net",
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apiserver/pkg/server/healthz",
],
)
go_test(
name = "go_default_test",
srcs = [
"controller_test.go",
"import_known_versions_test.go",
"master_openapi_test.go",
"master_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/autoscaling/v1:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/batch/v1:go_default_library",
"//pkg/apis/batch/v2alpha1:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/clientset/fake:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/client/testing/core:go_default_library",
"//pkg/generated/openapi:go_default_library",
"//pkg/genericapiserver/server:go_default_library",
"//pkg/kubelet/client:go_default_library",
"//pkg/storage/etcd/testing:go_default_library",
"//pkg/util/intstr:go_default_library",
"//pkg/version:go_default_library",
"//vendor:github.com/go-openapi/loads",
"//vendor:github.com/go-openapi/spec",
"//vendor:github.com/go-openapi/strfmt",
"//vendor:github.com/go-openapi/validate",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/net",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/version",
"//vendor:k8s.io/apiserver/pkg/endpoints/request",
"//vendor:k8s.io/client-go/rest",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/master/ports:all-srcs",
"//pkg/master/thirdparty:all-srcs",
"//pkg/master/tunneler:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -1,40 +0,0 @@
approvers:
- derekwaynecarr
- lavalamp
- mikedanese
- nikhiljindal
- wojtek-t
reviewers:
- thockin
- lavalamp
- smarterclayton
- wojtek-t
- deads2k
- yujuhong
- derekwaynecarr
- caesarxuchao
- mikedanese
- liggitt
- nikhiljindal
- bprashanth
- gmarek
- erictune
- davidopp
- pmorie
- sttts
- dchen1107
- saad-ali
- luxas
- janetkuo
- justinsb
- roberthbailey
- ncdc
- timstclair
- mwielgus
- timothysc
- soltysh
- piosz
- madhusudancs
- hongchaodeng
- krousey
- jszczepkowski

View file

@ -1,465 +0,0 @@
/*
Copyright 2014 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 master
import (
"fmt"
"net"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/endpoints"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
genericapiserver "k8s.io/kubernetes/pkg/genericapiserver/server"
"k8s.io/kubernetes/pkg/registry/core/rangeallocation"
corerest "k8s.io/kubernetes/pkg/registry/core/rest"
servicecontroller "k8s.io/kubernetes/pkg/registry/core/service/ipallocator/controller"
portallocatorcontroller "k8s.io/kubernetes/pkg/registry/core/service/portallocator/controller"
"k8s.io/kubernetes/pkg/util/async"
"k8s.io/kubernetes/pkg/util/intstr"
)
const kubernetesServiceName = "kubernetes"
// Controller is the controller manager for the core bootstrap Kubernetes controller
// loops, which manage creating the "kubernetes" service, the "default" and "kube-system"
// namespace, and provide the IP repair check on service IPs
type Controller struct {
ServiceClient coreclient.ServicesGetter
NamespaceClient coreclient.NamespacesGetter
ServiceClusterIPRegistry rangeallocation.RangeRegistry
ServiceClusterIPInterval time.Duration
ServiceClusterIPRange net.IPNet
ServiceNodePortRegistry rangeallocation.RangeRegistry
ServiceNodePortInterval time.Duration
ServiceNodePortRange utilnet.PortRange
EndpointReconciler EndpointReconciler
EndpointInterval time.Duration
SystemNamespaces []string
SystemNamespacesInterval time.Duration
PublicIP net.IP
// ServiceIP indicates where the kubernetes service will live. It may not be nil.
ServiceIP net.IP
ServicePort int
ExtraServicePorts []api.ServicePort
ExtraEndpointPorts []api.EndpointPort
PublicServicePort int
KubernetesServiceNodePort int
runner *async.Runner
}
// NewBootstrapController returns a controller for watching the core capabilities of the master
func (c *Config) NewBootstrapController(legacyRESTStorage corerest.LegacyRESTStorage, serviceClient coreclient.ServicesGetter, nsClient coreclient.NamespacesGetter) *Controller {
return &Controller{
ServiceClient: serviceClient,
NamespaceClient: nsClient,
EndpointReconciler: c.EndpointReconcilerConfig.Reconciler,
EndpointInterval: c.EndpointReconcilerConfig.Interval,
SystemNamespaces: []string{metav1.NamespaceSystem},
SystemNamespacesInterval: 1 * time.Minute,
ServiceClusterIPRegistry: legacyRESTStorage.ServiceClusterIPAllocator,
ServiceClusterIPRange: c.ServiceIPRange,
ServiceClusterIPInterval: 3 * time.Minute,
ServiceNodePortRegistry: legacyRESTStorage.ServiceNodePortAllocator,
ServiceNodePortRange: c.ServiceNodePortRange,
ServiceNodePortInterval: 3 * time.Minute,
PublicIP: c.GenericConfig.PublicAddress,
ServiceIP: c.APIServerServiceIP,
ServicePort: c.APIServerServicePort,
ExtraServicePorts: c.ExtraServicePorts,
ExtraEndpointPorts: c.ExtraEndpointPorts,
PublicServicePort: c.GenericConfig.ReadWritePort,
KubernetesServiceNodePort: c.KubernetesServiceNodePort,
}
}
func (c *Controller) PostStartHook(hookContext genericapiserver.PostStartHookContext) error {
c.Start()
return nil
}
// Start begins the core controller loops that must exist for bootstrapping
// a cluster.
func (c *Controller) Start() {
if c.runner != nil {
return
}
repairClusterIPs := servicecontroller.NewRepair(c.ServiceClusterIPInterval, c.ServiceClient, &c.ServiceClusterIPRange, c.ServiceClusterIPRegistry)
repairNodePorts := portallocatorcontroller.NewRepair(c.ServiceNodePortInterval, c.ServiceClient, c.ServiceNodePortRange, c.ServiceNodePortRegistry)
// run all of the controllers once prior to returning from Start.
if err := repairClusterIPs.RunOnce(); err != nil {
// If we fail to repair cluster IPs apiserver is useless. We should restart and retry.
glog.Fatalf("Unable to perform initial IP allocation check: %v", err)
}
if err := repairNodePorts.RunOnce(); err != nil {
// If we fail to repair node ports apiserver is useless. We should restart and retry.
glog.Fatalf("Unable to perform initial service nodePort check: %v", err)
}
// Service definition is reconciled during first run to correct port and type per expectations.
if err := c.UpdateKubernetesService(true); err != nil {
glog.Errorf("Unable to perform initial Kubernetes service initialization: %v", err)
}
c.runner = async.NewRunner(c.RunKubernetesNamespaces, c.RunKubernetesService, repairClusterIPs.RunUntil, repairNodePorts.RunUntil)
c.runner.Start()
}
// RunKubernetesNamespaces periodically makes sure that all internal namespaces exist
func (c *Controller) RunKubernetesNamespaces(ch chan struct{}) {
wait.Until(func() {
// Loop the system namespace list, and create them if they do not exist
for _, ns := range c.SystemNamespaces {
if err := c.CreateNamespaceIfNeeded(ns); err != nil {
runtime.HandleError(fmt.Errorf("unable to create required kubernetes system namespace %s: %v", ns, err))
}
}
}, c.SystemNamespacesInterval, ch)
}
// RunKubernetesService periodically updates the kubernetes service
func (c *Controller) RunKubernetesService(ch chan struct{}) {
wait.Until(func() {
// Service definition is not reconciled after first
// run, ports and type will be corrected only during
// start.
if err := c.UpdateKubernetesService(false); err != nil {
runtime.HandleError(fmt.Errorf("unable to sync kubernetes service: %v", err))
}
}, c.EndpointInterval, ch)
}
// UpdateKubernetesService attempts to update the default Kube service.
func (c *Controller) UpdateKubernetesService(reconcile bool) error {
// Update service & endpoint records.
// TODO: when it becomes possible to change this stuff,
// stop polling and start watching.
// TODO: add endpoints of all replicas, not just the elected master.
if err := c.CreateNamespaceIfNeeded(metav1.NamespaceDefault); err != nil {
return err
}
servicePorts, serviceType := createPortAndServiceSpec(c.ServicePort, c.KubernetesServiceNodePort, "https", c.ExtraServicePorts)
if err := c.CreateOrUpdateMasterServiceIfNeeded(kubernetesServiceName, c.ServiceIP, servicePorts, serviceType, reconcile); err != nil {
return err
}
endpointPorts := createEndpointPortSpec(c.PublicServicePort, "https", c.ExtraEndpointPorts)
if err := c.EndpointReconciler.ReconcileEndpoints(kubernetesServiceName, c.PublicIP, endpointPorts, reconcile); err != nil {
return err
}
return nil
}
// CreateNamespaceIfNeeded will create a namespace if it doesn't already exist
func (c *Controller) CreateNamespaceIfNeeded(ns string) error {
if _, err := c.NamespaceClient.Namespaces().Get(ns, metav1.GetOptions{}); err == nil {
// the namespace already exists
return nil
}
newNs := &api.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
Namespace: "",
},
}
_, err := c.NamespaceClient.Namespaces().Create(newNs)
if err != nil && errors.IsAlreadyExists(err) {
err = nil
}
return err
}
// createPortAndServiceSpec creates an array of service ports.
// If the NodePort value is 0, just the servicePort is used, otherwise, a node port is exposed.
func createPortAndServiceSpec(servicePort int, nodePort int, servicePortName string, extraServicePorts []api.ServicePort) ([]api.ServicePort, api.ServiceType) {
//Use the Cluster IP type for the service port if NodePort isn't provided.
//Otherwise, we will be binding the master service to a NodePort.
servicePorts := []api.ServicePort{{Protocol: api.ProtocolTCP,
Port: int32(servicePort),
Name: servicePortName,
TargetPort: intstr.FromInt(servicePort)}}
serviceType := api.ServiceTypeClusterIP
if nodePort > 0 {
servicePorts[0].NodePort = int32(nodePort)
serviceType = api.ServiceTypeNodePort
}
if extraServicePorts != nil {
servicePorts = append(servicePorts, extraServicePorts...)
}
return servicePorts, serviceType
}
// createEndpointPortSpec creates an array of endpoint ports
func createEndpointPortSpec(endpointPort int, endpointPortName string, extraEndpointPorts []api.EndpointPort) []api.EndpointPort {
endpointPorts := []api.EndpointPort{{Protocol: api.ProtocolTCP,
Port: int32(endpointPort),
Name: endpointPortName,
}}
if extraEndpointPorts != nil {
endpointPorts = append(endpointPorts, extraEndpointPorts...)
}
return endpointPorts
}
// CreateMasterServiceIfNeeded will create the specified service if it
// doesn't already exist.
func (c *Controller) CreateOrUpdateMasterServiceIfNeeded(serviceName string, serviceIP net.IP, servicePorts []api.ServicePort, serviceType api.ServiceType, reconcile bool) error {
if s, err := c.ServiceClient.Services(metav1.NamespaceDefault).Get(serviceName, metav1.GetOptions{}); err == nil {
// The service already exists.
if reconcile {
if svc, updated := getMasterServiceUpdateIfNeeded(s, servicePorts, serviceType); updated {
glog.Warningf("Resetting master service %q to %#v", serviceName, svc)
_, err := c.ServiceClient.Services(metav1.NamespaceDefault).Update(svc)
return err
}
}
return nil
}
svc := &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
Namespace: metav1.NamespaceDefault,
Labels: map[string]string{"provider": "kubernetes", "component": "apiserver"},
},
Spec: api.ServiceSpec{
Ports: servicePorts,
// maintained by this code, not by the pod selector
Selector: nil,
ClusterIP: serviceIP.String(),
SessionAffinity: api.ServiceAffinityClientIP,
Type: serviceType,
},
}
_, err := c.ServiceClient.Services(metav1.NamespaceDefault).Create(svc)
if errors.IsAlreadyExists(err) {
return c.CreateOrUpdateMasterServiceIfNeeded(serviceName, serviceIP, servicePorts, serviceType, reconcile)
}
return err
}
// EndpointReconciler knows how to reconcile the endpoints for the apiserver service.
type EndpointReconciler interface {
// ReconcileEndpoints sets the endpoints for the given apiserver service (ro or rw).
// ReconcileEndpoints expects that the endpoints objects it manages will all be
// managed only by ReconcileEndpoints; therefore, to understand this, you need only
// understand the requirements.
//
// Requirements:
// * All apiservers MUST use the same ports for their {rw, ro} services.
// * All apiservers MUST use ReconcileEndpoints and only ReconcileEndpoints to manage the
// endpoints for their {rw, ro} services.
// * ReconcileEndpoints is called periodically from all apiservers.
ReconcileEndpoints(serviceName string, ip net.IP, endpointPorts []api.EndpointPort, reconcilePorts bool) error
}
// masterCountEndpointReconciler reconciles endpoints based on a specified expected number of
// masters. masterCountEndpointReconciler implements EndpointReconciler.
type masterCountEndpointReconciler struct {
masterCount int
endpointClient coreclient.EndpointsGetter
}
var _ EndpointReconciler = &masterCountEndpointReconciler{}
// NewMasterCountEndpointReconciler creates a new EndpointReconciler that reconciles based on a
// specified expected number of masters.
func NewMasterCountEndpointReconciler(masterCount int, endpointClient coreclient.EndpointsGetter) *masterCountEndpointReconciler {
return &masterCountEndpointReconciler{
masterCount: masterCount,
endpointClient: endpointClient,
}
}
// ReconcileEndpoints sets the endpoints for the given apiserver service (ro or rw).
// ReconcileEndpoints expects that the endpoints objects it manages will all be
// managed only by ReconcileEndpoints; therefore, to understand this, you need only
// understand the requirements and the body of this function.
//
// Requirements:
// * All apiservers MUST use the same ports for their {rw, ro} services.
// * All apiservers MUST use ReconcileEndpoints and only ReconcileEndpoints to manage the
// endpoints for their {rw, ro} services.
// * All apiservers MUST know and agree on the number of apiservers expected
// to be running (c.masterCount).
// * ReconcileEndpoints is called periodically from all apiservers.
func (r *masterCountEndpointReconciler) ReconcileEndpoints(serviceName string, ip net.IP, endpointPorts []api.EndpointPort, reconcilePorts bool) error {
e, err := r.endpointClient.Endpoints(metav1.NamespaceDefault).Get(serviceName, metav1.GetOptions{})
if err != nil {
e = &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
Namespace: metav1.NamespaceDefault,
},
}
}
if errors.IsNotFound(err) {
// Simply create non-existing endpoints for the service.
e.Subsets = []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: ip.String()}},
Ports: endpointPorts,
}}
_, err = r.endpointClient.Endpoints(metav1.NamespaceDefault).Create(e)
return err
}
// First, determine if the endpoint is in the format we expect (one
// subset, ports matching endpointPorts, N IP addresses).
formatCorrect, ipCorrect, portsCorrect := checkEndpointSubsetFormat(e, ip.String(), endpointPorts, r.masterCount, reconcilePorts)
if !formatCorrect {
// Something is egregiously wrong, just re-make the endpoints record.
e.Subsets = []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: ip.String()}},
Ports: endpointPorts,
}}
glog.Warningf("Resetting endpoints for master service %q to %#v", serviceName, e)
_, err = r.endpointClient.Endpoints(metav1.NamespaceDefault).Update(e)
return err
}
if ipCorrect && portsCorrect {
return nil
}
if !ipCorrect {
// We *always* add our own IP address.
e.Subsets[0].Addresses = append(e.Subsets[0].Addresses, api.EndpointAddress{IP: ip.String()})
// Lexicographic order is retained by this step.
e.Subsets = endpoints.RepackSubsets(e.Subsets)
// If too many IP addresses, remove the ones lexicographically after our
// own IP address. Given the requirements stated at the top of
// this function, this should cause the list of IP addresses to
// become eventually correct.
if addrs := &e.Subsets[0].Addresses; len(*addrs) > r.masterCount {
// addrs is a pointer because we're going to mutate it.
for i, addr := range *addrs {
if addr.IP == ip.String() {
for len(*addrs) > r.masterCount {
// wrap around if necessary.
remove := (i + 1) % len(*addrs)
*addrs = append((*addrs)[:remove], (*addrs)[remove+1:]...)
}
break
}
}
}
}
if !portsCorrect {
// Reset ports.
e.Subsets[0].Ports = endpointPorts
}
glog.Warningf("Resetting endpoints for master service %q to %v", serviceName, e)
_, err = r.endpointClient.Endpoints(metav1.NamespaceDefault).Update(e)
return err
}
// Determine if the endpoint is in the format ReconcileEndpoints expects.
//
// Return values:
// * formatCorrect is true if exactly one subset is found.
// * ipCorrect is true when current master's IP is found and the number
// of addresses is less than or equal to the master count.
// * portsCorrect is true when endpoint ports exactly match provided ports.
// portsCorrect is only evaluated when reconcilePorts is set to true.
func checkEndpointSubsetFormat(e *api.Endpoints, ip string, ports []api.EndpointPort, count int, reconcilePorts bool) (formatCorrect bool, ipCorrect bool, portsCorrect bool) {
if len(e.Subsets) != 1 {
return false, false, false
}
sub := &e.Subsets[0]
portsCorrect = true
if reconcilePorts {
if len(sub.Ports) != len(ports) {
portsCorrect = false
}
for i, port := range ports {
if len(sub.Ports) <= i || port != sub.Ports[i] {
portsCorrect = false
break
}
}
}
for _, addr := range sub.Addresses {
if addr.IP == ip {
ipCorrect = len(sub.Addresses) <= count
break
}
}
return true, ipCorrect, portsCorrect
}
// * getMasterServiceUpdateIfNeeded sets service attributes for the
// given apiserver service.
// * getMasterServiceUpdateIfNeeded expects that the service object it
// manages will be managed only by getMasterServiceUpdateIfNeeded;
// therefore, to understand this, you need only understand the
// requirements and the body of this function.
// * getMasterServiceUpdateIfNeeded ensures that the correct ports are
// are set.
//
// Requirements:
// * All apiservers MUST use getMasterServiceUpdateIfNeeded and only
// getMasterServiceUpdateIfNeeded to manage service attributes
// * updateMasterService is called periodically from all apiservers.
func getMasterServiceUpdateIfNeeded(svc *api.Service, servicePorts []api.ServicePort, serviceType api.ServiceType) (s *api.Service, updated bool) {
// Determine if the service is in the format we expect
// (servicePorts are present and service type matches)
formatCorrect := checkServiceFormat(svc, servicePorts, serviceType)
if formatCorrect {
return svc, false
}
svc.Spec.Ports = servicePorts
svc.Spec.Type = serviceType
return svc, true
}
// Determine if the service is in the correct format
// getMasterServiceUpdateIfNeeded expects (servicePorts are correct
// and service type matches).
func checkServiceFormat(s *api.Service, ports []api.ServicePort, serviceType api.ServiceType) (formatCorrect bool) {
if s.Spec.Type != serviceType {
return false
}
if len(ports) != len(s.Spec.Ports) {
return false
}
for i, port := range ports {
if port != s.Spec.Ports[i] {
return false
}
}
return true
}

View file

@ -1,947 +0,0 @@
/*
Copyright 2014 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 master
import (
"net"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/util/intstr"
)
func TestReconcileEndpoints(t *testing.T) {
ns := metav1.NamespaceDefault
om := func(name string) metav1.ObjectMeta {
return metav1.ObjectMeta{Namespace: ns, Name: name}
}
reconcile_tests := []struct {
testName string
serviceName string
ip string
endpointPorts []api.EndpointPort
additionalMasters int
endpoints *api.EndpointsList
expectUpdate *api.Endpoints // nil means none expected
expectCreate *api.Endpoints // nil means none expected
}{
{
testName: "no existing endpoints",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
endpoints: nil,
expectCreate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints satisfy",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
},
{
testName: "existing endpoints satisfy but too many",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}, {IP: "4.3.2.1"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints satisfy but too many + extra masters",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
additionalMasters: 3,
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{
{IP: "1.2.3.4"},
{IP: "4.3.2.1"},
{IP: "4.3.2.2"},
{IP: "4.3.2.3"},
{IP: "4.3.2.4"},
},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{
{IP: "1.2.3.4"},
{IP: "4.3.2.2"},
{IP: "4.3.2.3"},
{IP: "4.3.2.4"},
},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints satisfy but too many + extra masters + delete first",
serviceName: "foo",
ip: "4.3.2.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
additionalMasters: 3,
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{
{IP: "1.2.3.4"},
{IP: "4.3.2.1"},
{IP: "4.3.2.2"},
{IP: "4.3.2.3"},
{IP: "4.3.2.4"},
},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{
{IP: "4.3.2.1"},
{IP: "4.3.2.2"},
{IP: "4.3.2.3"},
{IP: "4.3.2.4"},
},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints satisfy and endpoint addresses length less than master count",
serviceName: "foo",
ip: "4.3.2.2",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
additionalMasters: 3,
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{
{IP: "4.3.2.1"},
{IP: "4.3.2.2"},
},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: nil,
},
{
testName: "existing endpoints current IP missing and address length less than master count",
serviceName: "foo",
ip: "4.3.2.2",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
additionalMasters: 3,
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{
{IP: "4.3.2.1"},
},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{
{IP: "4.3.2.1"},
{IP: "4.3.2.2"},
},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints wrong name",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("bar"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectCreate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints wrong IP",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "4.3.2.1"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints wrong port",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 9090, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints wrong protocol",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "UDP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints wrong port name",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "baz", Port: 8080, Protocol: "TCP"}},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "baz", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "existing endpoints extra service ports satisfy",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{
{Name: "foo", Port: 8080, Protocol: "TCP"},
{Name: "bar", Port: 1000, Protocol: "TCP"},
{Name: "baz", Port: 1010, Protocol: "TCP"},
},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{
{Name: "foo", Port: 8080, Protocol: "TCP"},
{Name: "bar", Port: 1000, Protocol: "TCP"},
{Name: "baz", Port: 1010, Protocol: "TCP"},
},
}},
}},
},
},
{
testName: "existing endpoints extra service ports missing port",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{
{Name: "foo", Port: 8080, Protocol: "TCP"},
{Name: "bar", Port: 1000, Protocol: "TCP"},
},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{
{Name: "foo", Port: 8080, Protocol: "TCP"},
{Name: "bar", Port: 1000, Protocol: "TCP"},
},
}},
},
},
}
for _, test := range reconcile_tests {
fakeClient := fake.NewSimpleClientset()
if test.endpoints != nil {
fakeClient = fake.NewSimpleClientset(test.endpoints)
}
reconciler := NewMasterCountEndpointReconciler(test.additionalMasters+1, fakeClient.Core())
err := reconciler.ReconcileEndpoints(test.serviceName, net.ParseIP(test.ip), test.endpointPorts, true)
if err != nil {
t.Errorf("case %q: unexpected error: %v", test.testName, err)
}
updates := []core.UpdateAction{}
for _, action := range fakeClient.Actions() {
if action.GetVerb() != "update" {
continue
}
updates = append(updates, action.(core.UpdateAction))
}
if test.expectUpdate != nil {
if len(updates) != 1 {
t.Errorf("case %q: unexpected updates: %v", test.testName, updates)
} else if e, a := test.expectUpdate, updates[0].GetObject(); !reflect.DeepEqual(e, a) {
t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a)
}
}
if test.expectUpdate == nil && len(updates) > 0 {
t.Errorf("case %q: no update expected, yet saw: %v", test.testName, updates)
}
creates := []core.CreateAction{}
for _, action := range fakeClient.Actions() {
if action.GetVerb() != "create" {
continue
}
creates = append(creates, action.(core.CreateAction))
}
if test.expectCreate != nil {
if len(creates) != 1 {
t.Errorf("case %q: unexpected creates: %v", test.testName, creates)
} else if e, a := test.expectCreate, creates[0].GetObject(); !reflect.DeepEqual(e, a) {
t.Errorf("case %q: expected create:\n%#v\ngot:\n%#v\n", test.testName, e, a)
}
}
if test.expectCreate == nil && len(creates) > 0 {
t.Errorf("case %q: no create expected, yet saw: %v", test.testName, creates)
}
}
non_reconcile_tests := []struct {
testName string
serviceName string
ip string
endpointPorts []api.EndpointPort
additionalMasters int
endpoints *api.EndpointsList
expectUpdate *api.Endpoints // nil means none expected
expectCreate *api.Endpoints // nil means none expected
}{
{
testName: "existing endpoints extra service ports missing port no update",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{
{Name: "foo", Port: 8080, Protocol: "TCP"},
{Name: "bar", Port: 1000, Protocol: "TCP"},
},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: nil,
},
{
testName: "existing endpoints extra service ports, wrong ports, wrong IP",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{
{Name: "foo", Port: 8080, Protocol: "TCP"},
{Name: "bar", Port: 1000, Protocol: "TCP"},
},
endpoints: &api.EndpointsList{
Items: []api.Endpoints{{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "4.3.2.1"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
}},
},
expectUpdate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
{
testName: "no existing endpoints",
serviceName: "foo",
ip: "1.2.3.4",
endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
endpoints: nil,
expectCreate: &api.Endpoints{
ObjectMeta: om("foo"),
Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}},
Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}},
}},
},
},
}
for _, test := range non_reconcile_tests {
fakeClient := fake.NewSimpleClientset()
if test.endpoints != nil {
fakeClient = fake.NewSimpleClientset(test.endpoints)
}
reconciler := NewMasterCountEndpointReconciler(test.additionalMasters+1, fakeClient.Core())
err := reconciler.ReconcileEndpoints(test.serviceName, net.ParseIP(test.ip), test.endpointPorts, false)
if err != nil {
t.Errorf("case %q: unexpected error: %v", test.testName, err)
}
updates := []core.UpdateAction{}
for _, action := range fakeClient.Actions() {
if action.GetVerb() != "update" {
continue
}
updates = append(updates, action.(core.UpdateAction))
}
if test.expectUpdate != nil {
if len(updates) != 1 {
t.Errorf("case %q: unexpected updates: %v", test.testName, updates)
} else if e, a := test.expectUpdate, updates[0].GetObject(); !reflect.DeepEqual(e, a) {
t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a)
}
}
if test.expectUpdate == nil && len(updates) > 0 {
t.Errorf("case %q: no update expected, yet saw: %v", test.testName, updates)
}
creates := []core.CreateAction{}
for _, action := range fakeClient.Actions() {
if action.GetVerb() != "create" {
continue
}
creates = append(creates, action.(core.CreateAction))
}
if test.expectCreate != nil {
if len(creates) != 1 {
t.Errorf("case %q: unexpected creates: %v", test.testName, creates)
} else if e, a := test.expectCreate, creates[0].GetObject(); !reflect.DeepEqual(e, a) {
t.Errorf("case %q: expected create:\n%#v\ngot:\n%#v\n", test.testName, e, a)
}
}
if test.expectCreate == nil && len(creates) > 0 {
t.Errorf("case %q: no create expected, yet saw: %v", test.testName, creates)
}
}
}
func TestCreateOrUpdateMasterService(t *testing.T) {
ns := metav1.NamespaceDefault
om := func(name string) metav1.ObjectMeta {
return metav1.ObjectMeta{Namespace: ns, Name: name}
}
create_tests := []struct {
testName string
serviceName string
servicePorts []api.ServicePort
serviceType api.ServiceType
expectCreate *api.Service // nil means none expected
}{
{
testName: "service does not exist",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
expectCreate: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
},
}
for _, test := range create_tests {
master := Controller{}
fakeClient := fake.NewSimpleClientset()
master.ServiceClient = fakeClient.Core()
master.CreateOrUpdateMasterServiceIfNeeded(test.serviceName, net.ParseIP("1.2.3.4"), test.servicePorts, test.serviceType, false)
creates := []core.CreateAction{}
for _, action := range fakeClient.Actions() {
if action.GetVerb() == "create" {
creates = append(creates, action.(core.CreateAction))
}
}
if test.expectCreate != nil {
if len(creates) != 1 {
t.Errorf("case %q: unexpected creations: %v", test.testName, creates)
} else {
obj := creates[0].GetObject()
if e, a := test.expectCreate.Spec, obj.(*api.Service).Spec; !reflect.DeepEqual(e, a) {
t.Errorf("case %q: expected create:\n%#v\ngot:\n%#v\n", test.testName, e, a)
}
}
}
if test.expectCreate == nil && len(creates) > 1 {
t.Errorf("case %q: no create expected, yet saw: %v", test.testName, creates)
}
}
reconcile_tests := []struct {
testName string
serviceName string
servicePorts []api.ServicePort
serviceType api.ServiceType
service *api.Service
expectUpdate *api.Service // nil means none expected
}{
{
testName: "service definition wrong port",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8000, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
expectUpdate: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
},
{
testName: "service definition missing port",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
{Name: "baz", Port: 1000, Protocol: "TCP", TargetPort: intstr.FromInt(1000)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
expectUpdate: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
{Name: "baz", Port: 1000, Protocol: "TCP", TargetPort: intstr.FromInt(1000)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
},
{
testName: "service definition incorrect port",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "bar", Port: 1000, Protocol: "UDP", TargetPort: intstr.FromInt(1000)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
expectUpdate: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
},
{
testName: "service definition incorrect port name",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 1000, Protocol: "UDP", TargetPort: intstr.FromInt(1000)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
expectUpdate: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
},
{
testName: "service definition incorrect target port",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(1000)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
expectUpdate: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
},
{
testName: "service definition incorrect protocol",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "UDP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
expectUpdate: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
},
{
testName: "service definition has incorrect type",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeNodePort,
},
},
expectUpdate: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
},
{
testName: "service definition satisfies",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
expectUpdate: nil,
},
}
for _, test := range reconcile_tests {
master := Controller{}
fakeClient := fake.NewSimpleClientset(test.service)
master.ServiceClient = fakeClient.Core()
err := master.CreateOrUpdateMasterServiceIfNeeded(test.serviceName, net.ParseIP("1.2.3.4"), test.servicePorts, test.serviceType, true)
if err != nil {
t.Errorf("case %q: unexpected error: %v", test.testName, err)
}
updates := []core.UpdateAction{}
for _, action := range fakeClient.Actions() {
if action.GetVerb() == "update" {
updates = append(updates, action.(core.UpdateAction))
}
}
if test.expectUpdate != nil {
if len(updates) != 1 {
t.Errorf("case %q: unexpected updates: %v", test.testName, updates)
} else {
obj := updates[0].GetObject()
if e, a := test.expectUpdate.Spec, obj.(*api.Service).Spec; !reflect.DeepEqual(e, a) {
t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a)
}
}
}
if test.expectUpdate == nil && len(updates) > 0 {
t.Errorf("case %q: no update expected, yet saw: %v", test.testName, updates)
}
}
non_reconcile_tests := []struct {
testName string
serviceName string
servicePorts []api.ServicePort
serviceType api.ServiceType
service *api.Service
expectUpdate *api.Service // nil means none expected
}{
{
testName: "service definition wrong port, no expected update",
serviceName: "foo",
servicePorts: []api.ServicePort{
{Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)},
},
serviceType: api.ServiceTypeClusterIP,
service: &api.Service{
ObjectMeta: om("foo"),
Spec: api.ServiceSpec{
Ports: []api.ServicePort{
{Name: "foo", Port: 1000, Protocol: "TCP", TargetPort: intstr.FromInt(1000)},
},
Selector: nil,
ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP,
Type: api.ServiceTypeClusterIP,
},
},
expectUpdate: nil,
},
}
for _, test := range non_reconcile_tests {
master := Controller{}
fakeClient := fake.NewSimpleClientset(test.service)
master.ServiceClient = fakeClient.Core()
err := master.CreateOrUpdateMasterServiceIfNeeded(test.serviceName, net.ParseIP("1.2.3.4"), test.servicePorts, test.serviceType, false)
if err != nil {
t.Errorf("case %q: unexpected error: %v", test.testName, err)
}
updates := []core.UpdateAction{}
for _, action := range fakeClient.Actions() {
if action.GetVerb() == "update" {
updates = append(updates, action.(core.UpdateAction))
}
}
if test.expectUpdate != nil {
if len(updates) != 1 {
t.Errorf("case %q: unexpected updates: %v", test.testName, updates)
} else {
obj := updates[0].GetObject()
if e, a := test.expectUpdate.Spec, obj.(*api.Service).Spec; !reflect.DeepEqual(e, a) {
t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a)
}
}
}
if test.expectUpdate == nil && len(updates) > 0 {
t.Errorf("case %q: no update expected, yet saw: %v", test.testName, updates)
}
}
}

View file

@ -1,19 +0,0 @@
/*
Copyright 2014 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 master contains code for setting up and running a Kubernetes
// cluster master.
package master // import "k8s.io/kubernetes/pkg/master"

View file

@ -1,43 +0,0 @@
/*
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 master
// These imports are the API groups the API server will support.
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
_ "k8s.io/kubernetes/pkg/api/install"
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/authentication/install"
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/certificates/install"
_ "k8s.io/kubernetes/pkg/apis/componentconfig/install"
_ "k8s.io/kubernetes/pkg/apis/extensions/install"
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
_ "k8s.io/kubernetes/pkg/apis/policy/install"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
_ "k8s.io/kubernetes/pkg/apis/storage/install"
)
func init() {
if missingVersions := api.Registry.ValidateEnvRequestedVersions(); len(missingVersions) != 0 {
panic(fmt.Sprintf("KUBE_API_VERSIONS contains versions that are not installed: %q.", missingVersions))
}
}

View file

@ -1,181 +0,0 @@
/*
Copyright 2016 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 master
import (
"encoding/json"
"reflect"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/util/intstr"
)
func TestGroupVersions(t *testing.T) {
// legacyUnsuffixedGroups contains the groups released prior to deciding that kubernetes API groups should be dns-suffixed
// new groups should be suffixed with ".k8s.io" (https://github.com/kubernetes/kubernetes/pull/31887#issuecomment-244462396)
legacyUnsuffixedGroups := sets.NewString(
"",
"apps",
"autoscaling",
"batch",
"componentconfig",
"extensions",
"federation",
"policy",
)
// No new groups should be added to the legacyUnsuffixedGroups exclusion list
if len(legacyUnsuffixedGroups) != 8 {
t.Errorf("No additional unnamespaced groups should be created")
}
for _, gv := range api.Registry.RegisteredGroupVersions() {
if !strings.HasSuffix(gv.Group, ".k8s.io") && !legacyUnsuffixedGroups.Has(gv.Group) {
t.Errorf("Group %s does not have the standard kubernetes API group suffix of .k8s.io", gv.Group)
}
}
}
func TestTypeTags(t *testing.T) {
for gvk, knownType := range api.Scheme.AllKnownTypes() {
if gvk.Version == runtime.APIVersionInternal {
ensureNoTags(t, gvk, knownType, nil)
} else {
ensureTags(t, gvk, knownType, nil)
}
}
}
// These types are registered in external versions, and therefore include json tags,
// but are also registered in internal versions (or referenced from internal types),
// so we explicitly allow tags for them
var typesAllowedTags = map[reflect.Type]bool{
reflect.TypeOf(intstr.IntOrString{}): true,
reflect.TypeOf(metav1.Time{}): true,
reflect.TypeOf(metav1.Duration{}): true,
reflect.TypeOf(metav1.TypeMeta{}): true,
reflect.TypeOf(metav1.ListMeta{}): true,
reflect.TypeOf(metav1.ObjectMeta{}): true,
reflect.TypeOf(metav1.OwnerReference{}): true,
reflect.TypeOf(metav1.LabelSelector{}): true,
reflect.TypeOf(metav1.GetOptions{}): true,
reflect.TypeOf(metav1.ExportOptions{}): true,
reflect.TypeOf(metav1.ListOptions{}): true,
reflect.TypeOf(metav1.DeleteOptions{}): true,
}
func ensureNoTags(t *testing.T, gvk schema.GroupVersionKind, tp reflect.Type, parents []reflect.Type) {
if _, ok := typesAllowedTags[tp]; ok {
return
}
parents = append(parents, tp)
switch tp.Kind() {
case reflect.Map, reflect.Slice, reflect.Ptr:
ensureNoTags(t, gvk, tp.Elem(), parents)
case reflect.String, reflect.Bool, reflect.Float32, reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uintptr, reflect.Uint32, reflect.Uint64, reflect.Interface:
// no-op
case reflect.Struct:
for i := 0; i < tp.NumField(); i++ {
f := tp.Field(i)
jsonTag := f.Tag.Get("json")
protoTag := f.Tag.Get("protobuf")
if len(jsonTag) > 0 || len(protoTag) > 0 {
t.Errorf("Internal types should not have json or protobuf tags. %#v has tag on field %v: %v", gvk, f.Name, f.Tag)
for i, tp := range parents {
t.Logf("%s%v:", strings.Repeat(" ", i), tp)
}
}
ensureNoTags(t, gvk, f.Type, parents)
}
default:
t.Errorf("Unexpected type %v in %#v", tp.Kind(), gvk)
for i, tp := range parents {
t.Logf("%s%v:", strings.Repeat(" ", i), tp)
}
}
}
var (
marshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
unmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
)
// These fields are limited exceptions to the standard JSON naming structure.
// Additions should only be made if a non-standard field name was released and cannot be changed for compatibility reasons.
var allowedNonstandardJSONNames = map[reflect.Type]string{
reflect.TypeOf(v1.DaemonEndpoint{}): "Port",
}
func ensureTags(t *testing.T, gvk schema.GroupVersionKind, tp reflect.Type, parents []reflect.Type) {
// This type handles its own encoding/decoding and doesn't need json tags
if tp.Implements(marshalerType) && (tp.Implements(unmarshalerType) || reflect.PtrTo(tp).Implements(unmarshalerType)) {
return
}
parents = append(parents, tp)
switch tp.Kind() {
case reflect.Map, reflect.Slice, reflect.Ptr:
ensureTags(t, gvk, tp.Elem(), parents)
case reflect.String, reflect.Bool, reflect.Float32, reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uintptr, reflect.Uint32, reflect.Uint64, reflect.Interface:
// no-op
case reflect.Struct:
for i := 0; i < tp.NumField(); i++ {
f := tp.Field(i)
jsonTag := f.Tag.Get("json")
if len(jsonTag) == 0 {
t.Errorf("External types should have json tags. %#v tags on field %v are: %s", gvk, f.Name, f.Tag)
for i, tp := range parents {
t.Logf("%s%v", strings.Repeat(" ", i), tp)
}
}
jsonTagName := strings.Split(jsonTag, ",")[0]
if len(jsonTagName) > 0 && (jsonTagName[0] < 'a' || jsonTagName[0] > 'z') && jsonTagName != "-" && allowedNonstandardJSONNames[tp] != jsonTagName {
t.Errorf("External types should have json names starting with lowercase letter. %#v has json tag on field %v with name %s", gvk, f.Name, jsonTagName)
t.Log(tp)
t.Log(allowedNonstandardJSONNames[tp])
for i, tp := range parents {
t.Logf("%s%v", strings.Repeat(" ", i), tp)
}
}
ensureTags(t, gvk, f.Type, parents)
}
default:
t.Errorf("Unexpected type %v in %#v", tp.Kind(), gvk)
for i, tp := range parents {
t.Logf("%s%v:", strings.Repeat(" ", i), tp)
}
}
}

View file

@ -1,418 +0,0 @@
/*
Copyright 2014 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 master
import (
"fmt"
"net"
"net/http"
"reflect"
"strconv"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/pkg/api"
apiv1 "k8s.io/kubernetes/pkg/api/v1"
appsapi "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
authenticationv1beta1 "k8s.io/kubernetes/pkg/apis/authentication/v1beta1"
authorizationapiv1beta1 "k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
batchapiv1 "k8s.io/kubernetes/pkg/apis/batch/v1"
certificatesapiv1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
policyapiv1beta1 "k8s.io/kubernetes/pkg/apis/policy/v1beta1"
rbacapi "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1"
rbacv1beta1 "k8s.io/kubernetes/pkg/apis/rbac/v1beta1"
storageapiv1beta1 "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
corev1client "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/genericapiserver/registry/generic"
genericregistry "k8s.io/kubernetes/pkg/genericapiserver/registry/generic/registry"
genericapiserver "k8s.io/kubernetes/pkg/genericapiserver/server"
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
"k8s.io/kubernetes/pkg/master/thirdparty"
"k8s.io/kubernetes/pkg/master/tunneler"
"k8s.io/kubernetes/pkg/routes"
nodeutil "k8s.io/kubernetes/pkg/util/node"
"github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus"
// RESTStorage installers
appsrest "k8s.io/kubernetes/pkg/registry/apps/rest"
authenticationrest "k8s.io/kubernetes/pkg/registry/authentication/rest"
authorizationrest "k8s.io/kubernetes/pkg/registry/authorization/rest"
autoscalingrest "k8s.io/kubernetes/pkg/registry/autoscaling/rest"
batchrest "k8s.io/kubernetes/pkg/registry/batch/rest"
certificatesrest "k8s.io/kubernetes/pkg/registry/certificates/rest"
corerest "k8s.io/kubernetes/pkg/registry/core/rest"
extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest"
policyrest "k8s.io/kubernetes/pkg/registry/policy/rest"
rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest"
storagerest "k8s.io/kubernetes/pkg/registry/storage/rest"
)
const (
// DefaultEndpointReconcilerInterval is the default amount of time for how often the endpoints for
// the kubernetes Service are reconciled.
DefaultEndpointReconcilerInterval = 10 * time.Second
)
type Config struct {
GenericConfig *genericapiserver.Config
APIResourceConfigSource genericapiserver.APIResourceConfigSource
StorageFactory genericapiserver.StorageFactory
EnableWatchCache bool
EnableCoreControllers bool
EndpointReconcilerConfig EndpointReconcilerConfig
DeleteCollectionWorkers int
EventTTL time.Duration
KubeletClientConfig kubeletclient.KubeletClientConfig
// Used to start and monitor tunneling
Tunneler tunneler.Tunneler
EnableUISupport bool
EnableLogsSupport bool
ProxyTransport http.RoundTripper
// Values to build the IP addresses used by discovery
// The range of IPs to be assigned to services with type=ClusterIP or greater
ServiceIPRange net.IPNet
// The IP address for the GenericAPIServer service (must be inside ServiceIPRange)
APIServerServiceIP net.IP
// Port for the apiserver service.
APIServerServicePort int
// TODO, we can probably group service related items into a substruct to make it easier to configure
// the API server items and `Extra*` fields likely fit nicely together.
// The range of ports to be assigned to services with type=NodePort or greater
ServiceNodePortRange utilnet.PortRange
// Additional ports to be exposed on the GenericAPIServer service
// extraServicePorts is injectable in the event that more ports
// (other than the default 443/tcp) are exposed on the GenericAPIServer
// and those ports need to be load balanced by the GenericAPIServer
// service because this pkg is linked by out-of-tree projects
// like openshift which want to use the GenericAPIServer but also do
// more stuff.
ExtraServicePorts []api.ServicePort
// Additional ports to be exposed on the GenericAPIServer endpoints
// Port names should align with ports defined in ExtraServicePorts
ExtraEndpointPorts []api.EndpointPort
// If non-zero, the "kubernetes" services uses this port as NodePort.
KubernetesServiceNodePort int
// Number of masters running; all masters must be started with the
// same value for this field. (Numbers > 1 currently untested.)
MasterCount int
}
// EndpointReconcilerConfig holds the endpoint reconciler and endpoint reconciliation interval to be
// used by the master.
type EndpointReconcilerConfig struct {
Reconciler EndpointReconciler
Interval time.Duration
}
// Master contains state for a Kubernetes cluster master/api server.
type Master struct {
GenericAPIServer *genericapiserver.GenericAPIServer
}
type completedConfig struct {
*Config
}
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func (c *Config) Complete() completedConfig {
c.GenericConfig.Complete()
serviceIPRange, apiServerServiceIP, err := DefaultServiceIPRange(c.ServiceIPRange)
if err != nil {
glog.Fatalf("Error determining service IP ranges: %v", err)
}
if c.ServiceIPRange.IP == nil {
c.ServiceIPRange = serviceIPRange
}
if c.APIServerServiceIP == nil {
c.APIServerServiceIP = apiServerServiceIP
}
discoveryAddresses := genericapiserver.DefaultDiscoveryAddresses{DefaultAddress: c.GenericConfig.ExternalAddress}
discoveryAddresses.DiscoveryCIDRRules = append(discoveryAddresses.DiscoveryCIDRRules,
genericapiserver.DiscoveryCIDRRule{IPRange: c.ServiceIPRange, Address: net.JoinHostPort(c.APIServerServiceIP.String(), strconv.Itoa(c.APIServerServicePort))})
c.GenericConfig.DiscoveryAddresses = discoveryAddresses
if c.ServiceNodePortRange.Size == 0 {
// TODO: Currently no way to specify an empty range (do we need to allow this?)
// We should probably allow this for clouds that don't require NodePort to do load-balancing (GCE)
// but then that breaks the strict nestedness of ServiceType.
// Review post-v1
c.ServiceNodePortRange = options.DefaultServiceNodePortRange
glog.Infof("Node port range unspecified. Defaulting to %v.", c.ServiceNodePortRange)
}
// enable swagger UI only if general UI support is on
c.GenericConfig.EnableSwaggerUI = c.GenericConfig.EnableSwaggerUI && c.EnableUISupport
if c.EndpointReconcilerConfig.Interval == 0 {
c.EndpointReconcilerConfig.Interval = DefaultEndpointReconcilerInterval
}
if c.EndpointReconcilerConfig.Reconciler == nil {
// use a default endpoint reconciler if nothing is set
endpointClient := coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
c.EndpointReconcilerConfig.Reconciler = NewMasterCountEndpointReconciler(c.MasterCount, endpointClient)
}
// this has always been hardcoded true in the past
c.GenericConfig.EnableMetrics = true
return completedConfig{c}
}
// SkipComplete provides a way to construct a server instance without config completion.
func (c *Config) SkipComplete() completedConfig {
return completedConfig{c}
}
// New returns a new instance of Master from the given config.
// Certain config fields will be set to a default value if unset.
// Certain config fields must be specified, including:
// KubeletClientConfig
func (c completedConfig) New() (*Master, error) {
if reflect.DeepEqual(c.KubeletClientConfig, kubeletclient.KubeletClientConfig{}) {
return nil, fmt.Errorf("Master.New() called with empty config.KubeletClientConfig")
}
s, err := c.Config.GenericConfig.SkipComplete().New() // completion is done in Complete, no need for a second time
if err != nil {
return nil, err
}
if c.EnableUISupport {
routes.UIRedirect{}.Install(s.HandlerContainer)
}
if c.EnableLogsSupport {
routes.Logs{}.Install(s.HandlerContainer)
}
m := &Master{
GenericAPIServer: s,
}
restOptionsFactory := &restOptionsFactory{
deleteCollectionWorkers: c.DeleteCollectionWorkers,
enableGarbageCollection: c.GenericConfig.EnableGarbageCollection,
storageFactory: c.StorageFactory,
}
if c.EnableWatchCache {
restOptionsFactory.storageDecorator = genericregistry.StorageWithCacher
} else {
restOptionsFactory.storageDecorator = generic.UndecoratedStorage
}
// install legacy rest storage
if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(apiv1.SchemeGroupVersion) {
legacyRESTStorageProvider := corerest.LegacyRESTStorageProvider{
StorageFactory: c.StorageFactory,
ProxyTransport: c.ProxyTransport,
KubeletClientConfig: c.KubeletClientConfig,
EventTTL: c.EventTTL,
ServiceIPRange: c.ServiceIPRange,
ServiceNodePortRange: c.ServiceNodePortRange,
LoopbackClientConfig: c.GenericConfig.LoopbackClientConfig,
}
m.InstallLegacyAPI(c.Config, restOptionsFactory, legacyRESTStorageProvider)
}
restStorageProviders := []RESTStorageProvider{
appsrest.RESTStorageProvider{},
authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authenticator},
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
autoscalingrest.RESTStorageProvider{},
batchrest.RESTStorageProvider{},
certificatesrest.RESTStorageProvider{},
extensionsrest.RESTStorageProvider{ResourceInterface: thirdparty.NewThirdPartyResourceServer(s, c.StorageFactory)},
policyrest.RESTStorageProvider{},
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
storagerest.RESTStorageProvider{},
}
m.InstallAPIs(c.Config.APIResourceConfigSource, restOptionsFactory, restStorageProviders...)
if c.Tunneler != nil {
m.installTunneler(c.Tunneler, corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes())
}
return m, nil
}
func (m *Master) InstallLegacyAPI(c *Config, restOptionsGetter generic.RESTOptionsGetter, legacyRESTStorageProvider corerest.LegacyRESTStorageProvider) {
legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(restOptionsGetter)
if err != nil {
glog.Fatalf("Error building core storage: %v", err)
}
if c.EnableCoreControllers {
coreClient := coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
bootstrapController := c.NewBootstrapController(legacyRESTStorage, coreClient, coreClient)
if err := m.GenericAPIServer.AddPostStartHook("bootstrap-controller", bootstrapController.PostStartHook); err != nil {
glog.Fatalf("Error registering PostStartHook %q: %v", "bootstrap-controller", err)
}
}
if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
glog.Fatalf("Error in registering group versions: %v", err)
}
}
func (m *Master) installTunneler(nodeTunneler tunneler.Tunneler, nodeClient corev1client.NodeInterface) {
nodeTunneler.Run(nodeAddressProvider{nodeClient}.externalAddresses)
m.GenericAPIServer.AddHealthzChecks(healthz.NamedCheck("SSH Tunnel Check", tunneler.TunnelSyncHealthChecker(nodeTunneler)))
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: "apiserver_proxy_tunnel_sync_latency_secs",
Help: "The time since the last successful synchronization of the SSH tunnels for proxy requests.",
}, func() float64 { return float64(nodeTunneler.SecondsSinceSync()) })
}
// RESTStorageProvider is a factory type for REST storage.
type RESTStorageProvider interface {
GroupName() string
NewRESTStorage(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool)
}
// InstallAPIs will install the APIs for the restStorageProviders if they are enabled.
func (m *Master) InstallAPIs(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, restStorageProviders ...RESTStorageProvider) {
apiGroupsInfo := []genericapiserver.APIGroupInfo{}
for _, restStorageBuilder := range restStorageProviders {
groupName := restStorageBuilder.GroupName()
if !apiResourceConfigSource.AnyResourcesForGroupEnabled(groupName) {
glog.V(1).Infof("Skipping disabled API group %q.", groupName)
continue
}
apiGroupInfo, enabled := restStorageBuilder.NewRESTStorage(apiResourceConfigSource, restOptionsGetter)
if !enabled {
glog.Warningf("Problem initializing API group %q, skipping.", groupName)
continue
}
glog.V(1).Infof("Enabling API group %q.", groupName)
if postHookProvider, ok := restStorageBuilder.(genericapiserver.PostStartHookProvider); ok {
name, hook, err := postHookProvider.PostStartHook()
if err != nil {
glog.Fatalf("Error building PostStartHook: %v", err)
}
if err := m.GenericAPIServer.AddPostStartHook(name, hook); err != nil {
glog.Fatalf("Error registering PostStartHook %q: %v", name, err)
}
}
apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
}
for i := range apiGroupsInfo {
if err := m.GenericAPIServer.InstallAPIGroup(&apiGroupsInfo[i]); err != nil {
glog.Fatalf("Error in registering group versions: %v", err)
}
}
}
type restOptionsFactory struct {
deleteCollectionWorkers int
enableGarbageCollection bool
storageFactory genericapiserver.StorageFactory
storageDecorator generic.StorageDecorator
}
func (f *restOptionsFactory) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
storageConfig, err := f.storageFactory.NewConfig(resource)
if err != nil {
return generic.RESTOptions{}, fmt.Errorf("Unable to find storage destination for %v, due to %v", resource, err.Error())
}
return generic.RESTOptions{
StorageConfig: storageConfig,
Decorator: f.storageDecorator,
DeleteCollectionWorkers: f.deleteCollectionWorkers,
EnableGarbageCollection: f.enableGarbageCollection,
ResourcePrefix: f.storageFactory.ResourcePrefix(resource),
}, nil
}
type nodeAddressProvider struct {
nodeClient corev1client.NodeInterface
}
func (n nodeAddressProvider) externalAddresses() ([]string, error) {
preferredAddressTypes := []apiv1.NodeAddressType{
apiv1.NodeExternalIP,
apiv1.NodeLegacyHostIP,
}
nodes, err := n.nodeClient.List(metav1.ListOptions{})
if err != nil {
return nil, err
}
addrs := []string{}
for ix := range nodes.Items {
node := &nodes.Items[ix]
addr, err := nodeutil.GetPreferredNodeAddress(node, preferredAddressTypes)
if err != nil {
return nil, err
}
addrs = append(addrs, addr)
}
return addrs, nil
}
func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig {
ret := genericapiserver.NewResourceConfig()
ret.EnableVersions(
apiv1.SchemeGroupVersion,
extensionsapiv1beta1.SchemeGroupVersion,
batchapiv1.SchemeGroupVersion,
authenticationv1beta1.SchemeGroupVersion,
autoscalingapiv1.SchemeGroupVersion,
appsapi.SchemeGroupVersion,
policyapiv1beta1.SchemeGroupVersion,
rbacv1beta1.SchemeGroupVersion,
rbacapi.SchemeGroupVersion,
storageapiv1beta1.SchemeGroupVersion,
certificatesapiv1beta1.SchemeGroupVersion,
authorizationapiv1beta1.SchemeGroupVersion,
)
// all extensions resources except these are disabled by default
ret.EnableResources(
extensionsapiv1beta1.SchemeGroupVersion.WithResource("daemonsets"),
extensionsapiv1beta1.SchemeGroupVersion.WithResource("deployments"),
extensionsapiv1beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"),
extensionsapiv1beta1.SchemeGroupVersion.WithResource("ingresses"),
extensionsapiv1beta1.SchemeGroupVersion.WithResource("networkpolicies"),
extensionsapiv1beta1.SchemeGroupVersion.WithResource("replicasets"),
extensionsapiv1beta1.SchemeGroupVersion.WithResource("thirdpartyresources"),
extensionsapiv1beta1.SchemeGroupVersion.WithResource("podsecuritypolicies"),
)
return ret
}

View file

@ -1,95 +0,0 @@
// +build !race
/*
Copyright 2014 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 master
// This test file is separated from master_test.go so we would be able to disable
// race check for it. TestValidOpenAPISpec will became extremely slow if -race
// flag exists, and will cause the tests to timeout.
import (
"net/http"
"net/http/httptest"
"testing"
openapigen "k8s.io/kubernetes/pkg/generated/openapi"
genericapiserver "k8s.io/kubernetes/pkg/genericapiserver/server"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// TestValidOpenAPISpec verifies that the open api is added
// at the proper endpoint and the spec is valid.
func TestValidOpenAPISpec(t *testing.T) {
_, etcdserver, config, assert := setUp(t)
defer etcdserver.Terminate(t)
config.GenericConfig.EnableIndex = true
config.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapigen.OpenAPIDefinitions)
config.GenericConfig.OpenAPIConfig.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "Kubernetes",
Version: "unversioned",
},
}
config.GenericConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig()
master, err := config.Complete().New()
if err != nil {
t.Fatalf("Error in bringing up the master: %v", err)
}
// make sure swagger.json is not registered before calling PrepareRun.
server := httptest.NewServer(master.GenericAPIServer.HandlerContainer.ServeMux)
defer server.Close()
resp, err := http.Get(server.URL + "/swagger.json")
if !assert.NoError(err) {
t.Errorf("unexpected error: %v", err)
}
assert.Equal(http.StatusNotFound, resp.StatusCode)
master.GenericAPIServer.PrepareRun()
resp, err = http.Get(server.URL + "/swagger.json")
if !assert.NoError(err) {
t.Errorf("unexpected error: %v", err)
}
assert.Equal(http.StatusOK, resp.StatusCode)
// as json schema
var sch spec.Schema
if assert.NoError(decodeResponse(resp, &sch)) {
validator := validate.NewSchemaValidator(spec.MustLoadSwagger20Schema(), nil, "", strfmt.Default)
res := validator.Validate(&sch)
assert.NoError(res.AsError())
}
// Validate OpenApi spec
doc, err := loads.Spec(server.URL + "/swagger.json")
if assert.NoError(err) {
validator := validate.NewSpecValidator(doc.Schema(), strfmt.Default)
res, warns := validator.Validate(doc)
assert.NoError(res.AsError())
if !warns.IsValid() {
t.Logf("Open API spec on root has some warnings : %v", warns)
}
}
}

View file

@ -1,305 +0,0 @@
/*
Copyright 2014 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 master
import (
"crypto/tls"
"encoding/json"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/version"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
apiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
appsapiv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/autoscaling"
autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
"k8s.io/kubernetes/pkg/apis/batch"
batchapiv1 "k8s.io/kubernetes/pkg/apis/batch/v1"
batchapiv2alpha1 "k8s.io/kubernetes/pkg/apis/batch/v2alpha1"
"k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/apis/extensions"
extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
genericapiserver "k8s.io/kubernetes/pkg/genericapiserver/server"
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
kubeversion "k8s.io/kubernetes/pkg/version"
"github.com/stretchr/testify/assert"
)
// setUp is a convience function for setting up for (most) tests.
func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
server, storageConfig := etcdtesting.NewUnsecuredEtcd3TestClientServer(t)
config := &Config{
GenericConfig: genericapiserver.NewConfig(),
APIResourceConfigSource: DefaultAPIResourceConfigSource(),
APIServerServicePort: 443,
MasterCount: 1,
}
resourceEncoding := genericapiserver.NewDefaultResourceEncodingConfig()
resourceEncoding.SetVersionEncoding(api.GroupName, api.Registry.GroupOrDie(api.GroupName).GroupVersion, schema.GroupVersion{Group: api.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(autoscaling.GroupName, *testapi.Autoscaling.GroupVersion(), schema.GroupVersion{Group: autoscaling.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(batch.GroupName, *testapi.Batch.GroupVersion(), schema.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(apps.GroupName, *testapi.Apps.GroupVersion(), schema.GroupVersion{Group: apps.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(extensions.GroupName, *testapi.Extensions.GroupVersion(), schema.GroupVersion{Group: extensions.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(rbac.GroupName, *testapi.Rbac.GroupVersion(), schema.GroupVersion{Group: rbac.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(certificates.GroupName, *testapi.Certificates.GroupVersion(), schema.GroupVersion{Group: certificates.GroupName, Version: runtime.APIVersionInternal})
storageFactory := genericapiserver.NewDefaultStorageFactory(*storageConfig, testapi.StorageMediaType(), api.Codecs, resourceEncoding, DefaultAPIResourceConfigSource())
kubeVersion := kubeversion.Get()
config.GenericConfig.Version = &kubeVersion
config.StorageFactory = storageFactory
config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs}}
config.GenericConfig.PublicAddress = net.ParseIP("192.168.10.4")
config.GenericConfig.LegacyAPIGroupPrefixes = sets.NewString("/api")
config.GenericConfig.RequestContextMapper = genericapirequest.NewRequestContextMapper()
config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs}}
config.GenericConfig.EnableMetrics = true
config.EnableCoreControllers = false
config.KubeletClientConfig = kubeletclient.KubeletClientConfig{Port: 10250}
config.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{
Dial: func(network, addr string) (net.Conn, error) { return nil, nil },
TLSClientConfig: &tls.Config{},
})
master, err := config.Complete().New()
if err != nil {
t.Fatal(err)
}
return master, server, *config, assert.New(t)
}
func newMaster(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
_, etcdserver, config, assert := setUp(t)
master, err := config.Complete().New()
if err != nil {
t.Fatalf("Error in bringing up the master: %v", err)
}
return master, etcdserver, config, assert
}
// limitedAPIResourceConfigSource only enables the core group, the extensions group, the batch group, and the autoscaling group.
func limitedAPIResourceConfigSource() *genericapiserver.ResourceConfig {
ret := genericapiserver.NewResourceConfig()
ret.EnableVersions(
apiv1.SchemeGroupVersion,
extensionsapiv1beta1.SchemeGroupVersion,
batchapiv1.SchemeGroupVersion,
batchapiv2alpha1.SchemeGroupVersion,
appsapiv1beta1.SchemeGroupVersion,
autoscalingapiv1.SchemeGroupVersion,
)
return ret
}
// newLimitedMaster only enables the core group, the extensions group, the batch group, and the autoscaling group.
func newLimitedMaster(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
_, etcdserver, config, assert := setUp(t)
config.APIResourceConfigSource = limitedAPIResourceConfigSource()
master, err := config.Complete().New()
if err != nil {
t.Fatalf("Error in bringing up the master: %v", err)
}
return master, etcdserver, config, assert
}
// TestVersion tests /version
func TestVersion(t *testing.T) {
s, etcdserver, _, _ := newMaster(t)
defer etcdserver.Terminate(t)
req, _ := http.NewRequest("GET", "/version", nil)
resp := httptest.NewRecorder()
s.GenericAPIServer.InsecureHandler.ServeHTTP(resp, req)
if resp.Code != 200 {
t.Fatalf("expected http 200, got: %d", resp.Code)
}
var info version.Info
err := json.NewDecoder(resp.Body).Decode(&info)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(kubeversion.Get(), info) {
t.Errorf("Expected %#v, Got %#v", kubeversion.Get(), info)
}
}
type fakeEndpointReconciler struct{}
func (*fakeEndpointReconciler) ReconcileEndpoints(serviceName string, ip net.IP, endpointPorts []api.EndpointPort, reconcilePorts bool) error {
return nil
}
func makeNodeList(nodes []string, nodeResources apiv1.NodeResources) *apiv1.NodeList {
list := apiv1.NodeList{
Items: make([]apiv1.Node, len(nodes)),
}
for i := range nodes {
list.Items[i].Name = nodes[i]
list.Items[i].Status.Capacity = nodeResources.Capacity
}
return &list
}
// TestGetNodeAddresses verifies that proper results are returned
// when requesting node addresses.
func TestGetNodeAddresses(t *testing.T) {
assert := assert.New(t)
fakeNodeClient := fake.NewSimpleClientset(makeNodeList([]string{"node1", "node2"}, apiv1.NodeResources{})).Core().Nodes()
addressProvider := nodeAddressProvider{fakeNodeClient}
// Fail case (no addresses associated with nodes)
nodes, _ := fakeNodeClient.List(metav1.ListOptions{})
addrs, err := addressProvider.externalAddresses()
assert.Error(err, "addresses should have caused an error as there are no addresses.")
assert.Equal([]string(nil), addrs)
// Pass case with External type IP
nodes, _ = fakeNodeClient.List(metav1.ListOptions{})
for index := range nodes.Items {
nodes.Items[index].Status.Addresses = []apiv1.NodeAddress{{Type: apiv1.NodeExternalIP, Address: "127.0.0.1"}}
fakeNodeClient.Update(&nodes.Items[index])
}
addrs, err = addressProvider.externalAddresses()
assert.NoError(err, "addresses should not have returned an error.")
assert.Equal([]string{"127.0.0.1", "127.0.0.1"}, addrs)
// Pass case with LegacyHost type IP
nodes, _ = fakeNodeClient.List(metav1.ListOptions{})
for index := range nodes.Items {
nodes.Items[index].Status.Addresses = []apiv1.NodeAddress{{Type: apiv1.NodeLegacyHostIP, Address: "127.0.0.2"}}
fakeNodeClient.Update(&nodes.Items[index])
}
addrs, err = addressProvider.externalAddresses()
assert.NoError(err, "addresses failback should not have returned an error.")
assert.Equal([]string{"127.0.0.2", "127.0.0.2"}, addrs)
}
func decodeResponse(resp *http.Response, obj interface{}) error {
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if err := json.Unmarshal(data, obj); err != nil {
return err
}
return nil
}
// Because we need to be backwards compatible with release 1.1, at endpoints
// that exist in release 1.1, the responses should have empty APIVersion.
func TestAPIVersionOfDiscoveryEndpoints(t *testing.T) {
master, etcdserver, _, assert := newMaster(t)
defer etcdserver.Terminate(t)
server := httptest.NewServer(master.GenericAPIServer.HandlerContainer.ServeMux)
// /api exists in release-1.1
resp, err := http.Get(server.URL + "/api")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
apiVersions := metav1.APIVersions{}
assert.NoError(decodeResponse(resp, &apiVersions))
assert.Equal(apiVersions.APIVersion, "")
// /api/v1 exists in release-1.1
resp, err = http.Get(server.URL + "/api/v1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
resourceList := metav1.APIResourceList{}
assert.NoError(decodeResponse(resp, &resourceList))
assert.Equal(resourceList.APIVersion, "")
// /apis exists in release-1.1
resp, err = http.Get(server.URL + "/apis")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
groupList := metav1.APIGroupList{}
assert.NoError(decodeResponse(resp, &groupList))
assert.Equal(groupList.APIVersion, "")
// /apis/extensions exists in release-1.1
resp, err = http.Get(server.URL + "/apis/extensions")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
group := metav1.APIGroup{}
assert.NoError(decodeResponse(resp, &group))
assert.Equal(group.APIVersion, "")
// /apis/extensions/v1beta1 exists in release-1.1
resp, err = http.Get(server.URL + "/apis/extensions/v1beta1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
resourceList = metav1.APIResourceList{}
assert.NoError(decodeResponse(resp, &resourceList))
assert.Equal(resourceList.APIVersion, "")
// /apis/autoscaling doesn't exist in release-1.1, so the APIVersion field
// should be non-empty in the results returned by the server.
resp, err = http.Get(server.URL + "/apis/autoscaling")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
group = metav1.APIGroup{}
assert.NoError(decodeResponse(resp, &group))
assert.Equal(group.APIVersion, "v1")
// apis/autoscaling/v1 doesn't exist in release-1.1, so the APIVersion field
// should be non-empty in the results returned by the server.
resp, err = http.Get(server.URL + "/apis/autoscaling/v1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
resourceList = metav1.APIResourceList{}
assert.NoError(decodeResponse(resp, &resourceList))
assert.Equal(resourceList.APIVersion, "v1")
}

View file

@ -1,30 +0,0 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"ports.go",
],
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -1,19 +0,0 @@
/*
Copyright 2014 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 ports defines ports used by various pieces of the kubernetes
// infrastructure.
package ports // import "k8s.io/kubernetes/pkg/master/ports"

View file

@ -1,41 +0,0 @@
/*
Copyright 2014 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 ports
const (
// ProxyPort is the default port for the proxy healthz server.
// May be overridden by a flag at startup.
ProxyStatusPort = 10249
// KubeletPort is the default port for the kubelet server on each host machine.
// May be overridden by a flag at startup.
KubeletPort = 10250
// SchedulerPort is the default port for the scheduler status server.
// May be overridden by a flag at startup.
SchedulerPort = 10251
// ControllerManagerPort is the default port for the controller manager status server.
// May be overridden by a flag at startup.
ControllerManagerPort = 10252
// CloudControllerManagerPort is the default port for the cloud controller manager server.
// This value may be overriden by a flag at startup.
CloudControllerManagerPort = 10253
// KubeletReadOnlyPort exposes basic read-only services from the kubelet.
// May be overridden by a flag at startup.
// This is necessary for heapster to collect monitoring stats from the kubelet
// until heapster can transition to using the SSL endpoint.
// TODO(roberthbailey): Remove this once we have a better solution for heapster.
KubeletReadOnlyPort = 10255
)

View file

@ -1,54 +0,0 @@
/*
Copyright 2016 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 master
import (
"fmt"
"net"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
)
// DefaultServiceIPRange takes a the serviceIPRange flag and returns the defaulted service ip range (if needed),
// api server service IP, and an error
// TODO move this out of the genericapiserver package
func DefaultServiceIPRange(passedServiceClusterIPRange net.IPNet) (net.IPNet, net.IP, error) {
serviceClusterIPRange := passedServiceClusterIPRange
if passedServiceClusterIPRange.IP == nil {
defaultNet := "10.0.0.0/24"
glog.Infof("Network range for service cluster IPs is unspecified. Defaulting to %v.", defaultNet)
_, defaultServiceClusterIPRange, err := net.ParseCIDR(defaultNet)
if err != nil {
return net.IPNet{}, net.IP{}, err
}
serviceClusterIPRange = *defaultServiceClusterIPRange
}
if size := ipallocator.RangeSize(&serviceClusterIPRange); size < 8 {
return net.IPNet{}, net.IP{}, fmt.Errorf("The service cluster IP range must be at least %d IP addresses", 8)
}
// Select the first valid IP from ServiceClusterIPRange to use as the GenericAPIServer service IP.
apiServerServiceIP, err := ipallocator.GetIndexedIP(&serviceClusterIPRange, 1)
if err != nil {
return net.IPNet{}, net.IP{}, err
}
glog.V(4).Infof("Setting service IP to %q (read-write).", apiServerServiceIP)
return serviceClusterIPRange, apiServerServiceIP, nil
}

View file

@ -1,46 +0,0 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["thirdparty.go"],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/genericapiserver/endpoints:go_default_library",
"//pkg/genericapiserver/endpoints/handlers:go_default_library",
"//pkg/genericapiserver/registry/generic:go_default_library",
"//pkg/genericapiserver/registry/rest:go_default_library",
"//pkg/genericapiserver/server:go_default_library",
"//pkg/registry/extensions/rest:go_default_library",
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
"//pkg/registry/extensions/thirdpartyresourcedata/storage:go_default_library",
"//pkg/storage/storagebackend:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apiserver/pkg/endpoints/request",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -1,336 +0,0 @@
/*
Copyright 2016 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 thirdparty
import (
"fmt"
"strings"
"sync"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
genericapi "k8s.io/kubernetes/pkg/genericapiserver/endpoints"
genericapihandlers "k8s.io/kubernetes/pkg/genericapiserver/endpoints/handlers"
"k8s.io/kubernetes/pkg/genericapiserver/registry/generic"
"k8s.io/kubernetes/pkg/genericapiserver/registry/rest"
genericapiserver "k8s.io/kubernetes/pkg/genericapiserver/server"
extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest"
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
thirdpartyresourcedatastore "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata/storage"
"k8s.io/kubernetes/pkg/storage/storagebackend"
)
// dynamicLister is used to list resources for dynamic third party
// apis. It implements the genericapihandlers.APIResourceLister interface
type dynamicLister struct {
m *ThirdPartyResourceServer
path string
}
func (d dynamicLister) ListAPIResources() []metav1.APIResource {
return d.m.getExistingThirdPartyResources(d.path)
}
var _ genericapihandlers.APIResourceLister = &dynamicLister{}
type ThirdPartyResourceServer struct {
genericAPIServer *genericapiserver.GenericAPIServer
deleteCollectionWorkers int
// storage for third party objects
thirdPartyStorageConfig *storagebackend.Config
// map from api path to a tuple of (storage for the objects, APIGroup)
thirdPartyResources map[string]*thirdPartyEntry
// protects the map
thirdPartyResourcesLock sync.RWMutex
// Useful for reliable testing. Shouldn't be used otherwise.
disableThirdPartyControllerForTesting bool
}
func NewThirdPartyResourceServer(genericAPIServer *genericapiserver.GenericAPIServer, storageFactory genericapiserver.StorageFactory) *ThirdPartyResourceServer {
ret := &ThirdPartyResourceServer{
genericAPIServer: genericAPIServer,
thirdPartyResources: map[string]*thirdPartyEntry{},
}
var err error
ret.thirdPartyStorageConfig, err = storageFactory.NewConfig(extensions.Resource("thirdpartyresources"))
if err != nil {
glog.Fatalf("Error building third party storage: %v", err)
}
return ret
}
// thirdPartyEntry combines objects storage and API group into one struct
// for easy lookup.
type thirdPartyEntry struct {
// Map from plural resource name to entry
storage map[string]*thirdpartyresourcedatastore.REST
group metav1.APIGroup
}
// HasThirdPartyResource returns true if a particular third party resource currently installed.
func (m *ThirdPartyResourceServer) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) {
kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
if err != nil {
return false, err
}
path := extensionsrest.MakeThirdPartyPath(group)
m.thirdPartyResourcesLock.Lock()
defer m.thirdPartyResourcesLock.Unlock()
entry := m.thirdPartyResources[path]
if entry == nil {
return false, nil
}
plural, _ := meta.KindToResource(schema.GroupVersionKind{
Group: group,
Version: rsrc.Versions[0].Name,
Kind: kind,
})
_, found := entry.storage[plural.Resource]
return found, nil
}
func (m *ThirdPartyResourceServer) removeThirdPartyStorage(path, resource string) error {
m.thirdPartyResourcesLock.Lock()
defer m.thirdPartyResourcesLock.Unlock()
entry, found := m.thirdPartyResources[path]
if !found {
return nil
}
storage, found := entry.storage[resource]
if !found {
return nil
}
if err := m.removeAllThirdPartyResources(storage); err != nil {
return err
}
delete(entry.storage, resource)
if len(entry.storage) == 0 {
delete(m.thirdPartyResources, path)
m.genericAPIServer.RemoveAPIGroupForDiscovery(extensionsrest.GetThirdPartyGroupName(path))
} else {
m.thirdPartyResources[path] = entry
}
return nil
}
// RemoveThirdPartyResource removes all resources matching `path`. Also deletes any stored data
func (m *ThirdPartyResourceServer) RemoveThirdPartyResource(path string) error {
ix := strings.LastIndex(path, "/")
if ix == -1 {
return fmt.Errorf("expected <api-group>/<resource-plural-name>, saw: %s", path)
}
resource := path[ix+1:]
path = path[0:ix]
if err := m.removeThirdPartyStorage(path, resource); err != nil {
return err
}
services := m.genericAPIServer.HandlerContainer.RegisteredWebServices()
for ix := range services {
root := services[ix].RootPath()
if root == path || strings.HasPrefix(root, path+"/") {
m.genericAPIServer.HandlerContainer.Remove(services[ix])
}
}
return nil
}
func (m *ThirdPartyResourceServer) removeAllThirdPartyResources(registry *thirdpartyresourcedatastore.REST) error {
ctx := genericapirequest.NewDefaultContext()
existingData, err := registry.List(ctx, nil)
if err != nil {
return err
}
list, ok := existingData.(*extensions.ThirdPartyResourceDataList)
if !ok {
return fmt.Errorf("expected a *ThirdPartyResourceDataList, got %#v", list)
}
for ix := range list.Items {
item := &list.Items[ix]
if _, err := registry.Delete(ctx, item.Name, nil); err != nil {
return err
}
}
return nil
}
// ListThirdPartyResources lists all currently installed third party resources
// The format is <path>/<resource-plural-name>
func (m *ThirdPartyResourceServer) ListThirdPartyResources() []string {
m.thirdPartyResourcesLock.RLock()
defer m.thirdPartyResourcesLock.RUnlock()
result := []string{}
for key := range m.thirdPartyResources {
for rsrc := range m.thirdPartyResources[key].storage {
result = append(result, key+"/"+rsrc)
}
}
return result
}
func (m *ThirdPartyResourceServer) getExistingThirdPartyResources(path string) []metav1.APIResource {
result := []metav1.APIResource{}
m.thirdPartyResourcesLock.Lock()
defer m.thirdPartyResourcesLock.Unlock()
entry := m.thirdPartyResources[path]
if entry != nil {
for key, obj := range entry.storage {
result = append(result, metav1.APIResource{
Name: key,
Namespaced: true,
Kind: obj.Kind(),
Verbs: metav1.Verbs([]string{
"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch",
}),
})
}
}
return result
}
func (m *ThirdPartyResourceServer) hasThirdPartyGroupStorage(path string) bool {
m.thirdPartyResourcesLock.Lock()
defer m.thirdPartyResourcesLock.Unlock()
_, found := m.thirdPartyResources[path]
return found
}
func (m *ThirdPartyResourceServer) addThirdPartyResourceStorage(path, resource string, storage *thirdpartyresourcedatastore.REST, apiGroup metav1.APIGroup) {
m.thirdPartyResourcesLock.Lock()
defer m.thirdPartyResourcesLock.Unlock()
entry, found := m.thirdPartyResources[path]
if entry == nil {
entry = &thirdPartyEntry{
group: apiGroup,
storage: map[string]*thirdpartyresourcedatastore.REST{},
}
m.thirdPartyResources[path] = entry
}
entry.storage[resource] = storage
if !found {
m.genericAPIServer.AddAPIGroupForDiscovery(apiGroup)
}
}
// InstallThirdPartyResource installs a third party resource specified by 'rsrc'. When a resource is
// installed a corresponding RESTful resource is added as a valid path in the web service provided by
// the master.
//
// For example, if you install a resource ThirdPartyResource{ Name: "foo.company.com", Versions: {"v1"} }
// then the following RESTful resource is created on the server:
// http://<host>/apis/company.com/v1/foos/...
func (m *ThirdPartyResourceServer) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource) error {
kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
if err != nil {
return err
}
if len(rsrc.Versions) == 0 {
return fmt.Errorf("ThirdPartyResource %s has no defined versions", rsrc.Name)
}
plural, _ := meta.KindToResource(schema.GroupVersionKind{
Group: group,
Version: rsrc.Versions[0].Name,
Kind: kind,
})
path := extensionsrest.MakeThirdPartyPath(group)
groupVersion := metav1.GroupVersionForDiscovery{
GroupVersion: group + "/" + rsrc.Versions[0].Name,
Version: rsrc.Versions[0].Name,
}
apiGroup := metav1.APIGroup{
Name: group,
Versions: []metav1.GroupVersionForDiscovery{groupVersion},
PreferredVersion: groupVersion,
}
thirdparty := m.thirdpartyapi(group, kind, rsrc.Versions[0].Name, plural.Resource)
// If storage exists, this group has already been added, just update
// the group with the new API
if m.hasThirdPartyGroupStorage(path) {
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedatastore.REST), apiGroup)
return thirdparty.UpdateREST(m.genericAPIServer.HandlerContainer.Container)
}
if err := thirdparty.InstallREST(m.genericAPIServer.HandlerContainer.Container); err != nil {
glog.Errorf("Unable to setup thirdparty api: %v", err)
}
m.genericAPIServer.HandlerContainer.Add(genericapi.NewGroupWebService(api.Codecs, path, apiGroup))
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedatastore.REST), apiGroup)
api.Registry.AddThirdPartyAPIGroupVersions(schema.GroupVersion{Group: group, Version: rsrc.Versions[0].Name})
return nil
}
func (m *ThirdPartyResourceServer) thirdpartyapi(group, kind, version, pluralResource string) *genericapi.APIGroupVersion {
resourceStorage := thirdpartyresourcedatastore.NewREST(
generic.RESTOptions{
StorageConfig: m.thirdPartyStorageConfig,
Decorator: generic.UndecoratedStorage,
DeleteCollectionWorkers: m.deleteCollectionWorkers,
},
group,
kind,
)
storage := map[string]rest.Storage{
pluralResource: resourceStorage,
}
optionsExternalVersion := api.Registry.GroupOrDie(api.GroupName).GroupVersion
internalVersion := schema.GroupVersion{Group: group, Version: runtime.APIVersionInternal}
externalVersion := schema.GroupVersion{Group: group, Version: version}
apiRoot := extensionsrest.MakeThirdPartyPath("")
return &genericapi.APIGroupVersion{
Root: apiRoot,
GroupVersion: externalVersion,
Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme),
Convertor: api.Scheme,
Copier: api.Scheme,
Typer: api.Scheme,
Mapper: thirdpartyresourcedata.NewMapper(api.Registry.GroupOrDie(extensions.GroupName).RESTMapper, kind, version, group),
Linker: api.Registry.GroupOrDie(extensions.GroupName).SelfLinker,
Storage: storage,
OptionsExternalVersion: &optionsExternalVersion,
Serializer: thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, kind, externalVersion, internalVersion),
ParameterCodec: thirdpartyresourcedata.NewThirdPartyParameterCodec(api.ParameterCodec),
Context: m.genericAPIServer.RequestContextMapper(),
MinRequestTimeout: m.genericAPIServer.MinRequestTimeout(),
ResourceLister: dynamicLister{m, extensionsrest.MakeThirdPartyPath(group)},
}
}

View file

@ -1,47 +0,0 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["ssh_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//vendor:github.com/stretchr/testify/assert",
"//vendor:k8s.io/client-go/util/clock",
],
)
go_library(
name = "go_default_library",
srcs = ["ssh.go"],
tags = ["automanaged"],
deps = [
"//pkg/ssh:go_default_library",
"//pkg/util:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/prometheus/client_golang/prometheus",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/client-go/util/clock",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -1,228 +0,0 @@
/*
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 tunneler
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"sync/atomic"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/clock"
"k8s.io/kubernetes/pkg/ssh"
"k8s.io/kubernetes/pkg/util"
"github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus"
)
type InstallSSHKey func(user string, data []byte) error
type AddressFunc func() (addresses []string, err error)
type Tunneler interface {
Run(AddressFunc)
Stop()
Dial(net, addr string) (net.Conn, error)
SecondsSinceSync() int64
SecondsSinceSSHKeySync() int64
}
// TunnelSyncHealthChecker returns a health func that indicates if a tunneler is healthy.
// It's compatible with healthz.NamedCheck
func TunnelSyncHealthChecker(tunneler Tunneler) func(req *http.Request) error {
return func(req *http.Request) error {
if tunneler == nil {
return nil
}
lag := tunneler.SecondsSinceSync()
if lag > 600 {
return fmt.Errorf("Tunnel sync is taking to long: %d", lag)
}
sshKeyLag := tunneler.SecondsSinceSSHKeySync()
if sshKeyLag > 600 {
return fmt.Errorf("SSHKey sync is taking to long: %d", sshKeyLag)
}
return nil
}
}
type SSHTunneler struct {
// Important: Since these two int64 fields are using sync/atomic, they have to be at the top of the struct due to a bug on 32-bit platforms
// See: https://golang.org/pkg/sync/atomic/ for more information
lastSync int64 // Seconds since Epoch
lastSSHKeySync int64 // Seconds since Epoch
SSHUser string
SSHKeyfile string
InstallSSHKey InstallSSHKey
HealthCheckURL *url.URL
tunnels *ssh.SSHTunnelList
lastSyncMetric prometheus.GaugeFunc
clock clock.Clock
getAddresses AddressFunc
stopChan chan struct{}
}
func New(sshUser, sshKeyfile string, healthCheckURL *url.URL, installSSHKey InstallSSHKey) Tunneler {
return &SSHTunneler{
SSHUser: sshUser,
SSHKeyfile: sshKeyfile,
InstallSSHKey: installSSHKey,
HealthCheckURL: healthCheckURL,
clock: clock.RealClock{},
}
}
// Run establishes tunnel loops and returns
func (c *SSHTunneler) Run(getAddresses AddressFunc) {
if c.stopChan != nil {
return
}
c.stopChan = make(chan struct{})
// Save the address getter
if getAddresses != nil {
c.getAddresses = getAddresses
}
// Usernames are capped @ 32
if len(c.SSHUser) > 32 {
glog.Warning("SSH User is too long, truncating to 32 chars")
c.SSHUser = c.SSHUser[0:32]
}
glog.Infof("Setting up proxy: %s %s", c.SSHUser, c.SSHKeyfile)
// public keyfile is written last, so check for that.
publicKeyFile := c.SSHKeyfile + ".pub"
exists, err := util.FileExists(publicKeyFile)
if err != nil {
glog.Errorf("Error detecting if key exists: %v", err)
} else if !exists {
glog.Infof("Key doesn't exist, attempting to create")
if err := generateSSHKey(c.SSHKeyfile, publicKeyFile); err != nil {
glog.Errorf("Failed to create key pair: %v", err)
}
}
c.tunnels = ssh.NewSSHTunnelList(c.SSHUser, c.SSHKeyfile, c.HealthCheckURL, c.stopChan)
// Sync loop to ensure that the SSH key has been installed.
c.lastSSHKeySync = c.clock.Now().Unix()
c.installSSHKeySyncLoop(c.SSHUser, publicKeyFile)
// Sync tunnelList w/ nodes.
c.lastSync = c.clock.Now().Unix()
c.nodesSyncLoop()
}
// Stop gracefully shuts down the tunneler
func (c *SSHTunneler) Stop() {
if c.stopChan != nil {
close(c.stopChan)
c.stopChan = nil
}
}
func (c *SSHTunneler) Dial(net, addr string) (net.Conn, error) {
return c.tunnels.Dial(net, addr)
}
func (c *SSHTunneler) SecondsSinceSync() int64 {
now := c.clock.Now().Unix()
then := atomic.LoadInt64(&c.lastSync)
return now - then
}
func (c *SSHTunneler) SecondsSinceSSHKeySync() int64 {
now := c.clock.Now().Unix()
then := atomic.LoadInt64(&c.lastSSHKeySync)
return now - then
}
func (c *SSHTunneler) installSSHKeySyncLoop(user, publicKeyfile string) {
go wait.Until(func() {
if c.InstallSSHKey == nil {
glog.Error("Won't attempt to install ssh key: InstallSSHKey function is nil")
return
}
key, err := ssh.ParsePublicKeyFromFile(publicKeyfile)
if err != nil {
glog.Errorf("Failed to load public key: %v", err)
return
}
keyData, err := ssh.EncodeSSHKey(key)
if err != nil {
glog.Errorf("Failed to encode public key: %v", err)
return
}
if err := c.InstallSSHKey(user, keyData); err != nil {
glog.Errorf("Failed to install ssh key: %v", err)
return
}
atomic.StoreInt64(&c.lastSSHKeySync, c.clock.Now().Unix())
}, 5*time.Minute, c.stopChan)
}
// nodesSyncLoop lists nodes every 15 seconds, calling Update() on the TunnelList
// each time (Update() is a noop if no changes are necessary).
func (c *SSHTunneler) nodesSyncLoop() {
// TODO (cjcullen) make this watch.
go wait.Until(func() {
addrs, err := c.getAddresses()
glog.V(4).Infof("Calling update w/ addrs: %v", addrs)
if err != nil {
glog.Errorf("Failed to getAddresses: %v", err)
}
c.tunnels.Update(addrs)
atomic.StoreInt64(&c.lastSync, c.clock.Now().Unix())
}, 15*time.Second, c.stopChan)
}
func generateSSHKey(privateKeyfile, publicKeyfile string) error {
private, public, err := ssh.GenerateKey(2048)
if err != nil {
return err
}
// If private keyfile already exists, we must have only made it halfway
// through last time, so delete it.
exists, err := util.FileExists(privateKeyfile)
if err != nil {
glog.Errorf("Error detecting if private key exists: %v", err)
} else if exists {
glog.Infof("Private key exists, but public key does not")
if err := os.Remove(privateKeyfile); err != nil {
glog.Errorf("Failed to remove stale private key: %v", err)
}
}
if err := ioutil.WriteFile(privateKeyfile, ssh.EncodePrivateKey(private), 0600); err != nil {
return err
}
publicKeyBytes, err := ssh.EncodePublicKey(public)
if err != nil {
return err
}
if err := ioutil.WriteFile(publicKeyfile+".tmp", publicKeyBytes, 0600); err != nil {
return err
}
return os.Rename(publicKeyfile+".tmp", publicKeyfile)
}

View file

@ -1,135 +0,0 @@
/*
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 tunneler
import (
"fmt"
"net"
"os"
"path/filepath"
"testing"
"time"
"k8s.io/client-go/util/clock"
"github.com/stretchr/testify/assert"
)
// TestSecondsSinceSync verifies that proper results are returned
// when checking the time between syncs
func TestSecondsSinceSync(t *testing.T) {
tunneler := &SSHTunneler{}
assert := assert.New(t)
tunneler.lastSync = time.Date(2015, time.January, 1, 1, 1, 1, 1, time.UTC).Unix()
// Nano Second. No difference.
tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 1, 2, time.UTC))
assert.Equal(int64(0), tunneler.SecondsSinceSync())
// Second
tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 2, 1, time.UTC))
assert.Equal(int64(1), tunneler.SecondsSinceSync())
// Minute
tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 1, 2, 1, 1, time.UTC))
assert.Equal(int64(60), tunneler.SecondsSinceSync())
// Hour
tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 2, 1, 1, 1, time.UTC))
assert.Equal(int64(3600), tunneler.SecondsSinceSync())
// Day
tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 2, 1, 1, 1, 1, time.UTC))
assert.Equal(int64(86400), tunneler.SecondsSinceSync())
// Month
tunneler.clock = clock.NewFakeClock(time.Date(2015, time.February, 1, 1, 1, 1, 1, time.UTC))
assert.Equal(int64(2678400), tunneler.SecondsSinceSync())
// Future Month. Should be -Month.
tunneler.lastSync = time.Date(2015, time.February, 1, 1, 1, 1, 1, time.UTC).Unix()
tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 1, 1, time.UTC))
assert.Equal(int64(-2678400), tunneler.SecondsSinceSync())
}
// generateTempFile creates a temporary file path
func generateTempFilePath(prefix string) string {
tmpPath, _ := filepath.Abs(fmt.Sprintf("%s/%s-%d", os.TempDir(), prefix, time.Now().Unix()))
return tmpPath
}
// TestGenerateSSHKey verifies that SSH key generation does indeed
// generate keys even with keys already exist.
func TestGenerateSSHKey(t *testing.T) {
assert := assert.New(t)
privateKey := generateTempFilePath("private")
publicKey := generateTempFilePath("public")
// Make sure we have no test keys laying around
os.Remove(privateKey)
os.Remove(publicKey)
// Pass case: Sunny day case
err := generateSSHKey(privateKey, publicKey)
assert.NoError(err, "generateSSHKey should not have retuend an error: %s", err)
// Pass case: PrivateKey exists test case
os.Remove(publicKey)
err = generateSSHKey(privateKey, publicKey)
assert.NoError(err, "generateSSHKey should not have retuend an error: %s", err)
// Pass case: PublicKey exists test case
os.Remove(privateKey)
err = generateSSHKey(privateKey, publicKey)
assert.NoError(err, "generateSSHKey should not have retuend an error: %s", err)
// Make sure we have no test keys laying around
os.Remove(privateKey)
os.Remove(publicKey)
// TODO: testing error cases where the file can not be removed?
}
type FakeTunneler struct {
SecondsSinceSyncValue int64
SecondsSinceSSHKeySyncValue int64
}
func (t *FakeTunneler) Run(AddressFunc) {}
func (t *FakeTunneler) Stop() {}
func (t *FakeTunneler) Dial(net, addr string) (net.Conn, error) { return nil, nil }
func (t *FakeTunneler) SecondsSinceSync() int64 { return t.SecondsSinceSyncValue }
func (t *FakeTunneler) SecondsSinceSSHKeySync() int64 { return t.SecondsSinceSSHKeySyncValue }
// TestIsTunnelSyncHealthy verifies that the 600 second lag test
// is honored.
func TestIsTunnelSyncHealthy(t *testing.T) {
tunneler := &FakeTunneler{}
// Pass case: 540 second lag
tunneler.SecondsSinceSyncValue = 540
healthFn := TunnelSyncHealthChecker(tunneler)
err := healthFn(nil)
assert.NoError(t, err, "IsTunnelSyncHealthy() should not have returned an error.")
// Fail case: 720 second lag
tunneler.SecondsSinceSyncValue = 720
err = healthFn(nil)
assert.Error(t, err, "IsTunnelSyncHealthy() should have returned an error.")
}