func init() { groupMeta, err := latest.RegisterGroup("experimental") if err != nil { glog.V(4).Infof("%v", err) return } registeredGroupVersions := registered.GroupVersionsForGroup("experimental") groupVersion := registeredGroupVersions[0] *groupMeta = latest.GroupMeta{ GroupVersion: groupVersion, Group: apiutil.GetGroup(groupVersion), Version: apiutil.GetVersion(groupVersion), Codec: runtime.CodecFor(api.Scheme, groupVersion), } var versions []string var groupVersions []string for i := len(registeredGroupVersions) - 1; i >= 0; i-- { versions = append(versions, apiutil.GetVersion(registeredGroupVersions[i])) groupVersions = append(groupVersions, registeredGroupVersions[i]) } groupMeta.Versions = versions groupMeta.GroupVersions = groupVersions groupMeta.SelfLinker = runtime.SelfLinker(accessor) // the list of kinds that are scoped at the root of the api hierarchy // if a kind is not enumerated here, it is assumed to have a namespace scope rootScoped := sets.NewString() ignoredKinds := sets.NewString() groupMeta.RESTMapper = api.NewDefaultRESTMapper("experimental", groupVersions, interfacesFor, importPrefix, ignoredKinds, rootScoped) api.RegisterRESTMapper(groupMeta.RESTMapper) groupMeta.InterfacesFor = interfacesFor }
func init() { groupMeta, err := latest.RegisterGroup("") if err != nil { glog.V(4).Infof("%v", err) return } // Use the first API version in the list of registered versions as the latest. registeredGroupVersions := registered.GroupVersionsForGroup("") groupVersion := registeredGroupVersions[0] *groupMeta = latest.GroupMeta{ GroupVersion: groupVersion, Group: apiutil.GetGroup(groupVersion), Version: apiutil.GetVersion(groupVersion), Codec: runtime.CodecFor(api.Scheme, groupVersion), } var versions []string var groupVersions []string for i := len(registeredGroupVersions) - 1; i >= 0; i-- { versions = append(versions, apiutil.GetVersion(registeredGroupVersions[i])) groupVersions = append(groupVersions, registeredGroupVersions[i]) } groupMeta.Versions = versions groupMeta.GroupVersions = groupVersions groupMeta.SelfLinker = runtime.SelfLinker(accessor) // the list of kinds that are scoped at the root of the api hierarchy // if a kind is not enumerated here, it is assumed to have a namespace scope // the list of kinds that are scoped at the root of the api hierarchy // if a kind is not enumerated here, it is assumed to have a namespace scope rootScoped := sets.NewString( "Node", "Minion", "Namespace", "PersistentVolume", "ComponentStatus", ) // these kinds should be excluded from the list of resources ignoredKinds := sets.NewString( "ListOptions", "DeleteOptions", "Status", "PodLogOptions", "PodExecOptions", "PodAttachOptions", "PodProxyOptions", "ThirdPartyResource", "ThirdPartyResourceData", "ThirdPartyResourceList") mapper := api.NewDefaultRESTMapper("", versions, interfacesFor, importPrefix, ignoredKinds, rootScoped) // setup aliases for groups of resources mapper.AddResourceAlias("all", userResources...) groupMeta.RESTMapper = mapper api.RegisterRESTMapper(groupMeta.RESTMapper) groupMeta.InterfacesFor = interfacesFor }
func init() { kubeTestAPI := os.Getenv("KUBE_TEST_API") if kubeTestAPI != "" { testGroupVersions := strings.Split(kubeTestAPI, ",") for _, groupVersion := range testGroupVersions { // TODO: caesarxuchao: the apiutil package is hacky, it will be replaced // by a following PR. Groups[apiutil.GetGroup(groupVersion)] = TestGroup{apiutil.GetGroup(groupVersion), apiutil.GetVersion(groupVersion), groupVersion} } } // TODO: caesarxuchao: we need a central place to store all available API // groups and their metadata. if _, ok := Groups[""]; !ok { // TODO: The second latest.GroupOrDie("").Version will be latest.GroupVersion after we // have multiple group support Groups[""] = TestGroup{"", latest.GroupOrDie("").Version, latest.GroupOrDie("").GroupVersion} } if _, ok := Groups["extensions"]; !ok { Groups["extensions"] = TestGroup{"extensions", latest.GroupOrDie("extensions").Version, latest.GroupOrDie("extensions").GroupVersion} } Default = Groups[""] Extensions = Groups["extensions"] }
func (s *SwaggerSchema) ValidateBytes(data []byte) error { var obj interface{} out, err := yaml.ToJSON(data) if err != nil { return err } data = out if err := json.Unmarshal(data, &obj); err != nil { return err } fields, ok := obj.(map[string]interface{}) if !ok { return fmt.Errorf("error in unmarshaling data %s", string(data)) } groupVersion := fields["apiVersion"] if groupVersion == nil { return fmt.Errorf("apiVersion not set") } kind := fields["kind"] if kind == nil { return fmt.Errorf("kind not set") } version := apiutil.GetVersion(groupVersion.(string)) allErrs := s.ValidateObject(obj, "", version+"."+kind.(string)) if len(allErrs) == 1 { return allErrs[0] } return errors.NewAggregate(allErrs) }
func TestDiscoveryAtAPIS(t *testing.T) { master, etcdserver, config, assert := setUp(t) defer etcdserver.Terminate(t) // ================= preparation for master.init() ====================== portRange := util.PortRange{Base: 10, Size: 10} master.serviceNodePortRange = portRange _, ipnet, err := net.ParseCIDR("192.168.1.1/24") if !assert.NoError(err) { t.Errorf("unexpected error: %v", err) } master.serviceClusterIPRange = ipnet mh := apiserver.MuxHelper{Mux: http.NewServeMux()} master.muxHelper = &mh master.rootWebService = new(restful.WebService) master.handlerContainer = restful.NewContainer() master.mux = http.NewServeMux() master.requestContextMapper = api.NewRequestContextMapper() // ======================= end of preparation =========================== master.init(&config) server := httptest.NewServer(master.handlerContainer.ServeMux) resp, err := http.Get(server.URL + "/apis") if !assert.NoError(err) { t.Errorf("unexpected error: %v", err) } assert.Equal(http.StatusOK, resp.StatusCode) groupList := unversioned.APIGroupList{} assert.NoError(decodeResponse(resp, &groupList)) if err != nil { t.Fatalf("unexpected error: %v", err) } expectGroupName := "extensions" expectVersions := []unversioned.GroupVersionForDiscovery{ { GroupVersion: testapi.Extensions.GroupAndVersion(), Version: testapi.Extensions.Version(), }, } expectPreferredVersion := unversioned.GroupVersionForDiscovery{ GroupVersion: config.StorageVersions["extensions"], Version: apiutil.GetVersion(config.StorageVersions["extensions"]), } assert.Equal(expectGroupName, groupList.Groups[0].Name) assert.Equal(expectVersions, groupList.Groups[0].Versions) assert.Equal(expectPreferredVersion, groupList.Groups[0].PreferredVersion) }
// validateList unpack a list and validate every item in the list. // It return nil if every item is ok. // Otherwise it return an error list contain errors of every item. func (s *SwaggerSchema) validateList(obj map[string]interface{}) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} items, exists := obj["items"] if !exists { return append(allErrs, fmt.Errorf("no items field in %#v", obj)) } itemList, ok := items.([]interface{}) if !ok { return append(allErrs, fmt.Errorf("items isn't a slice")) } for i, item := range itemList { fields, ok := item.(map[string]interface{}) if !ok { allErrs = append(allErrs, fmt.Errorf("items[%d] isn't a map[string]interface{}", i)) continue } groupVersion := fields["apiVersion"] if groupVersion == nil { allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion not set", i)) continue } itemVersion, ok := groupVersion.(string) if !ok { allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion isn't string type", i)) continue } if len(itemVersion) == 0 { allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion is empty", i)) } kind := fields["kind"] if kind == nil { allErrs = append(allErrs, fmt.Errorf("items[%d].kind not set", i)) continue } itemKind, ok := kind.(string) if !ok { allErrs = append(allErrs, fmt.Errorf("items[%d].kind isn't string type", i)) continue } if len(itemKind) == 0 { allErrs = append(allErrs, fmt.Errorf("items[%d].kind is empty", i)) } version := apiutil.GetVersion(itemVersion) errs := s.ValidateObject(item, "", version+"."+itemKind) if len(errs) >= 1 { allErrs = append(allErrs, errs...) } } return allErrs }
func TestDiscoveryAtAPIS(t *testing.T) { master, etcdserver, config, assert := newMaster(t) defer etcdserver.Terminate(t) server := httptest.NewServer(master.HandlerContainer.ServeMux) groupList, err := getGroupList(server) if err != nil { t.Fatalf("unexpected error: %v", err) } assert.Equal(0, len(groupList.Groups)) // Add a Group. extensionsGroupName := extensions.GroupName extensionsVersions := []unversioned.GroupVersionForDiscovery{ { GroupVersion: testapi.Extensions.GroupVersion().String(), Version: testapi.Extensions.GroupVersion().Version, }, } extensionsPreferredVersion := unversioned.GroupVersionForDiscovery{ GroupVersion: config.StorageVersions[extensions.GroupName], Version: apiutil.GetVersion(config.StorageVersions[extensions.GroupName]), } master.AddAPIGroupForDiscovery(unversioned.APIGroup{ Name: extensionsGroupName, Versions: extensionsVersions, PreferredVersion: extensionsPreferredVersion, }) groupList, err = getGroupList(server) if err != nil { t.Fatalf("unexpected error: %v", err) } assert.Equal(1, len(groupList.Groups)) groupListGroup := groupList.Groups[0] assert.Equal(extensionsGroupName, groupListGroup.Name) assert.Equal(extensionsVersions, groupListGroup.Versions) assert.Equal(extensionsPreferredVersion, groupListGroup.PreferredVersion) assert.Equal(master.getServerAddressByClientCIDRs(&http.Request{}), groupListGroup.ServerAddressByClientCIDRs) // Remove the group. master.RemoveAPIGroupForDiscovery(extensionsGroupName) groupList, err = getGroupList(server) if err != nil { t.Fatalf("unexpected error: %v", err) } assert.Equal(0, len(groupList.Groups)) }
// PrintModelDescription prints the description of a specific model or dot path func PrintModelDescription(inModel string, fieldsPath []string, w io.Writer, swaggerSchema *swagger.ApiDeclaration, r bool) error { recursive = r // this is global for convenience apiVer := apiutil.GetVersion(swaggerSchema.ApiVersion) + "." var pointedModel *swagger.NamedModel for i := range swaggerSchema.Models.List { name := swaggerSchema.Models.List[i].Name allModels[name] = &swaggerSchema.Models.List[i] if strings.ToLower(name) == strings.ToLower(apiVer+inModel) { pointedModel = &swaggerSchema.Models.List[i] } } if pointedModel == nil { return fmt.Errorf("requested resource %q is not defined", inModel) } if len(fieldsPath) == 0 { return printTopLevelResourceInfo(w, pointedModel) } var pointedModelAsProp *swagger.NamedModelProperty for _, field := range fieldsPath { if prop, nextModel, isModel := getField(pointedModel, field); prop != nil { if isModel { pointedModelAsProp = prop pointedModel = allModels[nextModel] } else { return printPrimitive(w, prop) } } else { return fmt.Errorf("field %q does not exist", field) } } return printModelInfo(w, pointedModel, pointedModelAsProp) }
// ValidateEvent makes sure that the event makes sense. func ValidateEvent(event *api.Event) field.ErrorList { allErrs := field.ErrorList{} // There is no namespace required for root-scoped kind, for example, node. // However, older client code accidentally sets event.Namespace // to api.NamespaceDefault, so we accept that too, but "" is preferred. // Todo: Events may reference 3rd party object, and we can't check whether the object is namespaced. // Suppose them are namespaced. Do check if we can get the piece of information. // This should apply to all groups served by this apiserver. group := apiutil.GetGroup(event.InvolvedObject.APIVersion) version := apiutil.GetVersion(event.InvolvedObject.APIVersion) if (group == "" || group == "extensions") && (version != "extensions" && version != "") { namespacedKindFlag, err := isNamespacedKind(event.InvolvedObject.Kind, event.InvolvedObject.APIVersion) if err != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "kind"), event.InvolvedObject.Kind, fmt.Sprintf("couldn't check whether namespace is allowed: %s", err))) } else { if !namespacedKindFlag && event.Namespace != api.NamespaceDefault && event.Namespace != "" { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, fmt.Sprintf("not allowed for %s", event.InvolvedObject.Kind))) } if namespacedKindFlag && event.Namespace != event.InvolvedObject.Namespace { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match involvedObject")) } } } else { if event.Namespace != event.InvolvedObject.Namespace { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match involvedObject")) } } if !validation.IsDNS1123Subdomain(event.Namespace) { allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, "")) } return allErrs }
// StorageGroupsToEncodingVersion returns a map from group name to group version, // computed from the s.DeprecatedStorageVersion and s.StorageVersions flags. // TODO: can we move the whole storage version concept to the generic apiserver? func (s *APIServer) StorageGroupsToEncodingVersion() (map[string]unversioned.GroupVersion, error) { storageVersionMap := map[string]unversioned.GroupVersion{} if s.DeprecatedStorageVersion != "" { storageVersionMap[""] = unversioned.GroupVersion{Group: apiutil.GetGroup(s.DeprecatedStorageVersion), Version: apiutil.GetVersion(s.DeprecatedStorageVersion)} } // First, get the defaults. if err := mergeGroupVersionIntoMap(s.DefaultStorageVersions, storageVersionMap); err != nil { return nil, err } // Override any defaults with the user settings. if err := mergeGroupVersionIntoMap(s.StorageVersions, storageVersionMap); err != nil { return nil, err } return storageVersionMap, nil }
// init initializes master. func (m *Master) init(c *Config) { healthzChecks := []healthz.HealthzChecker{} m.clock = util.RealClock{} dbClient := func(resource string) storage.Interface { return c.StorageDestinations.get("", resource) } podStorage := podetcd.NewStorage(dbClient("pods"), c.EnableWatchCache, c.KubeletClient) podTemplateStorage := podtemplateetcd.NewREST(dbClient("podTemplates")) eventStorage := eventetcd.NewREST(dbClient("events"), uint64(c.EventTTL.Seconds())) limitRangeStorage := limitrangeetcd.NewREST(dbClient("limitRanges")) resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(dbClient("resourceQuotas")) secretStorage := secretetcd.NewREST(dbClient("secrets")) serviceAccountStorage := serviceaccountetcd.NewREST(dbClient("serviceAccounts")) persistentVolumeStorage, persistentVolumeStatusStorage := pvetcd.NewREST(dbClient("persistentVolumes")) persistentVolumeClaimStorage, persistentVolumeClaimStatusStorage := pvcetcd.NewREST(dbClient("persistentVolumeClaims")) namespaceStorage, namespaceStatusStorage, namespaceFinalizeStorage := namespaceetcd.NewREST(dbClient("namespaces")) m.namespaceRegistry = namespace.NewRegistry(namespaceStorage) endpointsStorage := endpointsetcd.NewREST(dbClient("endpoints"), c.EnableWatchCache) m.endpointRegistry = endpoint.NewRegistry(endpointsStorage) nodeStorage, nodeStatusStorage := nodeetcd.NewREST(dbClient("nodes"), c.EnableWatchCache, c.KubeletClient) m.nodeRegistry = node.NewRegistry(nodeStorage) serviceStorage := serviceetcd.NewREST(dbClient("services")) m.serviceRegistry = service.NewRegistry(serviceStorage) var serviceClusterIPRegistry service.RangeRegistry serviceClusterIPAllocator := ipallocator.NewAllocatorCIDRRange(m.serviceClusterIPRange, func(max int, rangeSpec string) allocator.Interface { mem := allocator.NewAllocationMap(max, rangeSpec) etcd := etcdallocator.NewEtcd(mem, "/ranges/serviceips", "serviceipallocation", dbClient("services")) serviceClusterIPRegistry = etcd return etcd }) m.serviceClusterIPAllocator = serviceClusterIPRegistry var serviceNodePortRegistry service.RangeRegistry serviceNodePortAllocator := portallocator.NewPortAllocatorCustom(m.serviceNodePortRange, func(max int, rangeSpec string) allocator.Interface { mem := allocator.NewAllocationMap(max, rangeSpec) etcd := etcdallocator.NewEtcd(mem, "/ranges/servicenodeports", "servicenodeportallocation", dbClient("services")) serviceNodePortRegistry = etcd return etcd }) m.serviceNodePortAllocator = serviceNodePortRegistry controllerStorage := controlleretcd.NewREST(dbClient("replicationControllers")) // TODO: Factor out the core API registration m.storage = map[string]rest.Storage{ "pods": podStorage.Pod, "pods/attach": podStorage.Attach, "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, "podTemplates": podTemplateStorage, "replicationControllers": controllerStorage, "services": service.NewStorage(m.serviceRegistry, m.endpointRegistry, serviceClusterIPAllocator, serviceNodePortAllocator), "endpoints": endpointsStorage, "nodes": nodeStorage, "nodes/status": nodeStatusStorage, "events": eventStorage, "limitRanges": limitRangeStorage, "resourceQuotas": resourceQuotaStorage, "resourceQuotas/status": resourceQuotaStatusStorage, "namespaces": namespaceStorage, "namespaces/status": namespaceStatusStorage, "namespaces/finalize": namespaceFinalizeStorage, "secrets": secretStorage, "serviceAccounts": serviceAccountStorage, "persistentVolumes": persistentVolumeStorage, "persistentVolumes/status": persistentVolumeStatusStorage, "persistentVolumeClaims": persistentVolumeClaimStorage, "persistentVolumeClaims/status": persistentVolumeClaimStatusStorage, "componentStatuses": componentstatus.NewStorage(func() map[string]apiserver.Server { return m.getServersToValidate(c) }), } // establish the node proxy dialer if len(c.SSHUser) > 0 { // 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") err := m.generateSSHKey(c.SSHUser, c.SSHKeyfile, publicKeyFile) if err != nil { glog.Errorf("Failed to create key pair: %v", err) } } m.tunnels = &util.SSHTunnelList{} m.dialer = m.Dial m.setupSecureProxy(c.SSHUser, c.SSHKeyfile, publicKeyFile) m.lastSync = m.clock.Now().Unix() // 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 { httpKubeletClient.Config.Dial = m.dialer 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.", c.KubeletClient) } 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()) }) } apiVersions := []string{} if m.v1 { if err := m.api_v1().InstallREST(m.handlerContainer); err != nil { glog.Fatalf("Unable to setup API v1: %v", err) } apiVersions = append(apiVersions, "v1") } apiserver.InstallSupport(m.muxHelper, m.rootWebService, c.EnableProfiling, healthzChecks...) apiserver.AddApiWebService(m.handlerContainer, c.APIPrefix, apiVersions) apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newAPIRequestInfoResolver(), apiVersions) // allGroups records all supported groups at /apis allGroups := []api.APIGroup{} if m.exp { m.thirdPartyStorage = c.StorageDestinations.APIGroups["experimental"].Default m.thirdPartyResources = map[string]*thirdpartyresourcedataetcd.REST{} expVersion := m.experimental(c) if err := expVersion.InstallREST(m.handlerContainer); err != nil { glog.Fatalf("Unable to setup experimental api: %v", err) } g, err := latest.Group("experimental") if err != nil { glog.Fatalf("Unable to setup experimental api: %v", err) } expAPIVersions := []api.GroupVersion{ { GroupVersion: expVersion.Version, Version: apiutil.GetVersion(expVersion.Version), }, } storageVersion, found := c.StorageVersions[g.Group] if !found { glog.Fatalf("Couldn't find storage version of group %v", g.Group) } group := api.APIGroup{ Name: g.Group, Versions: expAPIVersions, PreferredVersion: api.GroupVersion{GroupVersion: storageVersion, Version: apiutil.GetVersion(storageVersion)}, } apiserver.AddGroupWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("experimental").Group+"/", group) allGroups = append(allGroups, group) apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newAPIRequestInfoResolver(), []string{expVersion.Version}) } // This should be done after all groups are registered // TODO: replace the hardcoded "apis". apiserver.AddApisWebService(m.handlerContainer, "/apis", allGroups) // Register root handler. // We do not register this using restful Webservice since we do not want to surface this in api docs. // Allow master to be embedded in contexts which already have something registered at the root if c.EnableIndex { m.mux.HandleFunc("/", apiserver.IndexHandler(m.handlerContainer, m.muxHelper)) } if c.EnableLogsSupport { apiserver.InstallLogsSupport(m.muxHelper) } if c.EnableUISupport { ui.InstallSupport(m.muxHelper, m.enableSwaggerSupport) } 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) } handler := http.Handler(m.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") } m.InsecureHandler = handler attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, m.newAPIRequestInfoResolver()) handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer) // Install Authenticator if c.Authenticator != nil { authenticatedHandler, err := handlers.NewRequestAuthenticator(m.requestContextMapper, c.Authenticator, handlers.Unauthorized(c.SupportsBasicAuth), handler) if err != nil { glog.Fatalf("Could not initialize authenticator: %v", err) } handler = authenticatedHandler } // Install root web services m.handlerContainer.Add(m.rootWebService) // TODO: Make this optional? Consumers of master depend on this currently. m.Handler = handler if m.enableSwaggerSupport { m.InstallSwaggerAPI() } // 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 } // TODO: Attempt clean shutdown? if m.enableCoreControllers { m.NewBootstrapController().Start() } }
// init initializes master. func (m *Master) init(c *Config) { if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil { m.proxyTransport = util.SetTransportDefaults(&http.Transport{ Dial: c.ProxyDialer, TLSClientConfig: c.ProxyTLSClientConfig, }) } healthzChecks := []healthz.HealthzChecker{} storageDecorator := c.storageDecorator() dbClient := func(resource string) storage.Interface { return c.StorageDestinations.get("", resource) } podStorage := podetcd.NewStorage(dbClient("pods"), storageDecorator, c.KubeletClient, m.proxyTransport) podTemplateStorage := podtemplateetcd.NewREST(dbClient("podTemplates"), storageDecorator) eventStorage := eventetcd.NewREST(dbClient("events"), storageDecorator, uint64(c.EventTTL.Seconds())) limitRangeStorage := limitrangeetcd.NewREST(dbClient("limitRanges"), storageDecorator) resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(dbClient("resourceQuotas"), storageDecorator) secretStorage := secretetcd.NewREST(dbClient("secrets"), storageDecorator) serviceAccountStorage := serviceaccountetcd.NewREST(dbClient("serviceAccounts"), storageDecorator) persistentVolumeStorage, persistentVolumeStatusStorage := pvetcd.NewREST(dbClient("persistentVolumes"), storageDecorator) persistentVolumeClaimStorage, persistentVolumeClaimStatusStorage := pvcetcd.NewREST(dbClient("persistentVolumeClaims"), storageDecorator) namespaceStorage, namespaceStatusStorage, namespaceFinalizeStorage := namespaceetcd.NewREST(dbClient("namespaces"), storageDecorator) m.namespaceRegistry = namespace.NewRegistry(namespaceStorage) endpointsStorage := endpointsetcd.NewREST(dbClient("endpoints"), storageDecorator) m.endpointRegistry = endpoint.NewRegistry(endpointsStorage) nodeStorage, nodeStatusStorage := nodeetcd.NewREST(dbClient("nodes"), storageDecorator, c.KubeletClient, m.proxyTransport) m.nodeRegistry = node.NewRegistry(nodeStorage) serviceStorage := serviceetcd.NewREST(dbClient("services"), storageDecorator) m.serviceRegistry = service.NewRegistry(serviceStorage) var serviceClusterIPRegistry service.RangeRegistry serviceClusterIPAllocator := ipallocator.NewAllocatorCIDRRange(m.serviceClusterIPRange, func(max int, rangeSpec string) allocator.Interface { mem := allocator.NewAllocationMap(max, rangeSpec) etcd := etcdallocator.NewEtcd(mem, "/ranges/serviceips", "serviceipallocation", dbClient("services")) serviceClusterIPRegistry = etcd return etcd }) m.serviceClusterIPAllocator = serviceClusterIPRegistry var serviceNodePortRegistry service.RangeRegistry serviceNodePortAllocator := portallocator.NewPortAllocatorCustom(m.serviceNodePortRange, func(max int, rangeSpec string) allocator.Interface { mem := allocator.NewAllocationMap(max, rangeSpec) etcd := etcdallocator.NewEtcd(mem, "/ranges/servicenodeports", "servicenodeportallocation", dbClient("services")) serviceNodePortRegistry = etcd return etcd }) m.serviceNodePortAllocator = serviceNodePortRegistry controllerStorage, controllerStatusStorage := controlleretcd.NewREST(dbClient("replicationControllers"), storageDecorator) // TODO: Factor out the core API registration m.storage = map[string]rest.Storage{ "pods": podStorage.Pod, "pods/attach": podStorage.Attach, "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, "podTemplates": podTemplateStorage, "replicationControllers": controllerStorage, "replicationControllers/status": controllerStatusStorage, "services": service.NewStorage(m.serviceRegistry, m.endpointRegistry, serviceClusterIPAllocator, serviceNodePortAllocator, m.proxyTransport), "endpoints": endpointsStorage, "nodes": nodeStorage, "nodes/status": nodeStatusStorage, "events": eventStorage, "limitRanges": limitRangeStorage, "resourceQuotas": resourceQuotaStorage, "resourceQuotas/status": resourceQuotaStatusStorage, "namespaces": namespaceStorage, "namespaces/status": namespaceStatusStorage, "namespaces/finalize": namespaceFinalizeStorage, "secrets": secretStorage, "serviceAccounts": serviceAccountStorage, "persistentVolumes": persistentVolumeStorage, "persistentVolumes/status": persistentVolumeStatusStorage, "persistentVolumeClaims": persistentVolumeClaimStorage, "persistentVolumeClaims/status": persistentVolumeClaimStatusStorage, "componentStatuses": componentstatus.NewStorage(func() map[string]apiserver.Server { return m.getServersToValidate(c) }), } if m.tunneler != nil { m.tunneler.Run(m.getNodeAddresses) healthzChecks = append(healthzChecks, healthz.NamedCheck("SSH Tunnel Check", m.IsTunnelSyncHealthy)) 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.tunneler.SecondsSinceSync()) }) } apiVersions := []string{} // Install v1 unless disabled. if !m.apiGroupVersionOverrides["api/v1"].Disable { if err := m.api_v1().InstallREST(m.handlerContainer); err != nil { glog.Fatalf("Unable to setup API v1: %v", err) } apiVersions = append(apiVersions, "v1") } apiserver.InstallSupport(m.muxHelper, m.rootWebService, c.EnableProfiling, healthzChecks...) apiserver.AddApiWebService(m.handlerContainer, c.APIPrefix, apiVersions) apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newRequestInfoResolver(), apiVersions) // allGroups records all supported groups at /apis allGroups := []unversioned.APIGroup{} // Install extensions unless disabled. if !m.apiGroupVersionOverrides["extensions/v1beta1"].Disable { m.thirdPartyStorage = c.StorageDestinations.APIGroups["extensions"].Default m.thirdPartyResources = map[string]*thirdpartyresourcedataetcd.REST{} expVersion := m.experimental(c) if err := expVersion.InstallREST(m.handlerContainer); err != nil { glog.Fatalf("Unable to setup experimental api: %v", err) } g, err := latest.Group("extensions") if err != nil { glog.Fatalf("Unable to setup experimental api: %v", err) } expAPIVersions := []unversioned.GroupVersionForDiscovery{ { GroupVersion: expVersion.GroupVersion.String(), Version: expVersion.GroupVersion.Version, }, } storageVersion, found := c.StorageVersions[g.Group] if !found { glog.Fatalf("Couldn't find storage version of group %v", g.Group) } group := unversioned.APIGroup{ Name: g.Group, Versions: expAPIVersions, PreferredVersion: unversioned.GroupVersionForDiscovery{GroupVersion: storageVersion, Version: apiutil.GetVersion(storageVersion)}, } apiserver.AddGroupWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("extensions").Group, group) allGroups = append(allGroups, group) apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newRequestInfoResolver(), []string{expVersion.GroupVersion.String()}) } // This should be done after all groups are registered // TODO: replace the hardcoded "apis". apiserver.AddApisWebService(m.handlerContainer, "/apis", allGroups) // Register root handler. // We do not register this using restful Webservice since we do not want to surface this in api docs. // Allow master to be embedded in contexts which already have something registered at the root if c.EnableIndex { m.mux.HandleFunc("/", apiserver.IndexHandler(m.handlerContainer, m.muxHelper)) } if c.EnableLogsSupport { apiserver.InstallLogsSupport(m.muxHelper) } if c.EnableUISupport { ui.InstallSupport(m.muxHelper, m.enableSwaggerSupport) } 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) } handler := http.Handler(m.mux.(*http.ServeMux)) insecureHandler := handler // 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 attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, m.newRequestInfoResolver()) handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer) // Install Authenticator if c.Authenticator != nil { authenticatedHandler, err := handlers.NewRequestAuthenticator(m.requestContextMapper, c.Authenticator, handlers.Unauthorized(c.SupportsBasicAuth), handler) if err != nil { glog.Fatalf("Could not initialize authenticator: %v", err) } handler = authenticatedHandler } // Since OPTIONS request cannot carry authn headers (by w3c standards), we are doing CORS check // before auth check. Otherwise all the CORS request will be rejected. 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") insecureHandler = apiserver.CORS(insecureHandler, allowedOriginRegexps, nil, nil, "true") } m.InsecureHandler = insecureHandler // Install root web services m.handlerContainer.Add(m.rootWebService) // TODO: Make this optional? Consumers of master depend on this currently. m.Handler = handler if m.enableSwaggerSupport { m.InstallSwaggerAPI() } // 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 } // TODO: Attempt clean shutdown? if m.enableCoreControllers { m.NewBootstrapController().Start() } }
func TestDiscoveryAtAPIS(t *testing.T) { master, etcdserver, config, assert := newMaster(t) defer etcdserver.Terminate(t) server := httptest.NewServer(master.HandlerContainer.ServeMux) resp, err := http.Get(server.URL + "/apis") if !assert.NoError(err) { t.Errorf("unexpected error: %v", err) } assert.Equal(http.StatusOK, resp.StatusCode) groupList := unversioned.APIGroupList{} assert.NoError(decodeResponse(resp, &groupList)) if err != nil { t.Fatalf("unexpected error: %v", err) } expectGroupName := extensions.GroupName expectVersions := []unversioned.GroupVersionForDiscovery{ { GroupVersion: testapi.Extensions.GroupVersion().String(), Version: testapi.Extensions.GroupVersion().Version, }, } expectPreferredVersion := unversioned.GroupVersionForDiscovery{ GroupVersion: config.StorageVersions[extensions.GroupName], Version: apiutil.GetVersion(config.StorageVersions[extensions.GroupName]), } assert.Equal(expectGroupName, groupList.Groups[0].Name) assert.Equal(expectVersions, groupList.Groups[0].Versions) assert.Equal(expectPreferredVersion, groupList.Groups[0].PreferredVersion) thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"} master.thirdPartyResources["/apis/company.com/v1"] = thirdPartyEntry{ nil, unversioned.APIGroup{ Name: "company.com", Versions: []unversioned.GroupVersionForDiscovery{thirdPartyGV}, PreferredVersion: thirdPartyGV, }, } resp, err = http.Get(server.URL + "/apis") if !assert.NoError(err) { t.Errorf("unexpected error: %v", err) } assert.Equal(http.StatusOK, resp.StatusCode) assert.NoError(decodeResponse(resp, &groupList)) if err != nil { t.Fatalf("unexpected error: %v", err) } thirdPartyGroupName := "company.com" thirdPartyExpectVersions := []unversioned.GroupVersionForDiscovery{thirdPartyGV} assert.Equal(thirdPartyGroupName, groupList.Groups[1].Name) assert.Equal(thirdPartyExpectVersions, groupList.Groups[1].Versions) assert.Equal(thirdPartyGV, groupList.Groups[1].PreferredVersion) }
// Check whether the kind in groupVersion is scoped at the root of the api hierarchy func isNamespacedKind(kind, groupVersion string) (bool, error) { group := apiutil.GetGroup(groupVersion) g, err := latest.Group(group) if err != nil { return false, err } restMapping, err := g.RESTMapper.RESTMapping(unversioned.GroupKind{Group: group, Kind: kind}, apiutil.GetVersion(groupVersion)) if err != nil { return false, err } scopeName := restMapping.Scope.Name() if scopeName == meta.RESTScopeNameNamespace { return true, nil } return false, nil }
func (m *Master) InstallAPIs(c *Config) { apiVersions := []string{} // Install v1 unless disabled. if !m.ApiGroupVersionOverrides["api/v1"].Disable { if err := m.api_v1(c).InstallREST(m.HandlerContainer); err != nil { glog.Fatalf("Unable to setup API v1: %v", err) } apiVersions = append(apiVersions, "v1") } // Run the tunnel. healthzChecks := []healthz.HealthzChecker{} if m.tunneler != nil { m.tunneler.Run(m.getNodeAddresses) healthzChecks = append(healthzChecks, healthz.NamedCheck("SSH Tunnel Check", m.IsTunnelSyncHealthy)) 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.tunneler.SecondsSinceSync()) }) } // TODO(nikhiljindal): Refactor generic parts of support services (like /versions) to genericapiserver. apiserver.InstallSupport(m.MuxHelper, m.RootWebService, c.EnableProfiling, healthzChecks...) // Install root web services m.HandlerContainer.Add(m.RootWebService) apiserver.AddApiWebService(m.HandlerContainer, c.APIPrefix, apiVersions) apiserver.InstallServiceErrorHandler(m.HandlerContainer, m.NewRequestInfoResolver(), apiVersions) // allGroups records all supported groups at /apis allGroups := []unversioned.APIGroup{} // Install extensions unless disabled. if !m.ApiGroupVersionOverrides["extensions/v1beta1"].Disable { m.thirdPartyStorage = c.StorageDestinations.APIGroups[extensions.GroupName].Default m.thirdPartyResources = map[string]thirdPartyEntry{} expVersion := m.experimental(c) if err := expVersion.InstallREST(m.HandlerContainer); err != nil { glog.Fatalf("Unable to setup experimental api: %v", err) } g, err := latest.Group(extensions.GroupName) if err != nil { glog.Fatalf("Unable to setup experimental api: %v", err) } expAPIVersions := []unversioned.GroupVersionForDiscovery{ { GroupVersion: expVersion.GroupVersion.String(), Version: expVersion.GroupVersion.Version, }, } storageVersion, found := c.StorageVersions[g.GroupVersion.Group] if !found { glog.Fatalf("Couldn't find storage version of group %v", g.GroupVersion.Group) } group := unversioned.APIGroup{ Name: g.GroupVersion.Group, Versions: expAPIVersions, PreferredVersion: unversioned.GroupVersionForDiscovery{GroupVersion: storageVersion, Version: apiutil.GetVersion(storageVersion)}, } apiserver.AddGroupWebService(m.HandlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie(extensions.GroupName).GroupVersion.Group, group) allGroups = append(allGroups, group) apiserver.InstallServiceErrorHandler(m.HandlerContainer, m.NewRequestInfoResolver(), []string{expVersion.GroupVersion.String()}) } // This should be done after all groups are registered // TODO: replace the hardcoded "apis". apiserver.AddApisWebService(m.HandlerContainer, "/apis", func() []unversioned.APIGroup { groups := []unversioned.APIGroup{} for ix := range allGroups { groups = append(groups, allGroups[ix]) } m.thirdPartyResourcesLock.Lock() defer m.thirdPartyResourcesLock.Unlock() if m.thirdPartyResources != nil { for key := range m.thirdPartyResources { groups = append(groups, m.thirdPartyResources[key].group) } } return groups }) }
func TestDiscoveryAtAPIS(t *testing.T) { master, etcdserver, config, assert := newLimitedMaster(t) defer etcdserver.Terminate(t) server := httptest.NewServer(master.HandlerContainer.ServeMux) resp, err := http.Get(server.URL + "/apis") if !assert.NoError(err) { t.Errorf("unexpected error: %v", err) } assert.Equal(http.StatusOK, resp.StatusCode) groupList := unversioned.APIGroupList{} assert.NoError(decodeResponse(resp, &groupList)) if err != nil { t.Fatalf("unexpected error: %v", err) } expectGroupNames := sets.NewString(autoscaling.GroupName, batch.GroupName, extensions.GroupName) expectVersions := map[string][]unversioned.GroupVersionForDiscovery{ autoscaling.GroupName: { { GroupVersion: testapi.Autoscaling.GroupVersion().String(), Version: testapi.Autoscaling.GroupVersion().Version, }, }, batch.GroupName: { { GroupVersion: testapi.Batch.GroupVersion().String(), Version: testapi.Batch.GroupVersion().Version, }, }, extensions.GroupName: { { GroupVersion: testapi.Extensions.GroupVersion().String(), Version: testapi.Extensions.GroupVersion().Version, }, }, } expectPreferredVersion := map[string]unversioned.GroupVersionForDiscovery{ autoscaling.GroupName: { GroupVersion: config.StorageVersions[autoscaling.GroupName], Version: apiutil.GetVersion(config.StorageVersions[autoscaling.GroupName]), }, batch.GroupName: { GroupVersion: config.StorageVersions[batch.GroupName], Version: apiutil.GetVersion(config.StorageVersions[batch.GroupName]), }, extensions.GroupName: { GroupVersion: config.StorageVersions[extensions.GroupName], Version: apiutil.GetVersion(config.StorageVersions[extensions.GroupName]), }, } assert.Equal(3, len(groupList.Groups)) for _, group := range groupList.Groups { if !expectGroupNames.Has(group.Name) { t.Errorf("got unexpected group %s", group.Name) } assert.Equal(expectVersions[group.Name], group.Versions) assert.Equal(expectPreferredVersion[group.Name], group.PreferredVersion) } thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"} master.addThirdPartyResourceStorage("/apis/company.com/v1", nil, unversioned.APIGroup{ Name: "company.com", Versions: []unversioned.GroupVersionForDiscovery{thirdPartyGV}, PreferredVersion: thirdPartyGV, }) resp, err = http.Get(server.URL + "/apis") if !assert.NoError(err) { t.Errorf("unexpected error: %v", err) } assert.Equal(http.StatusOK, resp.StatusCode) assert.NoError(decodeResponse(resp, &groupList)) if err != nil { t.Fatalf("unexpected error: %v", err) } assert.Equal(4, len(groupList.Groups)) expectGroupNames.Insert("company.com") expectVersions["company.com"] = []unversioned.GroupVersionForDiscovery{thirdPartyGV} expectPreferredVersion["company.com"] = thirdPartyGV for _, group := range groupList.Groups { if !expectGroupNames.Has(group.Name) { t.Errorf("got unexpected group %s", group.Name) } assert.Equal(expectVersions[group.Name], group.Versions) assert.Equal(expectPreferredVersion[group.Name], group.PreferredVersion) } }