diff --git a/examples/apiserver/apiserver.go b/examples/apiserver/apiserver.go index 08fbe9a7f06..36cfd243d48 100644 --- a/examples/apiserver/apiserver.go +++ b/examples/apiserver/apiserver.go @@ -66,7 +66,7 @@ func Run(serverOptions *genericoptions.ServerRunOptions) error { genericvalidation.VerifyEtcdServersList(serverOptions) config := genericapiserver.NewConfig(serverOptions) config.Serializer = api.Codecs - s, err := genericapiserver.New(config) + s, err := config.New() if err != nil { return fmt.Errorf("Error in bringing up the server: %v", err) } diff --git a/federation/cmd/federation-apiserver/app/server.go b/federation/cmd/federation-apiserver/app/server.go index 0b161eeba67..dbbfb80cb87 100644 --- a/federation/cmd/federation-apiserver/app/server.go +++ b/federation/cmd/federation-apiserver/app/server.go @@ -191,7 +191,7 @@ func Run(s *options.ServerRunOptions) error { cachesize.SetWatchCacheSizes(s.WatchCacheSizes) } - m, err := genericapiserver.New(genericConfig) + m, err := genericConfig.New() if err != nil { return err } diff --git a/pkg/genericapiserver/config.go b/pkg/genericapiserver/config.go new file mode 100644 index 00000000000..049c4bf98cb --- /dev/null +++ b/pkg/genericapiserver/config.go @@ -0,0 +1,467 @@ +/* +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 genericapiserver + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "net/http/pprof" + "os" + "strconv" + "strings" + "time" + + "github.com/emicklei/go-restful" + "github.com/go-openapi/spec" + "github.com/golang/glog" + "gopkg.in/natefinch/lumberjack.v2" + + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apiserver" + "k8s.io/kubernetes/pkg/apiserver/audit" + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/handlers" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/genericapiserver/options" + genericvalidation "k8s.io/kubernetes/pkg/genericapiserver/validation" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/generic/registry" + ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/ui" + "k8s.io/kubernetes/pkg/util" + utilnet "k8s.io/kubernetes/pkg/util/net" +) + +// Config is a structure used to configure a GenericAPIServer. +type Config struct { + // The storage factory for other objects + StorageFactory StorageFactory + AuditLogPath string + AuditLogMaxAge int + AuditLogMaxBackups int + AuditLogMaxSize int + // allow downstream consumers to disable the core controller loops + EnableLogsSupport bool + EnableUISupport bool + // Allow downstream consumers to disable swagger. + // This includes returning the generated swagger spec at /swaggerapi and swagger ui at /swagger-ui. + EnableSwaggerSupport bool + // Allow downstream consumers to disable swagger ui. + // Note that this is ignored if either EnableSwaggerSupport or EnableUISupport is false. + EnableSwaggerUI bool + // Allows api group versions or specific resources to be conditionally enabled/disabled. + APIResourceConfigSource APIResourceConfigSource + // allow downstream consumers to disable the index route + EnableIndex bool + EnableProfiling bool + EnableWatchCache bool + APIPrefix string + APIGroupPrefix string + CorsAllowedOriginList []string + Authenticator authenticator.Request + // TODO(roberthbailey): Remove once the server no longer supports http basic auth. + SupportsBasicAuth bool + Authorizer authorizer.Authorizer + AdmissionControl admission.Interface + MasterServiceNamespace string + // TODO(ericchiang): Determine if policy escalation checks should be an admission controller. + AuthorizerRBACSuperUser string + + // Map requests to contexts. Exported so downstream consumers can provider their own mappers + RequestContextMapper api.RequestContextMapper + + // Required, the interface for serializing and converting objects to and from the wire + Serializer runtime.NegotiatedSerializer + + // If specified, all web services will be registered into this container + RestfulContainer *restful.Container + + // If specified, requests will be allocated a random timeout between this value, and twice this value. + // Note that it is up to the request handlers to ignore or honor this timeout. In seconds. + MinRequestTimeout int + + // Number of masters running; all masters must be started with the + // same value for this field. (Numbers > 1 currently untested.) + MasterCount int + + // The port on PublicAddress where a read-write server will be installed. + // Defaults to 6443 if not set. + ReadWritePort int + + // ExternalHost is the host name to use for external (public internet) facing URLs (e.g. Swagger) + ExternalHost string + + // PublicAddress is the IP address where members of the cluster (kubelet, + // kube-proxy, services, etc.) can reach the GenericAPIServer. + // If nil or 0.0.0.0, the host's default interface will be used. + PublicAddress net.IP + + // Control the interval that pod, node IP, and node heath status caches + // expire. + CacheTimeout time.Duration + + // The range of IPs to be assigned to services with type=ClusterIP or greater + ServiceClusterIPRange *net.IPNet + + // The IP address for the GenericAPIServer service (must be inside ServiceClusterIPRange) + ServiceReadWriteIP net.IP + + // Port for the apiserver service. + ServiceReadWritePort int + + // The range of ports to be assigned to services with type=NodePort or greater + ServiceNodePortRange utilnet.PortRange + + // Used to customize default proxy dial/tls options + ProxyDialer apiserver.ProxyDialerFunc + ProxyTLSClientConfig *tls.Config + + // 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 + + KubernetesServiceNodePort int + + // EnableOpenAPISupport enables OpenAPI support. Allow downstream customers to disable OpenAPI spec. + EnableOpenAPISupport bool + + // OpenAPIInfo will be directly available as Info section of Open API spec. + OpenAPIInfo spec.Info + + // OpenAPIDefaultResponse will be used if an web service operation does not have any responses listed. + OpenAPIDefaultResponse spec.Response +} + +func NewConfig(options *options.ServerRunOptions) *Config { + return &Config{ + APIGroupPrefix: options.APIGroupPrefix, + APIPrefix: options.APIPrefix, + CorsAllowedOriginList: options.CorsAllowedOriginList, + AuditLogPath: options.AuditLogPath, + AuditLogMaxAge: options.AuditLogMaxAge, + AuditLogMaxBackups: options.AuditLogMaxBackups, + AuditLogMaxSize: options.AuditLogMaxSize, + EnableIndex: true, + EnableLogsSupport: options.EnableLogsSupport, + EnableProfiling: options.EnableProfiling, + EnableSwaggerSupport: true, + EnableSwaggerUI: options.EnableSwaggerUI, + EnableUISupport: true, + EnableWatchCache: options.EnableWatchCache, + ExternalHost: options.ExternalHost, + KubernetesServiceNodePort: options.KubernetesServiceNodePort, + MasterCount: options.MasterCount, + MinRequestTimeout: options.MinRequestTimeout, + PublicAddress: options.AdvertiseAddress, + ReadWritePort: options.SecurePort, + ServiceClusterIPRange: &options.ServiceClusterIPRange, + ServiceNodePortRange: options.ServiceNodePortRange, + EnableOpenAPISupport: true, + OpenAPIDefaultResponse: spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: "Default Response."}}, + OpenAPIInfo: spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Generic API Server", + Version: "unversioned", + }, + }, + } +} + +// setDefaults fills in any fields not set that are required to have valid data. +func (c *Config) setDefaults() { + if c.ServiceClusterIPRange == nil { + defaultNet := "10.0.0.0/24" + glog.Warningf("Network range for service cluster IPs is unspecified. Defaulting to %v.", defaultNet) + _, serviceClusterIPRange, err := net.ParseCIDR(defaultNet) + if err != nil { + glog.Fatalf("Unable to parse CIDR: %v", err) + } + if size := ipallocator.RangeSize(serviceClusterIPRange); size < 8 { + glog.Fatalf("The service cluster IP range must be at least %d IP addresses", 8) + } + c.ServiceClusterIPRange = serviceClusterIPRange + } + if c.ServiceReadWriteIP == nil { + // Select the first valid IP from ServiceClusterIPRange to use as the GenericAPIServer service IP. + serviceReadWriteIP, err := ipallocator.GetIndexedIP(c.ServiceClusterIPRange, 1) + if err != nil { + glog.Fatalf("Failed to generate service read-write IP for GenericAPIServer service: %v", err) + } + glog.V(4).Infof("Setting GenericAPIServer service IP to %q (read-write).", serviceReadWriteIP) + c.ServiceReadWriteIP = serviceReadWriteIP + } + if c.ServiceReadWritePort == 0 { + c.ServiceReadWritePort = 443 + } + 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) + } + if c.MasterCount == 0 { + // Clearly, there will be at least one GenericAPIServer. + c.MasterCount = 1 + } + if c.ReadWritePort == 0 { + c.ReadWritePort = 6443 + } + if c.CacheTimeout == 0 { + c.CacheTimeout = 5 * time.Second + } + if c.RequestContextMapper == nil { + c.RequestContextMapper = api.NewRequestContextMapper() + } + if len(c.ExternalHost) == 0 && c.PublicAddress != nil { + hostAndPort := c.PublicAddress.String() + if c.ReadWritePort != 0 { + hostAndPort = net.JoinHostPort(hostAndPort, strconv.Itoa(c.ReadWritePort)) + } + c.ExternalHost = hostAndPort + } +} + +// New returns a new instance of GenericAPIServer from the given config. +// Certain config fields will be set to a default value if unset, +// including: +// ServiceClusterIPRange +// ServiceNodePortRange +// MasterCount +// ReadWritePort +// PublicAddress +// Public fields: +// Handler -- The returned GenericAPIServer has a field TopHandler which is an +// http.Handler which handles all the endpoints provided by the GenericAPIServer, +// including the API, the UI, and miscellaneous debugging endpoints. All +// these are subject to authorization and authentication. +// InsecureHandler -- an http.Handler which handles all the same +// endpoints as Handler, but no authorization and authentication is done. +// 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 GenericAPIServer's built-in endpoints. +// If the caller wants to add additional endpoints not using the GenericAPIServer's +// auth, then the caller should create a handler for those endpoints, which delegates the +// any unhandled paths to "Handler". +func (c Config) New() (*GenericAPIServer, error) { + if c.Serializer == nil { + return nil, fmt.Errorf("Genericapiserver.New() called with config.Serializer == nil") + } + + c.setDefaults() + + s := &GenericAPIServer{ + ServiceClusterIPRange: c.ServiceClusterIPRange, + ServiceNodePortRange: c.ServiceNodePortRange, + legacyAPIPrefix: c.APIPrefix, + apiPrefix: c.APIGroupPrefix, + admissionControl: c.AdmissionControl, + requestContextMapper: c.RequestContextMapper, + Serializer: c.Serializer, + + minRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second, + enableSwaggerSupport: c.EnableSwaggerSupport, + + MasterCount: c.MasterCount, + ExternalAddress: c.ExternalHost, + ClusterIP: c.PublicAddress, + PublicReadWritePort: c.ReadWritePort, + ServiceReadWriteIP: c.ServiceReadWriteIP, + ServiceReadWritePort: c.ServiceReadWritePort, + ExtraServicePorts: c.ExtraServicePorts, + ExtraEndpointPorts: c.ExtraEndpointPorts, + + KubernetesServiceNodePort: c.KubernetesServiceNodePort, + apiGroupsForDiscovery: map[string]unversioned.APIGroup{}, + + enableOpenAPISupport: c.EnableOpenAPISupport, + openAPIInfo: c.OpenAPIInfo, + openAPIDefaultResponse: c.OpenAPIDefaultResponse, + } + + if c.EnableWatchCache { + s.storageDecorator = registry.StorageWithCacher + } else { + s.storageDecorator = generic.UndecoratedStorage + } + + if c.RestfulContainer != nil { + s.mux = c.RestfulContainer.ServeMux + s.HandlerContainer = c.RestfulContainer + } else { + mux := http.NewServeMux() + s.mux = mux + s.HandlerContainer = NewHandlerContainer(mux, c.Serializer) + } + // 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}/{*}) + s.HandlerContainer.Router(restful.CurlyRouter{}) + s.MuxHelper = &apiserver.MuxHelper{Mux: s.mux, RegisteredPaths: []string{}} + + if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil { + s.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{ + Dial: c.ProxyDialer, + TLSClientConfig: c.ProxyTLSClientConfig, + }) + } + + // Register root handler. + // We do not register this using restful Webservice since we do not want to surface this in api docs. + // Allow GenericAPIServer to be embedded in contexts which already have something registered at the root + if c.EnableIndex { + s.mux.HandleFunc("/", apiserver.IndexHandler(s.HandlerContainer, s.MuxHelper)) + } + + if c.EnableLogsSupport { + apiserver.InstallLogsSupport(s.MuxHelper, s.HandlerContainer) + } + if c.EnableUISupport { + ui.InstallSupport(s.MuxHelper, c.EnableSwaggerSupport && c.EnableSwaggerUI) + } + + if c.EnableProfiling { + s.mux.HandleFunc("/debug/pprof/", pprof.Index) + s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + } + + apiserver.InstallVersionHandler(s.MuxHelper, s.HandlerContainer) + + handler := http.Handler(s.mux.(*http.ServeMux)) + + // 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 + + 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") + } + + s.InsecureHandler = handler + + attributeGetter := apiserver.NewRequestAttributeGetter(c.RequestContextMapper, s.NewRequestInfoResolver()) + handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, c.Authorizer) + if len(c.AuditLogPath) != 0 { + // audit handler must comes before the impersonationFilter to read the original user + writer := &lumberjack.Logger{ + Filename: c.AuditLogPath, + MaxAge: c.AuditLogMaxAge, + MaxBackups: c.AuditLogMaxBackups, + MaxSize: c.AuditLogMaxSize, + } + handler = audit.WithAudit(handler, c.RequestContextMapper, writer) + defer writer.Close() + } + handler = apiserver.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer) + + // Install Authenticator + if c.Authenticator != nil { + authenticatedHandler, err := handlers.NewRequestAuthenticator(c.RequestContextMapper, c.Authenticator, handlers.Unauthorized(c.SupportsBasicAuth), handler) + if err != nil { + glog.Fatalf("Could not initialize authenticator: %v", err) + } + handler = authenticatedHandler + } + + // TODO: Make this optional? Consumers of GenericAPIServer depend on this currently. + s.Handler = handler + + // After all wrapping is done, put a context filter around both handlers + var err error + handler, err = api.NewRequestContextFilter(c.RequestContextMapper, s.Handler) + if err != nil { + glog.Fatalf("Could not initialize request context filter for s.Handler: %v", err) + } + s.Handler = handler + + handler, err = api.NewRequestContextFilter(c.RequestContextMapper, s.InsecureHandler) + if err != nil { + glog.Fatalf("Could not initialize request context filter for s.InsecureHandler: %v", err) + } + s.InsecureHandler = handler + + s.installGroupsDiscoveryHandler() + + return s, nil +} + +func DefaultAndValidateRunOptions(options *options.ServerRunOptions) { + genericvalidation.ValidateRunOptions(options) + + // If advertise-address is not specified, use bind-address. If bind-address + // is not usable (unset, 0.0.0.0, or loopback), we will use the host's default + // interface as valid public addr for master (see: util/net#ValidPublicAddrForMaster) + if options.AdvertiseAddress == nil || options.AdvertiseAddress.IsUnspecified() { + hostIP, err := utilnet.ChooseBindAddress(options.BindAddress) + if err != nil { + glog.Fatalf("Unable to find suitable network address.error='%v' . "+ + "Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this.", err) + } + options.AdvertiseAddress = hostIP + } + glog.Infof("Will report %v as public IP address.", options.AdvertiseAddress) + + // Set default value for ExternalHost if not specified. + if len(options.ExternalHost) == 0 { + // TODO: extend for other providers + if options.CloudProvider == "gce" { + cloud, err := cloudprovider.InitCloudProvider(options.CloudProvider, options.CloudConfigFile) + if err != nil { + glog.Fatalf("Cloud provider could not be initialized: %v", err) + } + instances, supported := cloud.Instances() + if !supported { + glog.Fatalf("GCE cloud provider has no instances. this shouldn't happen. exiting.") + } + name, err := os.Hostname() + if err != nil { + glog.Fatalf("Failed to get hostname: %v", err) + } + addrs, err := instances.NodeAddresses(name) + if err != nil { + glog.Warningf("Unable to obtain external host address from cloud provider: %v", err) + } else { + for _, addr := range addrs { + if addr.Type == api.NodeExternalIP { + options.ExternalHost = addr.Address + } + } + } + } + } +} diff --git a/pkg/genericapiserver/genericapiserver.go b/pkg/genericapiserver/genericapiserver.go index 8f8db710247..43ddb090b27 100644 --- a/pkg/genericapiserver/genericapiserver.go +++ b/pkg/genericapiserver/genericapiserver.go @@ -21,8 +21,6 @@ import ( "fmt" "net" "net/http" - "net/http/pprof" - "os" "path" "regexp" "sort" @@ -34,7 +32,6 @@ import ( "github.com/emicklei/go-restful" "github.com/emicklei/go-restful/swagger" "github.com/golang/glog" - "gopkg.in/natefinch/lumberjack.v2" "github.com/go-openapi/spec" "k8s.io/kubernetes/pkg/admission" @@ -44,20 +41,10 @@ import ( "k8s.io/kubernetes/pkg/apimachinery" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apiserver" - "k8s.io/kubernetes/pkg/apiserver/audit" - "k8s.io/kubernetes/pkg/auth/authenticator" - "k8s.io/kubernetes/pkg/auth/authorizer" - "k8s.io/kubernetes/pkg/auth/handlers" - "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/genericapiserver/openapi" "k8s.io/kubernetes/pkg/genericapiserver/options" - genericvalidation "k8s.io/kubernetes/pkg/genericapiserver/validation" "k8s.io/kubernetes/pkg/registry/generic" - "k8s.io/kubernetes/pkg/registry/generic/registry" - ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/ui" - "k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util/crypto" utilnet "k8s.io/kubernetes/pkg/util/net" utilruntime "k8s.io/kubernetes/pkg/util/runtime" @@ -96,114 +83,6 @@ type APIGroupInfo struct { SubresourceGroupVersionKind map[string]unversioned.GroupVersionKind } -// Config is a structure used to configure a GenericAPIServer. -type Config struct { - // The storage factory for other objects - StorageFactory StorageFactory - AuditLogPath string - AuditLogMaxAge int - AuditLogMaxBackups int - AuditLogMaxSize int - // allow downstream consumers to disable the core controller loops - EnableLogsSupport bool - EnableUISupport bool - // Allow downstream consumers to disable swagger. - // This includes returning the generated swagger spec at /swaggerapi and swagger ui at /swagger-ui. - EnableSwaggerSupport bool - // Allow downstream consumers to disable swagger ui. - // Note that this is ignored if either EnableSwaggerSupport or EnableUISupport is false. - EnableSwaggerUI bool - // Allows api group versions or specific resources to be conditionally enabled/disabled. - APIResourceConfigSource APIResourceConfigSource - // allow downstream consumers to disable the index route - EnableIndex bool - EnableProfiling bool - EnableWatchCache bool - APIPrefix string - APIGroupPrefix string - CorsAllowedOriginList []string - Authenticator authenticator.Request - // TODO(roberthbailey): Remove once the server no longer supports http basic auth. - SupportsBasicAuth bool - Authorizer authorizer.Authorizer - AdmissionControl admission.Interface - MasterServiceNamespace string - // TODO(ericchiang): Determine if policy escalation checks should be an admission controller. - AuthorizerRBACSuperUser string - - // Map requests to contexts. Exported so downstream consumers can provider their own mappers - RequestContextMapper api.RequestContextMapper - - // Required, the interface for serializing and converting objects to and from the wire - Serializer runtime.NegotiatedSerializer - - // If specified, all web services will be registered into this container - RestfulContainer *restful.Container - - // If specified, requests will be allocated a random timeout between this value, and twice this value. - // Note that it is up to the request handlers to ignore or honor this timeout. In seconds. - MinRequestTimeout int - - // Number of masters running; all masters must be started with the - // same value for this field. (Numbers > 1 currently untested.) - MasterCount int - - // The port on PublicAddress where a read-write server will be installed. - // Defaults to 6443 if not set. - ReadWritePort int - - // ExternalHost is the host name to use for external (public internet) facing URLs (e.g. Swagger) - ExternalHost string - - // PublicAddress is the IP address where members of the cluster (kubelet, - // kube-proxy, services, etc.) can reach the GenericAPIServer. - // If nil or 0.0.0.0, the host's default interface will be used. - PublicAddress net.IP - - // Control the interval that pod, node IP, and node heath status caches - // expire. - CacheTimeout time.Duration - - // The range of IPs to be assigned to services with type=ClusterIP or greater - ServiceClusterIPRange *net.IPNet - - // The IP address for the GenericAPIServer service (must be inside ServiceClusterIPRange) - ServiceReadWriteIP net.IP - - // Port for the apiserver service. - ServiceReadWritePort int - - // The range of ports to be assigned to services with type=NodePort or greater - ServiceNodePortRange utilnet.PortRange - - // Used to customize default proxy dial/tls options - ProxyDialer apiserver.ProxyDialerFunc - ProxyTLSClientConfig *tls.Config - - // 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 - - KubernetesServiceNodePort int - - // EnableOpenAPISupport enables OpenAPI support. Allow downstream customers to disable OpenAPI spec. - EnableOpenAPISupport bool - - // OpenAPIInfo will be directly available as Info section of Open API spec. - OpenAPIInfo spec.Info - - // OpenAPIDefaultResponse will be used if an web service operation does not have any responses listed. - OpenAPIDefaultResponse spec.Response -} - // GenericAPIServer contains state for a Kubernetes cluster api server. type GenericAPIServer struct { // ServiceClusterIPRange is used to build cluster IPs for discovery. It is exposed so that `master.go` can @@ -303,142 +182,6 @@ func (s *GenericAPIServer) MinRequestTimeout() time.Duration { return s.minRequestTimeout } -// setDefaults fills in any fields not set that are required to have valid data. -func setDefaults(c *Config) { - if c.ServiceClusterIPRange == nil { - defaultNet := "10.0.0.0/24" - glog.Warningf("Network range for service cluster IPs is unspecified. Defaulting to %v.", defaultNet) - _, serviceClusterIPRange, err := net.ParseCIDR(defaultNet) - if err != nil { - glog.Fatalf("Unable to parse CIDR: %v", err) - } - if size := ipallocator.RangeSize(serviceClusterIPRange); size < 8 { - glog.Fatalf("The service cluster IP range must be at least %d IP addresses", 8) - } - c.ServiceClusterIPRange = serviceClusterIPRange - } - if c.ServiceReadWriteIP == nil { - // Select the first valid IP from ServiceClusterIPRange to use as the GenericAPIServer service IP. - serviceReadWriteIP, err := ipallocator.GetIndexedIP(c.ServiceClusterIPRange, 1) - if err != nil { - glog.Fatalf("Failed to generate service read-write IP for GenericAPIServer service: %v", err) - } - glog.V(4).Infof("Setting GenericAPIServer service IP to %q (read-write).", serviceReadWriteIP) - c.ServiceReadWriteIP = serviceReadWriteIP - } - if c.ServiceReadWritePort == 0 { - c.ServiceReadWritePort = 443 - } - 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) - } - if c.MasterCount == 0 { - // Clearly, there will be at least one GenericAPIServer. - c.MasterCount = 1 - } - if c.ReadWritePort == 0 { - c.ReadWritePort = 6443 - } - if c.CacheTimeout == 0 { - c.CacheTimeout = 5 * time.Second - } - if c.RequestContextMapper == nil { - c.RequestContextMapper = api.NewRequestContextMapper() - } - if len(c.ExternalHost) == 0 && c.PublicAddress != nil { - hostAndPort := c.PublicAddress.String() - if c.ReadWritePort != 0 { - hostAndPort = net.JoinHostPort(hostAndPort, strconv.Itoa(c.ReadWritePort)) - } - c.ExternalHost = hostAndPort - } -} - -// New returns a new instance of GenericAPIServer from the given config. -// Certain config fields will be set to a default value if unset, -// including: -// ServiceClusterIPRange -// ServiceNodePortRange -// MasterCount -// ReadWritePort -// PublicAddress -// Public fields: -// Handler -- The returned GenericAPIServer has a field TopHandler which is an -// http.Handler which handles all the endpoints provided by the GenericAPIServer, -// including the API, the UI, and miscellaneous debugging endpoints. All -// these are subject to authorization and authentication. -// InsecureHandler -- an http.Handler which handles all the same -// endpoints as Handler, but no authorization and authentication is done. -// 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 GenericAPIServer's built-in endpoints. -// If the caller wants to add additional endpoints not using the GenericAPIServer's -// auth, then the caller should create a handler for those endpoints, which delegates the -// any unhandled paths to "Handler". -func New(c *Config) (*GenericAPIServer, error) { - if c.Serializer == nil { - return nil, fmt.Errorf("Genericapiserver.New() called with config.Serializer == nil") - } - setDefaults(c) - - s := &GenericAPIServer{ - ServiceClusterIPRange: c.ServiceClusterIPRange, - ServiceNodePortRange: c.ServiceNodePortRange, - legacyAPIPrefix: c.APIPrefix, - apiPrefix: c.APIGroupPrefix, - admissionControl: c.AdmissionControl, - requestContextMapper: c.RequestContextMapper, - Serializer: c.Serializer, - - minRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second, - enableSwaggerSupport: c.EnableSwaggerSupport, - - MasterCount: c.MasterCount, - ExternalAddress: c.ExternalHost, - ClusterIP: c.PublicAddress, - PublicReadWritePort: c.ReadWritePort, - ServiceReadWriteIP: c.ServiceReadWriteIP, - ServiceReadWritePort: c.ServiceReadWritePort, - ExtraServicePorts: c.ExtraServicePorts, - ExtraEndpointPorts: c.ExtraEndpointPorts, - - KubernetesServiceNodePort: c.KubernetesServiceNodePort, - apiGroupsForDiscovery: map[string]unversioned.APIGroup{}, - - enableOpenAPISupport: c.EnableOpenAPISupport, - openAPIInfo: c.OpenAPIInfo, - openAPIDefaultResponse: c.OpenAPIDefaultResponse, - } - - if c.EnableWatchCache { - s.storageDecorator = registry.StorageWithCacher - } else { - s.storageDecorator = generic.UndecoratedStorage - } - - if c.RestfulContainer != nil { - s.mux = c.RestfulContainer.ServeMux - s.HandlerContainer = c.RestfulContainer - } else { - mux := http.NewServeMux() - s.mux = mux - s.HandlerContainer = NewHandlerContainer(mux, c.Serializer) - } - // 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}/{*}) - s.HandlerContainer.Router(restful.CurlyRouter{}) - s.MuxHelper = &apiserver.MuxHelper{Mux: s.mux, RegisteredPaths: []string{}} - - s.init(c) - - return s, nil -} - func (s *GenericAPIServer) NewRequestInfoResolver() *apiserver.RequestInfoResolver { return &apiserver.RequestInfoResolver{ APIPrefixes: sets.NewString(strings.Trim(s.legacyAPIPrefix, "/"), strings.Trim(s.apiPrefix, "/")), // all possible API prefixes @@ -473,98 +216,6 @@ func NewHandlerContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *re return container } -// init initializes GenericAPIServer. -func (s *GenericAPIServer) init(c *Config) { - - if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil { - s.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{ - Dial: c.ProxyDialer, - TLSClientConfig: c.ProxyTLSClientConfig, - }) - } - - // Register root handler. - // We do not register this using restful Webservice since we do not want to surface this in api docs. - // Allow GenericAPIServer to be embedded in contexts which already have something registered at the root - if c.EnableIndex { - s.mux.HandleFunc("/", apiserver.IndexHandler(s.HandlerContainer, s.MuxHelper)) - } - - if c.EnableLogsSupport { - apiserver.InstallLogsSupport(s.MuxHelper, s.HandlerContainer) - } - if c.EnableUISupport { - ui.InstallSupport(s.MuxHelper, c.EnableSwaggerSupport && c.EnableSwaggerUI) - } - - if c.EnableProfiling { - s.mux.HandleFunc("/debug/pprof/", pprof.Index) - s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - } - - apiserver.InstallVersionHandler(s.MuxHelper, s.HandlerContainer) - - handler := http.Handler(s.mux.(*http.ServeMux)) - - // 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 - - 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") - } - - s.InsecureHandler = handler - - attributeGetter := apiserver.NewRequestAttributeGetter(c.RequestContextMapper, s.NewRequestInfoResolver()) - handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, c.Authorizer) - if len(c.AuditLogPath) != 0 { - // audit handler must comes before the impersonationFilter to read the original user - writer := &lumberjack.Logger{ - Filename: c.AuditLogPath, - MaxAge: c.AuditLogMaxAge, - MaxBackups: c.AuditLogMaxBackups, - MaxSize: c.AuditLogMaxSize, - } - handler = audit.WithAudit(handler, c.RequestContextMapper, writer) - defer writer.Close() - } - handler = apiserver.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer) - - // Install Authenticator - if c.Authenticator != nil { - authenticatedHandler, err := handlers.NewRequestAuthenticator(c.RequestContextMapper, c.Authenticator, handlers.Unauthorized(c.SupportsBasicAuth), handler) - if err != nil { - glog.Fatalf("Could not initialize authenticator: %v", err) - } - handler = authenticatedHandler - } - - // TODO: Make this optional? Consumers of GenericAPIServer depend on this currently. - s.Handler = handler - - // After all wrapping is done, put a context filter around both handlers - var err error - handler, err = api.NewRequestContextFilter(c.RequestContextMapper, s.Handler) - if err != nil { - glog.Fatalf("Could not initialize request context filter for s.Handler: %v", err) - } - s.Handler = handler - - handler, err = api.NewRequestContextFilter(c.RequestContextMapper, s.InsecureHandler) - if err != nil { - glog.Fatalf("Could not initialize request context filter for s.InsecureHandler: %v", err) - } - s.InsecureHandler = handler - - s.installGroupsDiscoveryHandler() -} - // Exposes the given group versions in API. Helper method to install multiple group versions at once. func (s *GenericAPIServer) InstallAPIGroups(groupsInfo []APIGroupInfo) error { for _, apiGroupInfo := range groupsInfo { @@ -597,89 +248,6 @@ func (s *GenericAPIServer) installGroupsDiscoveryHandler() { }) } -func NewConfig(options *options.ServerRunOptions) *Config { - return &Config{ - APIGroupPrefix: options.APIGroupPrefix, - APIPrefix: options.APIPrefix, - CorsAllowedOriginList: options.CorsAllowedOriginList, - AuditLogPath: options.AuditLogPath, - AuditLogMaxAge: options.AuditLogMaxAge, - AuditLogMaxBackups: options.AuditLogMaxBackups, - AuditLogMaxSize: options.AuditLogMaxSize, - EnableIndex: true, - EnableLogsSupport: options.EnableLogsSupport, - EnableProfiling: options.EnableProfiling, - EnableSwaggerSupport: true, - EnableSwaggerUI: options.EnableSwaggerUI, - EnableUISupport: true, - EnableWatchCache: options.EnableWatchCache, - ExternalHost: options.ExternalHost, - KubernetesServiceNodePort: options.KubernetesServiceNodePort, - MasterCount: options.MasterCount, - MinRequestTimeout: options.MinRequestTimeout, - PublicAddress: options.AdvertiseAddress, - ReadWritePort: options.SecurePort, - ServiceClusterIPRange: &options.ServiceClusterIPRange, - ServiceNodePortRange: options.ServiceNodePortRange, - EnableOpenAPISupport: true, - OpenAPIDefaultResponse: spec.Response{ - ResponseProps: spec.ResponseProps{ - Description: "Default Response."}}, - OpenAPIInfo: spec.Info{ - InfoProps: spec.InfoProps{ - Title: "Generic API Server", - Version: "unversioned", - }, - }, - } -} - -func DefaultAndValidateRunOptions(options *options.ServerRunOptions) { - genericvalidation.ValidateRunOptions(options) - - // If advertise-address is not specified, use bind-address. If bind-address - // is not usable (unset, 0.0.0.0, or loopback), we will use the host's default - // interface as valid public addr for master (see: util/net#ValidPublicAddrForMaster) - if options.AdvertiseAddress == nil || options.AdvertiseAddress.IsUnspecified() { - hostIP, err := utilnet.ChooseBindAddress(options.BindAddress) - if err != nil { - glog.Fatalf("Unable to find suitable network address.error='%v' . "+ - "Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this.", err) - } - options.AdvertiseAddress = hostIP - } - glog.Infof("Will report %v as public IP address.", options.AdvertiseAddress) - - // Set default value for ExternalHost if not specified. - if len(options.ExternalHost) == 0 { - // TODO: extend for other providers - if options.CloudProvider == "gce" { - cloud, err := cloudprovider.InitCloudProvider(options.CloudProvider, options.CloudConfigFile) - if err != nil { - glog.Fatalf("Cloud provider could not be initialized: %v", err) - } - instances, supported := cloud.Instances() - if !supported { - glog.Fatalf("GCE cloud provider has no instances. this shouldn't happen. exiting.") - } - name, err := os.Hostname() - if err != nil { - glog.Fatalf("Failed to get hostname: %v", err) - } - addrs, err := instances.NodeAddresses(name) - if err != nil { - glog.Warningf("Unable to obtain external host address from cloud provider: %v", err) - } else { - for _, addr := range addrs { - if addr.Type == api.NodeExternalIP { - options.ExternalHost = addr.Address - } - } - } - } - } -} - func (s *GenericAPIServer) Run(options *options.ServerRunOptions) { if s.enableSwaggerSupport { s.InstallSwaggerAPI() diff --git a/pkg/genericapiserver/genericapiserver_test.go b/pkg/genericapiserver/genericapiserver_test.go index a7f903e242d..44e32632fce 100644 --- a/pkg/genericapiserver/genericapiserver_test.go +++ b/pkg/genericapiserver/genericapiserver_test.go @@ -36,6 +36,7 @@ import ( "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apiserver" + ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator" etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" utilnet "k8s.io/kubernetes/pkg/util/net" @@ -49,7 +50,7 @@ func setUp(t *testing.T) (GenericAPIServer, *etcdtesting.EtcdTestServer, Config, genericapiserver := GenericAPIServer{} config := Config{} config.PublicAddress = net.ParseIP("192.168.10.4") - + config.RequestContextMapper = api.NewRequestContextMapper() return genericapiserver, etcdServer, config, assert.New(t) } @@ -62,7 +63,7 @@ func newMaster(t *testing.T) (*GenericAPIServer, *etcdtesting.EtcdTestServer, Co config.APIPrefix = "/api" config.APIGroupPrefix = "/apis" - s, err := New(&config) + s, err := config.New() if err != nil { t.Fatalf("Error in bringing up the server: %v", err) } @@ -81,10 +82,14 @@ func TestNew(t *testing.T) { assert.Equal(s.apiPrefix, config.APIGroupPrefix) assert.Equal(s.admissionControl, config.AdmissionControl) assert.Equal(s.RequestContextMapper(), config.RequestContextMapper) - assert.Equal(s.ExternalAddress, config.ExternalHost) assert.Equal(s.ClusterIP, config.PublicAddress) - assert.Equal(s.PublicReadWritePort, config.ReadWritePort) - assert.Equal(s.ServiceReadWriteIP, config.ServiceReadWriteIP) + + // these values get defaulted + _, serviceClusterIPRange, _ := net.ParseCIDR("10.0.0.0/24") + serviceReadWriteIP, _ := ipallocator.GetIndexedIP(serviceClusterIPRange, 1) + assert.Equal(s.ServiceReadWriteIP, serviceReadWriteIP) + assert.Equal(s.ExternalAddress, net.JoinHostPort(config.PublicAddress.String(), "6443")) + assert.Equal(s.PublicReadWritePort, 6443) // These functions should point to the same memory location serverDialer, _ := utilnet.Dialer(s.ProxyTransport) @@ -106,7 +111,7 @@ func TestInstallAPIGroups(t *testing.T) { config.APIGroupPrefix = "/apiGroupPrefix" config.Serializer = api.Codecs - s, err := New(&config) + s, err := config.New() if err != nil { t.Fatalf("Error in bringing up the server: %v", err) } diff --git a/pkg/master/master.go b/pkg/master/master.go index 221680b8d8b..385970fdb72 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -179,7 +179,7 @@ func New(c *Config) (*Master, error) { return nil, fmt.Errorf("Master.New() called with config.KubeletClient == nil") } - s, err := genericapiserver.New(c.Config) + s, err := c.Config.New() if err != nil { return nil, err } diff --git a/pkg/master/master_test.go b/pkg/master/master_test.go index 09a8db30232..bccb987a50c 100644 --- a/pkg/master/master_test.go +++ b/pkg/master/master_test.go @@ -54,6 +54,7 @@ import ( "k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/registry/namespace" "k8s.io/kubernetes/pkg/registry/registrytest" + ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator" "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/storage" @@ -113,6 +114,7 @@ func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert. config.APIResourceConfigSource = DefaultAPIResourceConfigSource() config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil } config.ProxyTLSClientConfig = &tls.Config{} + config.RequestContextMapper = api.NewRequestContextMapper() // TODO: this is kind of hacky. The trouble is that the sync loop // runs in a go-routine and there is no way to validate in the test @@ -174,10 +176,14 @@ func TestNew(t *testing.T) { assert.Equal(master.enableCoreControllers, config.EnableCoreControllers) assert.Equal(master.tunneler, config.Tunneler) assert.Equal(master.RequestContextMapper(), config.RequestContextMapper) - assert.Equal(master.MasterCount, config.MasterCount) assert.Equal(master.ClusterIP, config.PublicAddress) - assert.Equal(master.PublicReadWritePort, config.ReadWritePort) - assert.Equal(master.ServiceReadWriteIP, config.ServiceReadWriteIP) + + // these values get defaulted + _, serviceClusterIPRange, _ := net.ParseCIDR("10.0.0.0/24") + serviceReadWriteIP, _ := ipallocator.GetIndexedIP(serviceClusterIPRange, 1) + assert.Equal(master.MasterCount, 1) + assert.Equal(master.PublicReadWritePort, 6443) + assert.Equal(master.ServiceReadWriteIP, serviceReadWriteIP) // These functions should point to the same memory location masterDialer, _ := utilnet.Dialer(master.ProxyTransport)