2014-06-16 06:29:07 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2014-06-16 06:29:07 +00:00
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 (
2014-11-11 07:11:45 +00:00
"bytes"
2014-11-02 20:52:31 +00:00
"fmt"
2015-05-28 18:45:08 +00:00
"io/ioutil"
2015-06-18 23:31:54 +00:00
"math/rand"
2014-09-18 23:03:34 +00:00
"net"
2014-10-23 23:55:14 +00:00
"net/http"
2015-03-13 15:44:11 +00:00
"net/http/pprof"
2014-11-02 20:52:31 +00:00
"net/url"
2015-06-17 18:49:13 +00:00
"os"
2014-11-11 07:11:45 +00:00
rt "runtime"
2014-10-28 00:56:33 +00:00
"strconv"
2014-10-23 23:55:14 +00:00
"strings"
2015-06-04 18:58:38 +00:00
"sync"
2015-07-14 19:30:43 +00:00
"sync/atomic"
2014-06-16 06:29:07 +00:00
"time"
2015-01-06 16:44:43 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
2014-11-02 20:52:31 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
2014-09-11 23:01:29 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
2015-03-21 16:24:16 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
2015-04-28 19:23:13 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1"
2014-06-16 06:29:07 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
2014-10-27 21:18:02 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
2014-11-03 15:57:08 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
2014-10-23 23:55:14 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
2014-06-16 06:29:07 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
2015-05-28 04:38:21 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
2015-07-14 19:30:43 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
2015-05-28 04:38:21 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
2014-12-16 03:45:27 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
2015-04-15 19:23:02 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/componentstatus"
2015-03-16 04:36:26 +00:00
controlleretcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/controller/etcd"
2014-08-14 19:48:34 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
2015-03-15 06:03:46 +00:00
endpointsetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint/etcd"
2014-08-11 07:34:59 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd"
2014-10-09 22:46:41 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/event"
2015-01-22 21:52:40 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/limitrange"
2014-08-11 07:34:59 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
2015-03-13 14:49:38 +00:00
nodeetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion/etcd"
2015-01-19 21:50:00 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
2015-03-12 15:08:06 +00:00
namespaceetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace/etcd"
2015-03-26 19:50:36 +00:00
pvetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/persistentvolume/etcd"
pvcetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/persistentvolumeclaim/etcd"
2014-08-11 07:34:59 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
2015-02-11 23:37:12 +00:00
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
2015-03-04 00:54:17 +00:00
podtemplateetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/podtemplate/etcd"
2015-03-13 19:15:04 +00:00
resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd"
2015-04-28 03:50:56 +00:00
secretetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret/etcd"
2014-08-11 07:34:59 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
2015-05-22 22:28:48 +00:00
etcdallocator "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service/allocator/etcd"
2015-05-04 19:11:19 +00:00
ipallocator "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service/ipallocator"
2015-04-27 22:53:28 +00:00
serviceaccountetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/serviceaccount/etcd"
2014-09-11 23:01:29 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
2014-10-23 20:56:18 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/ui"
2014-06-16 06:29:07 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
2014-10-23 23:55:14 +00:00
2015-05-22 22:28:48 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service/allocator"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service/portallocator"
2014-11-11 07:11:45 +00:00
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful/swagger"
2014-10-23 23:55:14 +00:00
"github.com/golang/glog"
2015-07-14 19:30:43 +00:00
"github.com/prometheus/client_golang/prometheus"
2014-06-16 06:29:07 +00:00
)
2015-03-11 17:10:09 +00:00
const (
DefaultEtcdPathPrefix = "/registry"
)
2014-07-27 02:16:39 +00:00
// Config is a structure used to configure a Master.
type Config struct {
2015-07-24 11:09:49 +00:00
DatabaseStorage tools . StorageInterface
EventTTL time . Duration
MinionRegexp string
KubeletClient client . KubeletClient
2015-05-03 22:44:05 +00:00
// allow downstream consumers to disable the core controller loops
EnableCoreControllers bool
EnableLogsSupport bool
EnableUISupport bool
2015-01-30 23:53:04 +00:00
// allow downstream consumers to disable swagger
EnableSwaggerSupport bool
2015-06-04 18:37:44 +00:00
// allow v1 to be conditionally disabled
DisableV1 bool
2015-01-30 23:53:04 +00:00
// allow downstream consumers to disable the index route
2015-04-28 05:24:26 +00:00
EnableIndex bool
EnableProfiling bool
APIPrefix string
CorsAllowedOriginList util . StringList
Authenticator authenticator . Request
// TODO(roberthbailey): Remove once the server no longer supports http basic auth.
SupportsBasicAuth bool
2015-01-08 15:25:14 +00:00
Authorizer authorizer . Authorizer
AdmissionControl admission . Interface
MasterServiceNamespace string
2014-10-28 00:56:33 +00:00
2015-02-11 22:09:25 +00:00
// Map requests to contexts. Exported so downstream consumers can provider their own mappers
RequestContextMapper api . RequestContextMapper
2014-12-15 20:29:55 +00:00
// If specified, all web services will be registered into this container
RestfulContainer * restful . Container
2015-05-27 01:39:42 +00:00
// If specified, requests will be allocated a random timeout between this value, and twice this value.
2015-06-16 02:39:31 +00:00
// Note that it is up to the request handlers to ignore or honor this timeout. In seconds.
2015-05-27 01:39:42 +00:00
MinRequestTimeout int
2014-10-28 23:49:52 +00:00
// Number of masters running; all masters must be started with the
// same value for this field. (Numbers > 1 currently untested.)
MasterCount int
2014-10-28 00:56:33 +00:00
// The port on PublicAddress where a read-write server will be installed.
2015-02-07 00:33:28 +00:00
// Defaults to 6443 if not set.
2014-10-28 00:56:33 +00:00
ReadWritePort int
2015-03-25 04:39:19 +00:00
// ExternalHost is the host name to use for external (public internet) facing URLs (e.g. Swagger)
ExternalHost string
2015-05-20 21:05:23 +00:00
// PublicAddress is the IP address where members of the cluster (kubelet,
// kube-proxy, services, etc.) can reach the master.
2015-05-22 06:12:57 +00:00
// If nil or 0.0.0.0, the host's default interface will be used.
2015-01-20 03:25:06 +00:00
PublicAddress net . IP
2015-01-28 22:39:57 +00:00
// Control the interval that pod, node IP, and node heath status caches
// expire.
CacheTimeout time . Duration
2015-02-13 22:58:42 +00:00
// The name of the cluster.
ClusterName string
2015-05-22 22:28:48 +00:00
2015-05-23 20:41:11 +00:00
// The range of IPs to be assigned to services with type=ClusterIP or greater
ServiceClusterIPRange * net . IPNet
2015-07-01 20:57:48 +00:00
// The IP address for the master service (must be inside ServiceClusterIPRange
ServiceReadWriteIP net . IP
2015-05-22 22:58:39 +00:00
// The range of ports to be assigned to services with type=NodePort or greater
2015-05-23 20:41:11 +00:00
ServiceNodePortRange util . PortRange
2015-05-28 04:38:21 +00:00
// Used for secure proxy. If empty, don't use secure proxy.
2015-05-28 18:45:08 +00:00
SSHUser string
SSHKeyfile string
InstallSSHKey InstallSSHKey
2014-07-27 02:16:39 +00:00
}
2015-05-28 18:45:08 +00:00
type InstallSSHKey func ( user string , data [ ] byte ) error
2014-06-16 06:29:07 +00:00
// Master contains state for a Kubernetes cluster master/api server.
type Master struct {
2014-10-23 23:55:14 +00:00
// "Inputs", Copied from Config
2015-05-23 20:41:11 +00:00
serviceClusterIPRange * net . IPNet
serviceNodePortRange util . PortRange
cacheTimeout time . Duration
2015-06-16 02:39:31 +00:00
minRequestTimeout time . Duration
2014-12-15 20:29:55 +00:00
2014-10-23 23:55:14 +00:00
mux apiserver . Mux
2015-01-16 00:18:47 +00:00
muxHelper * apiserver . MuxHelper
2015-06-16 02:39:31 +00:00
handlerContainer * restful . Container
2014-11-11 07:11:45 +00:00
rootWebService * restful . WebService
2015-05-03 22:44:05 +00:00
enableCoreControllers bool
2014-10-23 23:55:14 +00:00
enableLogsSupport bool
enableUISupport bool
2014-12-15 20:29:55 +00:00
enableSwaggerSupport bool
2015-03-13 15:44:11 +00:00
enableProfiling bool
2014-10-23 23:55:14 +00:00
apiPrefix string
corsAllowedOriginList util . StringList
2014-11-19 15:31:43 +00:00
authenticator authenticator . Request
2014-11-02 06:50:00 +00:00
authorizer authorizer . Authorizer
2015-01-07 19:33:21 +00:00
admissionControl admission . Interface
2014-10-28 23:49:52 +00:00
masterCount int
2015-04-28 19:23:13 +00:00
v1 bool
2015-02-11 22:09:25 +00:00
requestContextMapper api . RequestContextMapper
2014-10-28 00:56:33 +00:00
2015-03-25 04:39:19 +00:00
// External host is the name that should be used in external (public internet) URLs for this master
externalHost string
// clusterIP is the IP address of the master within the cluster.
clusterIP net . IP
2015-01-20 03:25:06 +00:00
publicReadWritePort int
serviceReadWriteIP net . IP
serviceReadWritePort int
masterServices * util . Runner
2014-11-06 17:11:31 +00:00
2015-02-12 00:07:54 +00:00
// storage contains the RESTful endpoints exposed by this master
2015-03-21 16:24:16 +00:00
storage map [ string ] rest . Storage
2015-02-12 00:07:54 +00:00
// registries are internal client APIs for accessing the storage layer
// TODO: define the internal typed interface in a way that clients can
// also be replaced
2015-05-23 20:41:11 +00:00
nodeRegistry minion . Registry
namespaceRegistry namespace . Registry
serviceRegistry service . Registry
endpointRegistry endpoint . Registry
serviceClusterIPAllocator service . RangeRegistry
serviceNodePortAllocator service . RangeRegistry
2015-02-12 00:07:54 +00:00
2014-11-06 17:11:31 +00:00
// "Outputs"
Handler http . Handler
InsecureHandler http . Handler
2015-05-28 04:38:21 +00:00
// Used for secure proxy
2015-07-14 19:30:43 +00:00
dialer apiserver . ProxyDialerFunc
tunnels * util . SSHTunnelList
tunnelsLock sync . Mutex
installSSHKey InstallSSHKey
lastSync int64 // Seconds since Epoch
lastSyncMetric prometheus . GaugeFunc
clock util . Clock
2014-06-16 06:29:07 +00:00
}
2015-07-24 11:09:49 +00:00
// NewEtcdStorage returns a StorageInterface for the provided arguments or an error if the version
2014-09-11 23:01:29 +00:00
// is incorrect.
2015-07-24 11:09:49 +00:00
func NewEtcdStorage ( client tools . EtcdClient , version string , prefix string ) ( etcdStorage tools . StorageInterface , err error ) {
2014-09-11 23:01:29 +00:00
if version == "" {
version = latest . Version
}
2014-09-25 22:08:09 +00:00
versionInterfaces , err := latest . InterfacesFor ( version )
2014-09-11 23:01:29 +00:00
if err != nil {
2015-07-24 11:09:49 +00:00
return etcdStorage , err
2014-09-11 23:01:29 +00:00
}
2015-07-24 11:09:49 +00:00
return tools . NewEtcdStorage ( client , versionInterfaces . Codec , prefix ) , nil
2014-09-11 23:01:29 +00:00
}
2014-10-28 00:56:33 +00:00
// setDefaults fills in any fields not set that are required to have valid data.
func setDefaults ( c * Config ) {
2015-05-23 20:41:11 +00:00
if c . ServiceClusterIPRange == nil {
2014-10-29 19:27:35 +00:00
defaultNet := "10.0.0.0/24"
2015-05-23 20:41:11 +00:00
glog . Warningf ( "Network range for service cluster IPs is unspecified. Defaulting to %v." , defaultNet )
_ , serviceClusterIPRange , err := net . ParseCIDR ( defaultNet )
2014-10-29 19:27:35 +00:00
if err != nil {
glog . Fatalf ( "Unable to parse CIDR: %v" , err )
}
2015-05-23 20:41:11 +00:00
if size := ipallocator . RangeSize ( serviceClusterIPRange ) ; size < 8 {
glog . Fatalf ( "The service cluster IP range must be at least %d IP addresses" , 8 )
2015-05-03 22:44:05 +00:00
}
2015-05-23 20:41:11 +00:00
c . ServiceClusterIPRange = serviceClusterIPRange
2014-10-29 19:27:35 +00:00
}
2015-07-01 20:57:48 +00:00
if c . ServiceReadWriteIP == nil {
// Select the first valid IP from ServiceClusterIPRange to use as the master service IP.
serviceReadWriteIP , err := ipallocator . GetIndexedIP ( c . ServiceClusterIPRange , 1 )
if err != nil {
glog . Fatalf ( "Failed to generate service read-write IP for master service: %v" , err )
}
glog . V ( 4 ) . Infof ( "Setting master service IP to %q (read-write)." , serviceReadWriteIP )
c . ServiceReadWriteIP = serviceReadWriteIP
}
2015-05-23 20:41:11 +00:00
if c . ServiceNodePortRange . Size == 0 {
2015-05-22 22:28:48 +00:00
// 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)
2015-05-22 22:58:39 +00:00
// but then that breaks the strict nestedness of ServiceType.
2015-05-22 22:28:48 +00:00
// Review post-v1
2015-06-06 18:40:13 +00:00
defaultServiceNodePortRange := util . PortRange { Base : 30000 , Size : 2768 }
2015-05-23 20:41:11 +00:00
c . ServiceNodePortRange = defaultServiceNodePortRange
glog . Infof ( "Node port range unspecified. Defaulting to %v." , c . ServiceNodePortRange )
2015-05-22 22:28:48 +00:00
}
2014-10-28 23:49:52 +00:00
if c . MasterCount == 0 {
// Clearly, there will be at least one master.
c . MasterCount = 1
}
2015-02-07 00:33:28 +00:00
if c . ReadWritePort == 0 {
c . ReadWritePort = 6443
}
2015-01-28 22:39:57 +00:00
if c . CacheTimeout == 0 {
c . CacheTimeout = 5 * time . Second
}
2015-04-07 18:49:08 +00:00
for c . PublicAddress == nil || c . PublicAddress . IsUnspecified ( ) {
// TODO: This should be done in the caller and just require a
// valid value to be passed in.
2015-03-02 10:41:45 +00:00
hostIP , err := util . ChooseHostInterface ( )
2014-10-28 00:56:33 +00:00
if err != nil {
2015-03-02 10:41:45 +00:00
glog . Fatalf ( "Unable to find suitable network address.error='%v' . " +
"Will try again in 5 seconds. Set the public address directly to avoid this wait." , err )
2014-11-05 20:07:33 +00:00
time . Sleep ( 5 * time . Second )
2014-10-28 00:56:33 +00:00
}
2015-03-02 10:41:45 +00:00
c . PublicAddress = hostIP
glog . Infof ( "Will report %v as public IP address." , c . PublicAddress )
2014-10-28 00:56:33 +00:00
}
2015-02-11 22:09:25 +00:00
if c . RequestContextMapper == nil {
c . RequestContextMapper = api . NewRequestContextMapper ( )
}
2014-10-28 00:56:33 +00:00
}
2014-10-28 20:02:19 +00:00
// New returns a new instance of Master from the given config.
// Certain config fields will be set to a default value if unset,
// including:
2015-05-23 20:41:11 +00:00
// ServiceClusterIPRange
// ServiceNodePortRange
2014-10-28 20:02:19 +00:00
// MasterCount
// ReadWritePort
// PublicAddress
// Certain config fields must be specified, including:
// KubeletClient
// Public fields:
// Handler -- The returned master has a field TopHandler which is an
// http.Handler which handles all the endpoints provided by the master,
// including the API, the UI, and miscelaneous debugging endpoints. All
// these are subject to authorization and authentication.
2014-11-06 17:11:31 +00:00
// InsecureHandler -- an http.Handler which handles all the same
// endpoints as Handler, but no authorization and authentication is done.
2014-10-28 20:02:19 +00:00
// Public methods:
// HandleWithAuth -- Allows caller to add an http.Handler for an endpoint
// that uses the same authentication and authorization (if any is configured)
// as the master's built-in endpoints.
// If the caller wants to add additional endpoints not using the master's
// auth, then the caller should create a handler for those endpoints, which delegates the
// any unhandled paths to "Handler".
2014-07-27 02:16:39 +00:00
func New ( c * Config ) * Master {
2014-10-28 00:56:33 +00:00
setDefaults ( c )
2014-11-03 22:50:41 +00:00
if c . KubeletClient == nil {
glog . Fatalf ( "master.New() called with config.KubeletClient == nil" )
}
2014-12-15 20:29:55 +00:00
2014-06-16 06:29:07 +00:00
m := & Master {
2015-05-23 20:41:11 +00:00
serviceClusterIPRange : c . ServiceClusterIPRange ,
serviceNodePortRange : c . ServiceNodePortRange ,
2014-11-11 07:11:45 +00:00
rootWebService : new ( restful . WebService ) ,
2015-05-03 22:44:05 +00:00
enableCoreControllers : c . EnableCoreControllers ,
2014-10-23 23:55:14 +00:00
enableLogsSupport : c . EnableLogsSupport ,
enableUISupport : c . EnableUISupport ,
2014-12-15 20:29:55 +00:00
enableSwaggerSupport : c . EnableSwaggerSupport ,
2015-03-13 15:44:11 +00:00
enableProfiling : c . EnableProfiling ,
2014-10-23 23:55:14 +00:00
apiPrefix : c . APIPrefix ,
corsAllowedOriginList : c . CorsAllowedOriginList ,
2014-11-19 15:31:43 +00:00
authenticator : c . Authenticator ,
2014-11-02 06:50:00 +00:00
authorizer : c . Authorizer ,
2015-01-06 16:44:43 +00:00
admissionControl : c . AdmissionControl ,
2015-06-04 18:37:44 +00:00
v1 : ! c . DisableV1 ,
2015-02-11 22:09:25 +00:00
requestContextMapper : c . RequestContextMapper ,
2015-01-28 22:39:57 +00:00
2015-06-16 02:39:31 +00:00
cacheTimeout : c . CacheTimeout ,
minRequestTimeout : time . Duration ( c . MinRequestTimeout ) * time . Second ,
2014-10-16 21:18:16 +00:00
2015-01-20 03:25:06 +00:00
masterCount : c . MasterCount ,
2015-03-25 04:39:19 +00:00
externalHost : c . ExternalHost ,
clusterIP : c . PublicAddress ,
2015-01-20 03:25:06 +00:00
publicReadWritePort : c . ReadWritePort ,
2015-07-01 20:57:48 +00:00
serviceReadWriteIP : c . ServiceReadWriteIP ,
2015-01-20 03:25:06 +00:00
// TODO: serviceReadWritePort should be passed in as an argument, it may not always be 443
serviceReadWritePort : 443 ,
2015-06-04 18:58:38 +00:00
2015-05-29 21:29:17 +00:00
installSSHKey : c . InstallSSHKey ,
2014-06-16 06:29:07 +00:00
}
2014-12-15 20:29:55 +00:00
2015-05-27 01:39:42 +00:00
var handlerContainer * restful . Container
2014-12-15 20:29:55 +00:00
if c . RestfulContainer != nil {
m . mux = c . RestfulContainer . ServeMux
2015-05-27 01:39:42 +00:00
handlerContainer = c . RestfulContainer
2014-12-15 20:29:55 +00:00
} else {
mux := http . NewServeMux ( )
m . mux = mux
2015-05-27 01:39:42 +00:00
handlerContainer = NewHandlerContainer ( mux )
2014-12-15 20:29:55 +00:00
}
2015-06-16 02:39:31 +00:00
m . handlerContainer = handlerContainer
2015-01-31 00:08:59 +00:00
// Use CurlyRouter to be able to use regular expressions in paths. Regular expressions are required in paths for example for proxy (where the path is proxy/{kind}/{name}/{*})
m . handlerContainer . Router ( restful . CurlyRouter { } )
2015-01-16 00:18:47 +00:00
m . muxHelper = & apiserver . MuxHelper { m . mux , [ ] string { } }
2014-12-15 20:29:55 +00:00
2014-10-08 23:14:37 +00:00
m . init ( c )
2015-05-06 21:54:54 +00:00
2014-06-16 06:29:07 +00:00
return m
}
2014-10-28 20:02:19 +00:00
// HandleWithAuth adds an http.Handler for pattern to an http.ServeMux
// Applies the same authentication and authorization (if any is configured)
// to the request is used for the master's built-in endpoints.
func ( m * Master ) HandleWithAuth ( pattern string , handler http . Handler ) {
// TODO: Add a way for plugged-in endpoints to translate their
// URLs into attributes that an Authorizer can understand, and have
// sensible policy defaults for plugged-in endpoints. This will be different
// for generic endpoints versus REST object endpoints.
2014-11-11 07:11:45 +00:00
// TODO: convert to go-restful
2015-01-16 00:18:47 +00:00
m . muxHelper . Handle ( pattern , handler )
2014-10-28 20:02:19 +00:00
}
// HandleFuncWithAuth adds an http.Handler for pattern to an http.ServeMux
// Applies the same authentication and authorization (if any is configured)
// to the request is used for the master's built-in endpoints.
func ( m * Master ) HandleFuncWithAuth ( pattern string , handler func ( http . ResponseWriter , * http . Request ) ) {
2014-11-11 07:11:45 +00:00
// TODO: convert to go-restful
2015-01-16 00:18:47 +00:00
m . muxHelper . HandleFunc ( pattern , handler )
2014-10-28 20:02:19 +00:00
}
2014-11-11 07:11:45 +00:00
func NewHandlerContainer ( mux * http . ServeMux ) * restful . Container {
container := restful . NewContainer ( )
container . ServeMux = mux
container . RecoverHandler ( logStackOnRecover )
return container
}
//TODO: Unify with RecoverPanics?
func logStackOnRecover ( panicReason interface { } , httpWriter http . ResponseWriter ) {
var buffer bytes . Buffer
buffer . WriteString ( fmt . Sprintf ( "recover from panic situation: - %v\r\n" , panicReason ) )
for i := 2 ; ; i += 1 {
_ , file , line , ok := rt . Caller ( i )
if ! ok {
break
}
buffer . WriteString ( fmt . Sprintf ( " %s:%d\r\n" , file , line ) )
}
glog . Errorln ( buffer . String ( ) )
}
2014-10-08 23:14:37 +00:00
// init initializes master.
func ( m * Master ) init ( c * Config ) {
2015-07-14 19:30:43 +00:00
healthzChecks := [ ] healthz . HealthzChecker { }
m . clock = util . RealClock { }
2015-07-24 11:09:49 +00:00
podStorage := podetcd . NewStorage ( c . DatabaseStorage , c . KubeletClient )
2015-04-06 12:23:33 +00:00
podRegistry := pod . NewRegistry ( podStorage . Pod )
2015-02-11 23:37:12 +00:00
2015-07-24 11:09:49 +00:00
podTemplateStorage := podtemplateetcd . NewREST ( c . DatabaseStorage )
2015-03-04 00:54:17 +00:00
2015-07-24 11:09:49 +00:00
eventRegistry := event . NewEtcdRegistry ( c . DatabaseStorage , uint64 ( c . EventTTL . Seconds ( ) ) )
limitRangeRegistry := limitrange . NewEtcdRegistry ( c . DatabaseStorage )
2015-03-13 19:15:04 +00:00
2015-07-24 11:09:49 +00:00
resourceQuotaStorage , resourceQuotaStatusStorage := resourcequotaetcd . NewStorage ( c . DatabaseStorage )
secretStorage := secretetcd . NewStorage ( c . DatabaseStorage )
serviceAccountStorage := serviceaccountetcd . NewStorage ( c . DatabaseStorage )
persistentVolumeStorage , persistentVolumeStatusStorage := pvetcd . NewStorage ( c . DatabaseStorage )
persistentVolumeClaimStorage , persistentVolumeClaimStatusStorage := pvcetcd . NewStorage ( c . DatabaseStorage )
2015-03-12 15:08:06 +00:00
2015-07-24 11:09:49 +00:00
namespaceStorage , namespaceStatusStorage , namespaceFinalizeStorage := namespaceetcd . NewStorage ( c . DatabaseStorage )
2015-03-12 15:08:06 +00:00
m . namespaceRegistry = namespace . NewRegistry ( namespaceStorage )
2015-02-12 00:07:54 +00:00
2015-07-24 11:09:49 +00:00
endpointsStorage := endpointsetcd . NewStorage ( c . DatabaseStorage )
2015-03-15 06:03:46 +00:00
m . endpointRegistry = endpoint . NewRegistry ( endpointsStorage )
2015-07-24 11:09:49 +00:00
nodeStorage , nodeStatusStorage := nodeetcd . NewStorage ( c . DatabaseStorage , c . KubeletClient )
2015-03-13 14:49:38 +00:00
m . nodeRegistry = minion . NewRegistry ( nodeStorage )
2015-02-12 00:07:54 +00:00
// TODO: split me up into distinct storage registries
2015-07-24 11:09:49 +00:00
registry := etcd . NewRegistry ( c . DatabaseStorage , podRegistry , m . endpointRegistry )
2015-02-12 00:07:54 +00:00
m . serviceRegistry = registry
2015-02-11 23:37:12 +00:00
2015-05-23 20:41:11 +00:00
var serviceClusterIPRegistry service . RangeRegistry
serviceClusterIPAllocator := ipallocator . NewAllocatorCIDRRange ( m . serviceClusterIPRange , func ( max int , rangeSpec string ) allocator . Interface {
2015-05-22 22:28:48 +00:00
mem := allocator . NewAllocationMap ( max , rangeSpec )
2015-07-24 11:09:49 +00:00
etcd := etcdallocator . NewEtcd ( mem , "/ranges/serviceips" , "serviceipallocation" , c . DatabaseStorage )
2015-05-23 20:41:11 +00:00
serviceClusterIPRegistry = etcd
2015-05-22 22:28:48 +00:00
return etcd
} )
2015-05-23 20:41:11 +00:00
m . serviceClusterIPAllocator = serviceClusterIPRegistry
2015-05-22 22:28:48 +00:00
var serviceNodePortRegistry service . RangeRegistry
2015-05-23 20:41:11 +00:00
serviceNodePortAllocator := portallocator . NewPortAllocatorCustom ( m . serviceNodePortRange , func ( max int , rangeSpec string ) allocator . Interface {
2015-05-22 22:28:48 +00:00
mem := allocator . NewAllocationMap ( max , rangeSpec )
2015-07-24 11:09:49 +00:00
etcd := etcdallocator . NewEtcd ( mem , "/ranges/servicenodeports" , "servicenodeportallocation" , c . DatabaseStorage )
2015-05-22 22:28:48 +00:00
serviceNodePortRegistry = etcd
return etcd
} )
m . serviceNodePortAllocator = serviceNodePortRegistry
2015-05-03 22:44:05 +00:00
2015-07-24 11:09:49 +00:00
controllerStorage := controlleretcd . NewREST ( c . DatabaseStorage )
2015-03-16 04:36:26 +00:00
2014-11-11 07:11:45 +00:00
// TODO: Factor out the core API registration
2015-03-21 16:24:16 +00:00
m . storage = map [ string ] rest . Storage {
2015-04-14 15:12:27 +00:00
"pods" : podStorage . Pod ,
"pods/status" : podStorage . Status ,
"pods/log" : podStorage . Log ,
"pods/exec" : podStorage . Exec ,
"pods/portforward" : podStorage . PortForward ,
"pods/proxy" : podStorage . Proxy ,
"pods/binding" : podStorage . Binding ,
"bindings" : podStorage . Binding ,
2015-02-11 23:37:12 +00:00
2015-03-04 00:54:17 +00:00
"podTemplates" : podTemplateStorage ,
2015-03-16 04:36:26 +00:00
"replicationControllers" : controllerStorage ,
2015-05-23 20:41:11 +00:00
"services" : service . NewStorage ( m . serviceRegistry , m . nodeRegistry , m . endpointRegistry , serviceClusterIPAllocator , serviceNodePortAllocator , c . ClusterName ) ,
2015-03-15 06:03:46 +00:00
"endpoints" : endpointsStorage ,
2015-02-12 00:07:54 +00:00
"nodes" : nodeStorage ,
2015-04-08 09:32:47 +00:00
"nodes/status" : nodeStatusStorage ,
2015-03-21 16:32:31 +00:00
"events" : event . NewStorage ( eventRegistry ) ,
2014-08-15 23:01:33 +00:00
2015-04-06 18:40:45 +00:00
"limitRanges" : limitrange . NewStorage ( limitRangeRegistry ) ,
"resourceQuotas" : resourceQuotaStorage ,
"resourceQuotas/status" : resourceQuotaStatusStorage ,
"namespaces" : namespaceStorage ,
"namespaces/status" : namespaceStatusStorage ,
"namespaces/finalize" : namespaceFinalizeStorage ,
2015-04-28 03:50:56 +00:00
"secrets" : secretStorage ,
2015-04-27 22:53:28 +00:00
"serviceAccounts" : serviceAccountStorage ,
2015-04-06 18:40:45 +00:00
"persistentVolumes" : persistentVolumeStorage ,
"persistentVolumes/status" : persistentVolumeStatusStorage ,
"persistentVolumeClaims" : persistentVolumeClaimStorage ,
"persistentVolumeClaims/status" : persistentVolumeClaimStatusStorage ,
2015-04-15 19:23:02 +00:00
2015-05-14 00:29:25 +00:00
"componentStatuses" : componentstatus . NewStorage ( func ( ) map [ string ] apiserver . Server { return m . getServersToValidate ( c ) } ) ,
2014-06-16 06:29:07 +00:00
}
2014-10-28 00:56:33 +00:00
2015-06-16 02:39:31 +00:00
// establish the node proxy dialer
2015-05-28 04:38:21 +00:00
if len ( c . SSHUser ) > 0 {
2015-06-18 06:10:32 +00:00
// 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 ]
}
2015-05-28 04:38:21 +00:00
glog . Infof ( "Setting up proxy: %s %s" , c . SSHUser , c . SSHKeyfile )
2015-06-16 02:39:31 +00:00
2015-06-17 18:49:13 +00:00
// public keyfile is written last, so check for that.
publicKeyFile := c . SSHKeyfile + ".pub"
exists , err := util . FileExists ( publicKeyFile )
2015-05-28 18:45:08 +00:00
if err != nil {
glog . Errorf ( "Error detecting if key exists: %v" , err )
} else if ! exists {
glog . Infof ( "Key doesn't exist, attempting to create" )
2015-06-17 18:49:13 +00:00
err := m . generateSSHKey ( c . SSHUser , c . SSHKeyfile , publicKeyFile )
2015-05-28 18:45:08 +00:00
if err != nil {
glog . Errorf ( "Failed to create key pair: %v" , err )
}
}
2015-06-17 18:49:13 +00:00
m . tunnels = & util . SSHTunnelList { }
2015-06-16 02:39:31 +00:00
m . dialer = m . Dial
2015-06-17 18:49:13 +00:00
m . setupSecureProxy ( c . SSHUser , c . SSHKeyfile , publicKeyFile )
2015-07-14 19:30:43 +00:00
m . lastSync = m . clock . Now ( ) . Unix ( )
2015-05-29 22:33:22 +00:00
// This is pretty ugly. A better solution would be to pull this all the way up into the
// server.go file.
httpKubeletClient , ok := c . KubeletClient . ( * client . HTTPKubeletClient )
if ok {
2015-06-16 02:39:31 +00:00
httpKubeletClient . Config . Dial = m . dialer
2015-05-29 22:33:22 +00:00
transport , err := client . MakeTransport ( httpKubeletClient . Config )
if err != nil {
glog . Errorf ( "Error setting up transport over SSH: %v" , err )
} else {
httpKubeletClient . Client . Transport = transport
}
} else {
glog . Errorf ( "Failed to cast %v to HTTPKubeletClient, skipping SSH tunnel." )
}
2015-07-14 19:30:43 +00:00
healthzChecks = append ( healthzChecks , healthz . NamedCheck ( "SSH Tunnel Check" , m . IsTunnelSyncHealthy ) )
m . lastSyncMetric = 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 ( m . secondsSinceSync ( ) ) } )
2015-05-28 04:38:21 +00:00
}
2015-05-11 23:09:25 +00:00
apiVersions := [ ] string { }
2015-04-28 19:23:13 +00:00
if m . v1 {
2015-06-16 02:39:31 +00:00
if err := m . api_v1 ( ) . InstallREST ( m . handlerContainer ) ; err != nil {
2015-04-28 19:23:13 +00:00
glog . Fatalf ( "Unable to setup API v1: %v" , err )
}
apiVersions = append ( apiVersions , "v1" )
2015-01-08 17:42:20 +00:00
}
2014-11-11 07:11:45 +00:00
2015-07-14 19:30:43 +00:00
apiserver . InstallSupport ( m . muxHelper , m . rootWebService , c . EnableProfiling , healthzChecks ... )
2015-06-16 02:39:31 +00:00
apiserver . AddApiWebService ( m . handlerContainer , c . APIPrefix , apiVersions )
2015-04-23 07:06:59 +00:00
defaultVersion := m . defaultAPIGroupVersion ( )
requestInfoResolver := & apiserver . APIRequestInfoResolver { util . NewStringSet ( strings . TrimPrefix ( defaultVersion . Root , "/" ) ) , defaultVersion . Mapper }
2015-06-16 02:39:31 +00:00
apiserver . InstallServiceErrorHandler ( m . handlerContainer , requestInfoResolver , apiVersions )
2015-01-07 23:43:38 +00:00
// Register root handler.
// We do not register this using restful Webservice since we do not want to surface this in api docs.
2015-01-30 23:53:04 +00:00
// Allow master to be embedded in contexts which already have something registered at the root
if c . EnableIndex {
2015-06-16 02:39:31 +00:00
m . mux . HandleFunc ( "/" , apiserver . IndexHandler ( m . handlerContainer , m . muxHelper ) )
2015-01-30 23:53:04 +00:00
}
2014-11-11 07:11:45 +00:00
2014-10-23 20:56:18 +00:00
if c . EnableLogsSupport {
2015-01-16 00:18:47 +00:00
apiserver . InstallLogsSupport ( m . muxHelper )
2014-10-23 20:56:18 +00:00
}
if c . EnableUISupport {
2015-01-16 00:18:47 +00:00
ui . InstallSupport ( m . muxHelper , m . enableSwaggerSupport )
2014-10-23 20:56:18 +00:00
}
2014-10-23 23:55:14 +00:00
2015-03-13 15:44:11 +00:00
if c . EnableProfiling {
m . mux . HandleFunc ( "/debug/pprof/" , pprof . Index )
m . mux . HandleFunc ( "/debug/pprof/profile" , pprof . Profile )
m . mux . HandleFunc ( "/debug/pprof/symbol" , pprof . Symbol )
}
2014-11-11 07:11:45 +00:00
2014-10-23 23:55:14 +00:00
handler := http . Handler ( m . mux . ( * http . ServeMux ) )
2014-11-11 07:11:45 +00:00
// TODO: handle CORS and auth using go-restful
// See github.com/emicklei/go-restful/blob/master/examples/restful-CORS-filter.go, and
// github.com/emicklei/go-restful/blob/master/examples/restful-basic-authentication.go
2014-10-23 23:55:14 +00:00
if len ( c . CorsAllowedOriginList ) > 0 {
allowedOriginRegexps , err := util . CompileRegexps ( c . CorsAllowedOriginList )
if err != nil {
glog . Fatalf ( "Invalid CORS allowed origin, --cors_allowed_origins flag was set to %v - %v" , strings . Join ( c . CorsAllowedOriginList , "," ) , err )
}
handler = apiserver . CORS ( handler , allowedOriginRegexps , nil , nil , "true" )
}
2014-11-06 17:11:31 +00:00
m . InsecureHandler = handler
2015-02-11 22:09:25 +00:00
attributeGetter := apiserver . NewRequestAttributeGetter ( m . requestContextMapper , latest . RESTMapper , "api" )
2014-11-02 06:50:00 +00:00
handler = apiserver . WithAuthorizationCheck ( handler , attributeGetter , m . authorizer )
2014-10-16 21:18:16 +00:00
// Install Authenticator
2015-02-11 22:09:25 +00:00
if c . Authenticator != nil {
2015-04-28 05:24:26 +00:00
authenticatedHandler , err := handlers . NewRequestAuthenticator ( m . requestContextMapper , c . Authenticator , handlers . Unauthorized ( c . SupportsBasicAuth ) , handler )
2015-02-11 22:09:25 +00:00
if err != nil {
glog . Fatalf ( "Could not initialize authenticator: %v" , err )
}
handler = authenticatedHandler
2014-10-23 23:55:14 +00:00
}
2014-11-11 07:11:45 +00:00
// Install root web services
m . handlerContainer . Add ( m . rootWebService )
2014-12-15 20:29:55 +00:00
// TODO: Make this optional? Consumers of master depend on this currently.
m . Handler = handler
if m . enableSwaggerSupport {
m . InstallSwaggerAPI ( )
}
2015-02-11 22:09:25 +00:00
// After all wrapping is done, put a context filter around both handlers
if handler , err := api . NewRequestContextFilter ( m . requestContextMapper , m . Handler ) ; err != nil {
glog . Fatalf ( "Could not initialize request context filter: %v" , err )
} else {
m . Handler = handler
}
if handler , err := api . NewRequestContextFilter ( m . requestContextMapper , m . InsecureHandler ) ; err != nil {
glog . Fatalf ( "Could not initialize request context filter: %v" , err )
} else {
m . InsecureHandler = handler
}
2014-12-15 20:29:55 +00:00
// TODO: Attempt clean shutdown?
2015-05-03 22:44:05 +00:00
if m . enableCoreControllers {
2015-05-04 19:11:19 +00:00
m . NewBootstrapController ( ) . Start ( )
}
}
// NewBootstrapController returns a controller for watching the core capabilities of the master.
func ( m * Master ) NewBootstrapController ( ) * Controller {
return & Controller {
NamespaceRegistry : m . namespaceRegistry ,
ServiceRegistry : m . serviceRegistry ,
MasterCount : m . masterCount ,
2015-05-23 20:41:11 +00:00
EndpointRegistry : m . endpointRegistry ,
EndpointInterval : 10 * time . Second ,
2015-05-22 22:28:48 +00:00
2015-05-23 20:41:11 +00:00
ServiceClusterIPRegistry : m . serviceClusterIPAllocator ,
ServiceClusterIPRange : m . serviceClusterIPRange ,
ServiceClusterIPInterval : 3 * time . Minute ,
ServiceNodePortRegistry : m . serviceNodePortAllocator ,
ServiceNodePortRange : m . serviceNodePortRange ,
2015-05-22 22:28:48 +00:00
ServiceNodePortInterval : 3 * time . Minute ,
2015-05-04 19:11:19 +00:00
PublicIP : m . clusterIP ,
ServiceIP : m . serviceReadWriteIP ,
ServicePort : m . serviceReadWritePort ,
PublicServicePort : m . publicReadWritePort ,
2015-05-03 22:44:05 +00:00
}
2014-12-15 20:29:55 +00:00
}
// InstallSwaggerAPI installs the /swaggerapi/ endpoint to allow schema discovery
// and traversal. It is optional to allow consumers of the Kubernetes master to
// register their own web services into the Kubernetes mux prior to initialization
// of swagger, so that other resource types show up in the documentation.
func ( m * Master ) InstallSwaggerAPI ( ) {
2015-03-25 04:39:19 +00:00
hostAndPort := m . externalHost
protocol := "https://"
// TODO: this is kind of messed up, we should just pipe in the full URL from the outside, rather
// than guessing at it.
if len ( m . externalHost ) == 0 && m . clusterIP != nil {
host := m . clusterIP . String ( )
if m . publicReadWritePort != 0 {
hostAndPort = net . JoinHostPort ( host , strconv . Itoa ( m . publicReadWritePort ) )
}
2015-03-11 23:14:14 +00:00
}
2015-03-25 04:39:19 +00:00
webServicesUrl := protocol + hostAndPort
2014-11-11 07:11:45 +00:00
// Enable swagger UI and discovery API
swaggerConfig := swagger . Config {
2015-03-11 23:14:14 +00:00
WebServicesUrl : webServicesUrl ,
WebServices : m . handlerContainer . RegisteredWebServices ( ) ,
2014-12-20 00:16:54 +00:00
ApiPath : "/swaggerapi/" ,
SwaggerPath : "/swaggerui/" ,
2015-01-05 23:50:19 +00:00
SwaggerFilePath : "/swagger-ui/" ,
2014-11-11 07:11:45 +00:00
}
2015-06-16 02:39:31 +00:00
swagger . RegisterSwaggerService ( swaggerConfig , m . handlerContainer )
2014-06-16 06:29:07 +00:00
}
2015-05-14 00:29:25 +00:00
func ( m * Master ) getServersToValidate ( c * Config ) map [ string ] apiserver . Server {
2014-11-02 20:52:31 +00:00
serversToValidate := map [ string ] apiserver . Server {
2014-12-16 03:45:27 +00:00
"controller-manager" : { Addr : "127.0.0.1" , Port : ports . ControllerManagerPort , Path : "/healthz" } ,
"scheduler" : { Addr : "127.0.0.1" , Port : ports . SchedulerPort , Path : "/healthz" } ,
2014-11-02 20:52:31 +00:00
}
2015-07-24 11:09:49 +00:00
for ix , machine := range c . DatabaseStorage . Backends ( ) {
2014-11-02 20:52:31 +00:00
etcdUrl , err := url . Parse ( machine )
if err != nil {
glog . Errorf ( "Failed to parse etcd url for validation: %v" , err )
continue
}
var port int
var addr string
if strings . Contains ( etcdUrl . Host , ":" ) {
var portString string
addr , portString , err = net . SplitHostPort ( etcdUrl . Host )
if err != nil {
glog . Errorf ( "Failed to split host/port: %s (%v)" , etcdUrl . Host , err )
continue
}
port , _ = strconv . Atoi ( portString )
} else {
addr = etcdUrl . Host
port = 4001
}
2015-06-24 04:47:24 +00:00
serversToValidate [ fmt . Sprintf ( "etcd-%d" , ix ) ] = apiserver . Server { Addr : addr , Port : port , Path : "/health" , Validate : tools . EtcdHealthCheck }
2014-11-02 20:52:31 +00:00
}
return serversToValidate
}
2015-03-04 20:57:05 +00:00
func ( m * Master ) defaultAPIGroupVersion ( ) * apiserver . APIGroupVersion {
return & apiserver . APIGroupVersion {
Root : m . apiPrefix ,
Mapper : latest . RESTMapper ,
2015-03-22 21:43:00 +00:00
Creater : api . Scheme ,
Convertor : api . Scheme ,
Typer : api . Scheme ,
Linker : latest . SelfLinker ,
2015-03-04 20:57:05 +00:00
Admit : m . admissionControl ,
Context : m . requestContextMapper ,
2015-06-16 02:39:31 +00:00
ProxyDialerFn : m . dialer ,
MinRequestTimeout : m . minRequestTimeout ,
2015-03-04 20:57:05 +00:00
}
}
2015-04-28 19:23:13 +00:00
// api_v1 returns the resources and codec for API version v1.
func ( m * Master ) api_v1 ( ) * apiserver . APIGroupVersion {
storage := make ( map [ string ] rest . Storage )
for k , v := range m . storage {
storage [ strings . ToLower ( k ) ] = v
}
version := m . defaultAPIGroupVersion ( )
version . Storage = storage
version . Version = "v1"
version . Codec = v1 . Codec
return version
}
2015-05-28 04:38:21 +00:00
2015-07-28 08:10:48 +00:00
// findExternalAddress returns ExternalIP of provided node with fallback to LegacyHostIP.
2015-05-28 04:38:21 +00:00
func findExternalAddress ( node * api . Node ) ( string , error ) {
2015-07-28 08:10:48 +00:00
var fallback string
2015-05-28 04:38:21 +00:00
for ix := range node . Status . Addresses {
addr := & node . Status . Addresses [ ix ]
if addr . Type == api . NodeExternalIP {
return addr . Address , nil
}
2015-07-28 08:10:48 +00:00
if fallback == "" && addr . Type == api . NodeLegacyHostIP {
fallback = addr . Address
}
}
if fallback != "" {
return fallback , nil
2015-05-28 04:38:21 +00:00
}
return "" , fmt . Errorf ( "Couldn't find external address: %v" , node )
}
func ( m * Master ) Dial ( net , addr string ) ( net . Conn , error ) {
2015-06-18 23:31:54 +00:00
// Only lock while picking a tunnel.
tunnel , err := func ( ) ( util . SSHTunnelEntry , error ) {
m . tunnelsLock . Lock ( )
defer m . tunnelsLock . Unlock ( )
return m . tunnels . PickRandomTunnel ( )
} ( )
if err != nil {
return nil , err
}
start := time . Now ( )
id := rand . Int63 ( ) // So you can match begins/ends in the log.
glog . V ( 3 ) . Infof ( "[%x: %v] Dialing..." , id , tunnel . Address )
defer func ( ) {
glog . V ( 3 ) . Infof ( "[%x: %v] Dialed in %v." , id , tunnel . Address , time . Now ( ) . Sub ( start ) )
} ( )
return tunnel . Tunnel . Dial ( net , addr )
2015-05-28 04:38:21 +00:00
}
2015-06-02 16:52:35 +00:00
func ( m * Master ) needToReplaceTunnels ( addrs [ ] string ) bool {
2015-06-29 23:15:42 +00:00
m . tunnelsLock . Lock ( )
defer m . tunnelsLock . Unlock ( )
2015-06-16 17:36:38 +00:00
if m . tunnels == nil || m . tunnels . Len ( ) != len ( addrs ) {
2015-05-28 04:38:21 +00:00
return true
}
2015-06-04 18:58:38 +00:00
// TODO (cjcullen): This doesn't need to be n^2
2015-05-28 04:38:21 +00:00
for ix := range addrs {
if ! m . tunnels . Has ( addrs [ ix ] ) {
return true
}
}
return false
}
2015-06-02 16:52:35 +00:00
func ( m * Master ) getNodeAddresses ( ) ( [ ] string , error ) {
2015-05-28 04:38:21 +00:00
nodes , err := m . nodeRegistry . ListMinions ( api . NewDefaultContext ( ) , labels . Everything ( ) , fields . Everything ( ) )
if err != nil {
2015-06-02 16:52:35 +00:00
return nil , err
2015-05-28 04:38:21 +00:00
}
2015-06-02 16:52:35 +00:00
addrs := [ ] string { }
2015-05-28 04:38:21 +00:00
for ix := range nodes . Items {
node := & nodes . Items [ ix ]
addr , err := findExternalAddress ( node )
if err != nil {
2015-06-02 16:52:35 +00:00
return nil , err
2015-05-28 04:38:21 +00:00
}
2015-06-02 16:52:35 +00:00
addrs = append ( addrs , addr )
2015-05-28 04:38:21 +00:00
}
2015-06-02 16:52:35 +00:00
return addrs , nil
}
2015-05-28 04:38:21 +00:00
2015-07-14 19:30:43 +00:00
func ( m * Master ) IsTunnelSyncHealthy ( req * http . Request ) error {
lag := m . secondsSinceSync ( )
if lag > 600 {
return fmt . Errorf ( "Tunnel sync is taking to long: %d" , lag )
}
return nil
}
func ( m * Master ) secondsSinceSync ( ) int64 {
now := m . clock . Now ( ) . Unix ( )
then := atomic . LoadInt64 ( & m . lastSync )
return now - then
}
2015-06-02 16:52:35 +00:00
func ( m * Master ) replaceTunnels ( user , keyfile string , newAddrs [ ] string ) error {
2015-06-04 18:58:38 +00:00
glog . Infof ( "replacing tunnels. New addrs: %v" , newAddrs )
2015-06-17 18:49:13 +00:00
tunnels := util . MakeSSHTunnels ( user , keyfile , newAddrs )
2015-06-15 19:38:14 +00:00
if err := tunnels . Open ( ) ; err != nil {
return err
}
2015-06-29 23:15:42 +00:00
m . tunnelsLock . Lock ( )
defer m . tunnelsLock . Unlock ( )
2015-05-28 04:38:21 +00:00
if m . tunnels != nil {
m . tunnels . Close ( )
}
m . tunnels = tunnels
2015-07-14 19:30:43 +00:00
atomic . StoreInt64 ( & m . lastSync , m . clock . Now ( ) . Unix ( ) )
2015-05-28 04:38:21 +00:00
return nil
}
2015-06-02 16:52:35 +00:00
func ( m * Master ) loadTunnels ( user , keyfile string ) error {
addrs , err := m . getNodeAddresses ( )
if err != nil {
return err
}
if ! m . needToReplaceTunnels ( addrs ) {
return nil
}
// TODO: This is going to unnecessarily close connections to unchanged nodes.
// See comment about using Watch above.
2015-06-04 18:58:38 +00:00
glog . Info ( "found different nodes. Need to replace tunnels" )
2015-06-02 16:52:35 +00:00
return m . replaceTunnels ( user , keyfile , addrs )
}
func ( m * Master ) refreshTunnels ( user , keyfile string ) error {
addrs , err := m . getNodeAddresses ( )
if err != nil {
return err
2015-05-28 04:38:21 +00:00
}
2015-06-02 16:52:35 +00:00
return m . replaceTunnels ( user , keyfile , addrs )
}
2015-05-28 04:38:21 +00:00
2015-06-17 18:49:13 +00:00
func ( m * Master ) setupSecureProxy ( user , privateKeyfile , publicKeyfile string ) {
// Sync loop to ensure that the SSH key has been installed.
go util . Until ( func ( ) {
if m . installSSHKey == nil {
glog . Error ( "Won't attempt to install ssh key: installSSHKey function is nil" )
return
}
key , err := util . ParsePublicKeyFromFile ( publicKeyfile )
if err != nil {
glog . Errorf ( "Failed to load public key: %v" , err )
return
}
keyData , err := util . EncodeSSHKey ( key )
if err != nil {
glog . Errorf ( "Failed to encode public key: %v" , err )
return
}
if err := m . installSSHKey ( user , keyData ) ; err != nil {
glog . Errorf ( "Failed to install ssh key: %v" , err )
}
} , 5 * time . Minute , util . NeverStop )
2015-05-28 04:38:21 +00:00
// Sync loop for tunnels
// TODO: switch this to watch.
2015-06-15 19:38:14 +00:00
go util . Until ( func ( ) {
2015-06-17 18:49:13 +00:00
if err := m . loadTunnels ( user , privateKeyfile ) ; err != nil {
2015-06-15 19:38:14 +00:00
glog . Errorf ( "Failed to load SSH Tunnels: %v" , err )
}
2015-06-16 17:36:38 +00:00
if m . tunnels != nil && m . tunnels . Len ( ) != 0 {
2015-06-16 00:13:11 +00:00
// Sleep for 10 seconds if we have some tunnels.
// TODO (cjcullen): tunnels can lag behind actually existing nodes.
2015-06-15 19:38:14 +00:00
time . Sleep ( 9 * time . Second )
2015-05-28 04:38:21 +00:00
}
2015-06-16 00:13:11 +00:00
} , 1 * time . Second , util . NeverStop )
2015-06-02 16:52:35 +00:00
// Refresh loop for tunnels
// TODO: could make this more controller-ish
2015-06-15 19:38:14 +00:00
go util . Until ( func ( ) {
time . Sleep ( 5 * time . Minute )
2015-06-17 18:49:13 +00:00
if err := m . refreshTunnels ( user , privateKeyfile ) ; err != nil {
2015-06-15 19:38:14 +00:00
glog . Errorf ( "Failed to refresh SSH Tunnels: %v" , err )
2015-06-02 16:52:35 +00:00
}
2015-06-16 00:13:11 +00:00
} , 0 * time . Second , util . NeverStop )
2015-05-28 04:38:21 +00:00
}
2015-05-28 18:45:08 +00:00
2015-06-17 18:49:13 +00:00
func ( m * Master ) generateSSHKey ( user , privateKeyfile , publicKeyfile string ) error {
2015-05-28 18:45:08 +00:00
private , public , err := util . GenerateKey ( 2048 )
if err != nil {
return err
}
2015-06-17 18:49:13 +00:00
// 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 , util . EncodePrivateKey ( private ) , 0600 ) ; err != nil {
2015-05-29 21:29:17 +00:00
return err
}
2015-06-17 18:49:13 +00:00
publicKeyBytes , err := util . EncodePublicKey ( public )
2015-05-28 18:45:08 +00:00
if err != nil {
return err
}
2015-06-17 18:49:13 +00:00
if err := ioutil . WriteFile ( publicKeyfile + ".tmp" , publicKeyBytes , 0600 ) ; err != nil {
return err
}
return os . Rename ( publicKeyfile + ".tmp" , publicKeyfile )
2015-05-28 18:45:08 +00:00
}