diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index 7a9eef7d257..61669ac6f00 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -42,6 +42,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/nodecontroller" replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller/replication" + explatest "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor" @@ -132,10 +133,14 @@ func startComponents(firstManifestURL, secondManifestURL, apiVersion string) (st cl := client.NewOrDie(&client.Config{Host: apiServer.URL, Version: apiVersion}) - etcdStorage, err := master.NewEtcdStorage(etcdClient, "", etcdtest.PathPrefix()) + etcdStorage, err := master.NewEtcdStorage(etcdClient, latest.InterfacesFor, latest.Version, etcdtest.PathPrefix()) if err != nil { glog.Fatalf("Unable to get etcd storage: %v", err) } + expEtcdStorage, err := master.NewEtcdStorage(etcdClient, explatest.InterfacesFor, explatest.Version, etcdtest.PathPrefix()) + if err != nil { + glog.Fatalf("Unable to get etcd storage for experimental: %v", err) + } // Master host, port, err := net.SplitHostPort(strings.TrimLeft(apiServer.URL, "http://")) @@ -155,11 +160,13 @@ func startComponents(firstManifestURL, secondManifestURL, apiVersion string) (st // Create a master and install handlers into mux. m := master.New(&master.Config{ DatabaseStorage: etcdStorage, + ExpDatabaseStorage: expEtcdStorage, KubeletClient: fakeKubeletClient{}, EnableCoreControllers: true, EnableLogsSupport: false, EnableProfiling: true, APIPrefix: "/api", + ExpAPIPrefix: "/experimental", Authorizer: apiserver.NewAlwaysAllowAuthorizer(), AdmissionControl: admit.NewAlwaysAdmit(), ReadWritePort: portNumber, diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 9c737d2380f..c62868b6639 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -32,10 +32,13 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" + explatest "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/master" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/storage" @@ -71,7 +74,9 @@ type APIServer struct { TLSPrivateKeyFile string CertDirectory string APIPrefix string + ExpAPIPrefix string StorageVersion string + ExpStorageVersion string CloudProvider string CloudConfigFile string EventTTL time.Duration @@ -115,6 +120,7 @@ func NewAPIServer() *APIServer { APIRate: 10.0, APIBurst: 200, APIPrefix: "/api", + ExpAPIPrefix: "/experimental", EventTTL: 1 * time.Hour, AuthorizationMode: "AlwaysAllow", AdmissionControl: "AlwaysAdmit", @@ -172,6 +178,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.CertDirectory, "cert-dir", s.CertDirectory, "The directory where the TLS certs are located (by default /var/run/kubernetes). "+ "If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored.") fs.StringVar(&s.APIPrefix, "api-prefix", s.APIPrefix, "The prefix for API requests on the server. Default '/api'.") + fs.StringVar(&s.ExpAPIPrefix, "experimental-prefix", s.ExpAPIPrefix, "The prefix for experimental API requests on the server. Default '/experimental'.") fs.StringVar(&s.StorageVersion, "storage-version", s.StorageVersion, "The version to store resources with. Defaults to server preferred") fs.StringVar(&s.CloudProvider, "cloud-provider", s.CloudProvider, "The provider for cloud services. Empty string for no provider.") fs.StringVar(&s.CloudConfigFile, "cloud-config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.") @@ -217,7 +224,7 @@ func (s *APIServer) verifyClusterIPFlags() { } } -func newEtcd(etcdConfigFile string, etcdServerList util.StringList, storageVersion string, pathPrefix string) (etcdStorage storage.Interface, err error) { +func newEtcd(etcdConfigFile string, etcdServerList util.StringList, interfacesFunc meta.VersionInterfacesFunc, defaultVersion, storageVersion, pathPrefix string) (etcdStorage storage.Interface, err error) { var client tools.EtcdClient if etcdConfigFile != "" { client, err = etcd.NewClientFromFile(etcdConfigFile) @@ -237,7 +244,10 @@ func newEtcd(etcdConfigFile string, etcdServerList util.StringList, storageVersi client = etcdClient } - return master.NewEtcdStorage(client, storageVersion, pathPrefix) + if storageVersion == "" { + storageVersion = defaultVersion + } + return master.NewEtcdStorage(client, interfacesFunc, storageVersion, pathPrefix) } // Run runs the specified APIServer. This should never exit. @@ -292,6 +302,10 @@ func (s *APIServer) Run(_ []string) error { disableV1 := disableAllAPIs disableV1 = !s.getRuntimeConfigValue("api/v1", !disableV1) + // "experimental/v1={true|false} allows users to enable/disable the experimental API. + // This takes preference over api/all, if specified. + enableExp := s.getRuntimeConfigValue("experimental/v1", false) + // TODO: expose same flags as client.BindClientConfigFlags but for a server clientConfig := &client.Config{ Host: net.JoinHostPort(s.InsecureBindAddress.String(), strconv.Itoa(s.InsecurePort)), @@ -302,10 +316,14 @@ func (s *APIServer) Run(_ []string) error { glog.Fatalf("Invalid server address: %v", err) } - etcdStorage, err := newEtcd(s.EtcdConfigFile, s.EtcdServerList, s.StorageVersion, s.EtcdPathPrefix) + etcdStorage, err := newEtcd(s.EtcdConfigFile, s.EtcdServerList, latest.InterfacesFor, latest.Version, s.StorageVersion, s.EtcdPathPrefix) if err != nil { glog.Fatalf("Invalid storage version or misconfigured etcd: %v", err) } + expEtcdStorage, err := newEtcd(s.EtcdConfigFile, s.EtcdServerList, explatest.InterfacesFor, explatest.Version, s.ExpStorageVersion, s.EtcdPathPrefix) + if err != nil { + glog.Fatalf("Invalid experimental storage version or misconfigured etcd: %v", err) + } n := net.IPNet(s.ServiceClusterIPRange) @@ -360,7 +378,9 @@ func (s *APIServer) Run(_ []string) error { } } config := &master.Config{ - DatabaseStorage: etcdStorage, + DatabaseStorage: etcdStorage, + ExpDatabaseStorage: expEtcdStorage, + EventTTL: s.EventTTL, KubeletClient: kubeletClient, ServiceClusterIPRange: &n, @@ -371,6 +391,7 @@ func (s *APIServer) Run(_ []string) error { EnableProfiling: s.EnableProfiling, EnableIndex: true, APIPrefix: s.APIPrefix, + ExpAPIPrefix: s.ExpAPIPrefix, CorsAllowedOriginList: s.CorsAllowedOriginList, ReadWritePort: s.SecurePort, PublicAddress: net.IP(s.AdvertiseAddress), @@ -379,6 +400,7 @@ func (s *APIServer) Run(_ []string) error { Authorizer: authorizer, AdmissionControl: admissionController, DisableV1: disableV1, + EnableExp: enableExp, MasterServiceNamespace: s.MasterServiceNamespace, ClusterName: s.ClusterName, ExternalHost: s.ExternalHost, diff --git a/cmd/kubernetes/kubernetes.go b/cmd/kubernetes/kubernetes.go index d510e2f3f32..7ddb3013e7e 100644 --- a/cmd/kubernetes/kubernetes.go +++ b/cmd/kubernetes/kubernetes.go @@ -30,12 +30,14 @@ import ( kubeletapp "github.com/GoogleCloudPlatform/kubernetes/cmd/kubelet/app" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/nodecontroller" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/servicecontroller" "github.com/GoogleCloudPlatform/kubernetes/pkg/controller/replication" + explatest "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor" kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" @@ -79,14 +81,19 @@ func (h *delegateHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func runApiServer(etcdClient tools.EtcdClient, addr net.IP, port int, masterServiceNamespace string) { handler := delegateHandler{} - etcdStorage, err := master.NewEtcdStorage(etcdClient, "", master.DefaultEtcdPathPrefix) + etcdStorage, err := master.NewEtcdStorage(etcdClient, latest.InterfacesFor, latest.Version, master.DefaultEtcdPathPrefix) if err != nil { glog.Fatalf("Unable to get etcd storage: %v", err) } + expEtcdStorage, err := master.NewEtcdStorage(etcdClient, explatest.InterfacesFor, explatest.Version, master.DefaultEtcdPathPrefix) + if err != nil { + glog.Fatalf("Unable to get etcd storage for experimental: %v", err) + } // Create a master and install handlers into mux. m := master.New(&master.Config{ - DatabaseStorage: etcdStorage, + DatabaseStorage: etcdStorage, + ExpDatabaseStorage: expEtcdStorage, KubeletClient: &client.HTTPKubeletClient{ Client: http.DefaultClient, Config: &client.KubeletConfig{Port: 10250}, @@ -96,6 +103,7 @@ func runApiServer(etcdClient tools.EtcdClient, addr net.IP, port int, masterServ EnableSwaggerSupport: true, EnableProfiling: *enableProfiling, APIPrefix: "/api", + ExpAPIPrefix: "/experimental", Authorizer: apiserver.NewAlwaysAllowAuthorizer(), ReadWritePort: port, diff --git a/pkg/api/latest/latest.go b/pkg/api/latest/latest.go index 37edae708af..c08370fdf9c 100644 --- a/pkg/api/latest/latest.go +++ b/pkg/api/latest/latest.go @@ -63,6 +63,8 @@ var RESTMapper meta.RESTMapper // userResources is a group of resources mostly used by a kubectl user var userResources = []string{"rc", "svc", "pods", "pvc"} +const importPrefix = "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + func init() { // Use the first API version in the list of registered versions as the latest. Version = registered.RegisteredVersions[0] @@ -75,28 +77,14 @@ func init() { Versions = append(Versions, versions[i]) } - mapper := meta.NewDefaultRESTMapper( - versions, - func(version string) (*meta.VersionInterfaces, bool) { - interfaces, err := InterfacesFor(version) - if err != nil { - return nil, false - } - return interfaces, true - }, - ) - // 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 - kindToRootScope := map[string]bool{ - "Node": true, - "Minion": true, - "Namespace": true, - "PersistentVolume": true, - } - - // setup aliases for groups of resources - mapper.AddResourceAlias("all", userResources...) + rootScoped := util.NewStringSet( + "Node", + "Minion", + "Namespace", + "PersistentVolume", + ) // these kinds should be excluded from the list of resources ignoredKinds := util.NewStringSet( @@ -107,20 +95,11 @@ func init() { "PodExecOptions", "PodProxyOptions") - // enumerate all supported versions, get the kinds, and register with the mapper how to address our resources. - for _, version := range versions { - for kind := range api.Scheme.KnownTypes(version) { - if ignoredKinds.Has(kind) { - continue - } - scope := meta.RESTScopeNamespace - if kindToRootScope[kind] { - scope = meta.RESTScopeRoot - } - mapper.Add(scope, kind, version, false) - } - } + mapper := api.NewDefaultRESTMapper(versions, InterfacesFor, importPrefix, ignoredKinds, rootScoped) + // setup aliases for groups of resources + mapper.AddResourceAlias("all", userResources...) RESTMapper = mapper + api.RegisterRESTMapper(RESTMapper) } // InterfacesFor returns the default Codec and ResourceVersioner for a given version diff --git a/pkg/api/mapper.go b/pkg/api/mapper.go new file mode 100644 index 00000000000..4cc3dd34473 --- /dev/null +++ b/pkg/api/mapper.go @@ -0,0 +1,57 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 api + +import ( + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +var RESTMapper meta.RESTMapper + +func init() { + RESTMapper = meta.MultiRESTMapper{} +} + +func RegisterRESTMapper(m meta.RESTMapper) { + RESTMapper = append(RESTMapper.(meta.MultiRESTMapper), m) +} + +func NewDefaultRESTMapper(versions []string, interfacesFunc meta.VersionInterfacesFunc, importPathPrefix string, + ignoredKinds, rootScoped util.StringSet) *meta.DefaultRESTMapper { + + mapper := meta.NewDefaultRESTMapper(versions, interfacesFunc) + // enumerate all supported versions, get the kinds, and register with the mapper how to address our resources. + for _, version := range versions { + for kind, oType := range Scheme.KnownTypes(version) { + // TODO: Remove import path prefix check. + // We check the import path prefix because we currently stuff both "api" and "experimental" objects + // into the same group within Scheme since Scheme has no notion of groups yet. + if !strings.HasPrefix(oType.PkgPath(), importPathPrefix) || ignoredKinds.Has(kind) { + continue + } + scope := meta.RESTScopeNamespace + if rootScoped.Has(kind) { + scope = meta.RESTScopeRoot + } + mapper.Add(scope, kind, version, false) + } + } + return mapper +} diff --git a/pkg/api/meta/restmapper.go b/pkg/api/meta/restmapper.go index 3970c365b67..05f62be969d 100644 --- a/pkg/api/meta/restmapper.go +++ b/pkg/api/meta/restmapper.go @@ -83,8 +83,8 @@ type DefaultRESTMapper struct { } // VersionInterfacesFunc returns the appropriate codec, typer, and metadata accessor for a -// given api version, or false if no such api version exists. -type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, bool) +// given api version, or an error if no such api version exists. +type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, error) // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion // to a resource name and back based on the objects in a runtime.Scheme @@ -226,8 +226,8 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", version, kind) } - interfaces, ok := m.interfacesFunc(version) - if !ok { + interfaces, err := m.interfacesFunc(version) + if err != nil { return nil, fmt.Errorf("the provided version %q has no relevant versions", version) } diff --git a/pkg/api/meta/restmapper_test.go b/pkg/api/meta/restmapper_test.go index 954f69c7591..e78e7ef232f 100644 --- a/pkg/api/meta/restmapper_test.go +++ b/pkg/api/meta/restmapper_test.go @@ -17,6 +17,7 @@ limitations under the License. package meta import ( + "errors" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -54,12 +55,14 @@ var validCodec = fakeCodec{} var validAccessor = resourceAccessor{} var validConvertor = fakeConvertor{} -func fakeInterfaces(version string) (*VersionInterfaces, bool) { - return &VersionInterfaces{Codec: validCodec, ObjectConvertor: validConvertor, MetadataAccessor: validAccessor}, true +func fakeInterfaces(version string) (*VersionInterfaces, error) { + return &VersionInterfaces{Codec: validCodec, ObjectConvertor: validConvertor, MetadataAccessor: validAccessor}, nil } -func unmatchedVersionInterfaces(version string) (*VersionInterfaces, bool) { - return nil, false +var unmatchedErr = errors.New("no version") + +func unmatchedVersionInterfaces(version string) (*VersionInterfaces, error) { + return nil, unmatchedErr } func TestRESTMapperVersionAndKindForResource(t *testing.T) { diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 9b01efe127e..6ace35819d9 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -88,16 +88,7 @@ func interfacesFor(version string) (*meta.VersionInterfaces, error) { } func newMapper() *meta.DefaultRESTMapper { - return meta.NewDefaultRESTMapper( - versions, - func(version string) (*meta.VersionInterfaces, bool) { - interfaces, err := interfacesFor(version) - if err != nil { - return nil, false - } - return interfaces, true - }, - ) + return meta.NewDefaultRESTMapper(versions, interfacesFor) } func addTestTypes() { diff --git a/pkg/expapi/deep_copy.go b/pkg/expapi/deep_copy.go new file mode 100644 index 00000000000..2becaae4d02 --- /dev/null +++ b/pkg/expapi/deep_copy.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 expapi + +func addDeepCopyFuncs() {} diff --git a/pkg/expapi/latest/latest.go b/pkg/expapi/latest/latest.go new file mode 100644 index 00000000000..628b84c00a6 --- /dev/null +++ b/pkg/expapi/latest/latest.go @@ -0,0 +1,75 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 latest + +import ( + "fmt" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi" + "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi/v1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +var ( + Version string + Versions []string + + accessor = meta.NewAccessor() + Codec runtime.Codec + SelfLinker = runtime.SelfLinker(accessor) + RESTMapper meta.RESTMapper +) + +const importPrefix = "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi" + +func init() { + Version = registered.RegisteredVersions[0] + Codec = runtime.CodecFor(api.Scheme, Version) + // Put the registered versions in Versions in reverse order. + for i := len(registered.RegisteredVersions) - 1; i >= 0; i-- { + Versions = append(Versions, registered.RegisteredVersions[i]) + } + + // 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 := util.NewStringSet() + + ignoredKinds := util.NewStringSet() + + RESTMapper = api.NewDefaultRESTMapper(Versions, InterfacesFor, importPrefix, ignoredKinds, rootScoped) + api.RegisterRESTMapper(RESTMapper) +} + +// InterfacesFor returns the default Codec and ResourceVersioner for a given version +// string, or an error if the version is not known. +func InterfacesFor(version string) (*meta.VersionInterfaces, error) { + switch version { + case "v1": + return &meta.VersionInterfaces{ + Codec: v1.Codec, + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + default: + return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", ")) + } +} diff --git a/pkg/expapi/register.go b/pkg/expapi/register.go new file mode 100644 index 00000000000..4198ad56282 --- /dev/null +++ b/pkg/expapi/register.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 expapi + +func init() {} diff --git a/pkg/expapi/types.go b/pkg/expapi/types.go new file mode 100644 index 00000000000..f30be24d406 --- /dev/null +++ b/pkg/expapi/types.go @@ -0,0 +1,29 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +/* +This file (together with pkg/expapi/v1/types.go) contain the experimental +types in kubernetes. These API objects are experimental, meaning that the +APIs may be broken at any time by the kubernetes team. + +DISCLAIMER: The implementation of the experimental API group itself is +a temporary one meant as a stopgap solution until kubernetes has proper +support for multiple API groups. The transition may require changes +beyond registration differences. In other words, experimental API group +support is experimental. +*/ + +package expapi diff --git a/pkg/expapi/v1/conversion.go b/pkg/expapi/v1/conversion.go new file mode 100644 index 00000000000..2f72ba4d382 --- /dev/null +++ b/pkg/expapi/v1/conversion.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1 + +func addConversionFuncs() {} diff --git a/pkg/expapi/v1/deep_copy.go b/pkg/expapi/v1/deep_copy.go new file mode 100644 index 00000000000..af00429d520 --- /dev/null +++ b/pkg/expapi/v1/deep_copy.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1 + +func addDeepCopyFuncs() {} diff --git a/pkg/expapi/v1/defaults.go b/pkg/expapi/v1/defaults.go new file mode 100644 index 00000000000..268770da898 --- /dev/null +++ b/pkg/expapi/v1/defaults.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1 + +func addDefaultingFuncs() {} diff --git a/pkg/expapi/v1/register.go b/pkg/expapi/v1/register.go new file mode 100644 index 00000000000..c3ab661be26 --- /dev/null +++ b/pkg/expapi/v1/register.go @@ -0,0 +1,30 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +var Codec = runtime.CodecFor(api.Scheme, "v1") + +func init() { + addDeepCopyFuncs() + addConversionFuncs() + addDefaultingFuncs() +} diff --git a/pkg/expapi/v1/types.go b/pkg/expapi/v1/types.go new file mode 100644 index 00000000000..2a5c8ca4cba --- /dev/null +++ b/pkg/expapi/v1/types.go @@ -0,0 +1,17 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1 diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 707badf27ca..a20a119e639 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -18,6 +18,7 @@ package cmd import ( "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -64,6 +65,15 @@ func (*internalType) IsAnAPIObject() {} func (*externalType) IsAnAPIObject() {} func (*ExternalType2) IsAnAPIObject() {} +var versionErr = errors.New("not a version") + +func versionErrIfFalse(b bool) error { + if b { + return nil + } + return versionErr +} + func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) { scheme := runtime.NewScheme() scheme.AddKnownTypeWithName("", "Type", &internalType{}) @@ -73,12 +83,12 @@ func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) { codec := runtime.CodecFor(scheme, "unlikelyversion") validVersion := testapi.Version() - mapper := meta.NewDefaultRESTMapper([]string{"unlikelyversion", validVersion}, func(version string) (*meta.VersionInterfaces, bool) { + mapper := meta.NewDefaultRESTMapper([]string{"unlikelyversion", validVersion}, func(version string) (*meta.VersionInterfaces, error) { return &meta.VersionInterfaces{ Codec: runtime.CodecFor(scheme, version), ObjectConvertor: scheme, MetadataAccessor: meta.NewAccessor(), - }, (version == validVersion || version == "unlikelyversion") + }, versionErrIfFalse(version == validVersion || version == "unlikelyversion") }) for _, version := range []string{"unlikelyversion", validVersion} { for kind := range scheme.KnownTypes(version) { diff --git a/pkg/master/master.go b/pkg/master/master.go index 43a5c47e85e..5689aace37f 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -36,6 +36,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" @@ -43,6 +44,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + explatest "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" @@ -89,22 +91,25 @@ const ( // Config is a structure used to configure a Master. type Config struct { - DatabaseStorage storage.Interface - EventTTL time.Duration - MinionRegexp string - KubeletClient client.KubeletClient + DatabaseStorage storage.Interface + ExpDatabaseStorage storage.Interface + EventTTL time.Duration + MinionRegexp string + KubeletClient client.KubeletClient // allow downstream consumers to disable the core controller loops EnableCoreControllers bool EnableLogsSupport bool EnableUISupport bool // allow downstream consumers to disable swagger EnableSwaggerSupport bool - // allow v1 to be conditionally disabled + // allow api versions to be conditionally disabled DisableV1 bool + EnableExp bool // allow downstream consumers to disable the index route EnableIndex bool EnableProfiling bool APIPrefix string + ExpAPIPrefix string CorsAllowedOriginList util.StringList Authenticator authenticator.Request // TODO(roberthbailey): Remove once the server no longer supports http basic auth. @@ -181,12 +186,14 @@ type Master struct { enableSwaggerSupport bool enableProfiling bool apiPrefix string + expAPIPrefix string corsAllowedOriginList util.StringList authenticator authenticator.Request authorizer authorizer.Authorizer admissionControl admission.Interface masterCount int v1 bool + exp bool requestContextMapper api.RequestContextMapper // External host is the name that should be used in external (public internet) URLs for this master @@ -227,11 +234,8 @@ type Master struct { // NewEtcdStorage returns a storage.Interface for the provided arguments or an error if the version // is incorrect. -func NewEtcdStorage(client tools.EtcdClient, version string, prefix string) (etcdStorage storage.Interface, err error) { - if version == "" { - version = latest.Version - } - versionInterfaces, err := latest.InterfacesFor(version) +func NewEtcdStorage(client tools.EtcdClient, interfacesFunc meta.VersionInterfacesFunc, version, prefix string) (etcdStorage storage.Interface, err error) { + versionInterfaces, err := interfacesFunc(version) if err != nil { return etcdStorage, err } @@ -337,11 +341,13 @@ func New(c *Config) *Master { enableSwaggerSupport: c.EnableSwaggerSupport, enableProfiling: c.EnableProfiling, apiPrefix: c.APIPrefix, + expAPIPrefix: c.ExpAPIPrefix, corsAllowedOriginList: c.CorsAllowedOriginList, authenticator: c.Authenticator, authorizer: c.Authorizer, admissionControl: c.AdmissionControl, v1: !c.DisableV1, + exp: c.EnableExp, requestContextMapper: c.RequestContextMapper, cacheTimeout: c.CacheTimeout, @@ -566,6 +572,16 @@ func (m *Master) init(c *Config) { requestInfoResolver := &apiserver.APIRequestInfoResolver{util.NewStringSet(strings.TrimPrefix(defaultVersion.Root, "/")), defaultVersion.Mapper} apiserver.InstallServiceErrorHandler(m.handlerContainer, requestInfoResolver, apiVersions) + if m.exp { + expVersion := m.expapi(c) + if err := expVersion.InstallREST(m.handlerContainer); err != nil { + glog.Fatalf("Unable to setup experimental api: %v", err) + } + apiserver.AddApiWebService(m.handlerContainer, c.ExpAPIPrefix, []string{expVersion.Version}) + expRequestInfoResolver := &apiserver.APIRequestInfoResolver{util.NewStringSet(strings.TrimPrefix(expVersion.Root, "/")), expVersion.Mapper} + apiserver.InstallServiceErrorHandler(m.handlerContainer, expRequestInfoResolver, []string{expVersion.Version}) + } + // 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 @@ -760,6 +776,30 @@ func (m *Master) api_v1() *apiserver.APIGroupVersion { return version } +// expapi returns the resources and codec for the experimental api +func (m *Master) expapi(c *Config) *apiserver.APIGroupVersion { + storage := map[string]rest.Storage{} + return &apiserver.APIGroupVersion{ + Root: m.expAPIPrefix, + + Creater: api.Scheme, + Convertor: api.Scheme, + Typer: api.Scheme, + + Mapper: explatest.RESTMapper, + Codec: explatest.Codec, + Linker: explatest.SelfLinker, + Storage: storage, + Version: explatest.Version, + + Admit: m.admissionControl, + Context: m.requestContextMapper, + + ProxyDialerFn: m.dialer, + MinRequestTimeout: m.minRequestTimeout, + } +} + // findExternalAddress returns ExternalIP of provided node with fallback to LegacyHostIP. func findExternalAddress(node *api.Node) (string, error) { var fallback string diff --git a/pkg/master/master_test.go b/pkg/master/master_test.go index 6dfd56eb5a5..0f4ef374870 100644 --- a/pkg/master/master_test.go +++ b/pkg/master/master_test.go @@ -21,6 +21,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + explatest "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" etcdstorage "github.com/GoogleCloudPlatform/kubernetes/pkg/storage/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" @@ -33,6 +34,7 @@ func TestGetServersToValidate(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) fakeClient.Machines = []string{"http://machine1:4001", "http://machine2", "http://machine3:4003"} config.DatabaseStorage = etcdstorage.NewEtcdStorage(fakeClient, latest.Codec, etcdtest.PathPrefix()) + config.ExpDatabaseStorage = etcdstorage.NewEtcdStorage(fakeClient, explatest.Codec, etcdtest.PathPrefix()) master.nodeRegistry = registrytest.NewMinionRegistry([]string{"node1", "node2"}, api.NodeResources{}) diff --git a/pkg/namespace/namespace_controller_test.go b/pkg/namespace/namespace_controller_test.go index 0dd480d23a1..0be0cbf4e17 100644 --- a/pkg/namespace/namespace_controller_test.go +++ b/pkg/namespace/namespace_controller_test.go @@ -21,7 +21,6 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -138,7 +137,7 @@ func TestSyncNamespaceThatIsActive(t *testing.T) { func TestRunStop(t *testing.T) { o := testclient.NewObjects(api.Scheme, api.Scheme) - client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} + client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} nsMgr := NewNamespaceManager(client, 1*time.Second) if nsMgr.StopEverything != nil { diff --git a/pkg/volume/persistent_claim/persistent_claim_test.go b/pkg/volume/persistent_claim/persistent_claim_test.go index b18db424621..a325f9e8b10 100644 --- a/pkg/volume/persistent_claim/persistent_claim_test.go +++ b/pkg/volume/persistent_claim/persistent_claim_test.go @@ -23,7 +23,6 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient" "github.com/GoogleCloudPlatform/kubernetes/pkg/types" @@ -238,7 +237,7 @@ func TestNewBuilder(t *testing.T) { o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(item.pv) o.Add(item.claim) - client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} + client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(testProbeVolumePlugins(), newTestHost(t, client)) @@ -295,7 +294,7 @@ func TestNewBuilderClaimNotBound(t *testing.T) { o := testclient.NewObjects(api.Scheme, api.Scheme) o.Add(pv) o.Add(claim) - client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} + client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(testProbeVolumePlugins(), newTestHost(t, client)) diff --git a/pkg/volumeclaimbinder/persistent_volume_claim_binder_test.go b/pkg/volumeclaimbinder/persistent_volume_claim_binder_test.go index 858d26ad667..1a8548ef347 100644 --- a/pkg/volumeclaimbinder/persistent_volume_claim_binder_test.go +++ b/pkg/volumeclaimbinder/persistent_volume_claim_binder_test.go @@ -23,7 +23,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient" "github.com/GoogleCloudPlatform/kubernetes/pkg/volume" @@ -32,7 +31,7 @@ import ( func TestRunStop(t *testing.T) { o := testclient.NewObjects(api.Scheme, api.Scheme) - client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} + client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} binder := NewPersistentVolumeClaimBinder(client, 1*time.Second) if len(binder.stopChannels) != 0 { @@ -119,7 +118,7 @@ func TestExampleObjects(t *testing.T) { t.Fatal(err) } - client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} + client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} if reflect.TypeOf(scenario.expected) == reflect.TypeOf(&api.PersistentVolumeClaim{}) { pvc, err := client.PersistentVolumeClaims("ns").Get("doesntmatter") @@ -179,7 +178,7 @@ func TestBindingWithExamples(t *testing.T) { t.Fatal(err) } - client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} + client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} pv, err := client.PersistentVolumes().Get("any") pv.Spec.PersistentVolumeReclaimPolicy = api.PersistentVolumeReclaimRecycle @@ -282,7 +281,7 @@ func TestMissingFromIndex(t *testing.T) { t.Fatal(err) } - client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)} + client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, api.RESTMapper)} pv, err := client.PersistentVolumes().Get("any") if err != nil { diff --git a/plugin/pkg/admission/namespace/autoprovision/admission.go b/plugin/pkg/admission/namespace/autoprovision/admission.go index 334ffa21008..4ce1e04a3ce 100644 --- a/plugin/pkg/admission/namespace/autoprovision/admission.go +++ b/plugin/pkg/admission/namespace/autoprovision/admission.go @@ -22,7 +22,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" @@ -48,11 +47,11 @@ type provision struct { } func (p *provision) Admit(a admission.Attributes) (err error) { - defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource()) + defaultVersion, kind, err := api.RESTMapper.VersionAndKindForResource(a.GetResource()) if err != nil { return admission.NewForbidden(a, err) } - mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion) + mapping, err := api.RESTMapper.RESTMapping(kind, defaultVersion) if err != nil { return admission.NewForbidden(a, err) } diff --git a/plugin/pkg/admission/namespace/exists/admission.go b/plugin/pkg/admission/namespace/exists/admission.go index 12dfd48177f..eab01285671 100644 --- a/plugin/pkg/admission/namespace/exists/admission.go +++ b/plugin/pkg/admission/namespace/exists/admission.go @@ -23,7 +23,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" @@ -49,11 +48,11 @@ type exists struct { } func (e *exists) Admit(a admission.Attributes) (err error) { - defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource()) + defaultVersion, kind, err := api.RESTMapper.VersionAndKindForResource(a.GetResource()) if err != nil { return admission.NewForbidden(a, err) } - mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion) + mapping, err := api.RESTMapper.RESTMapping(kind, defaultVersion) if err != nil { return admission.NewForbidden(a, err) } diff --git a/plugin/pkg/admission/namespace/lifecycle/admission.go b/plugin/pkg/admission/namespace/lifecycle/admission.go index 16b70bebf09..9c6049a58e0 100644 --- a/plugin/pkg/admission/namespace/lifecycle/admission.go +++ b/plugin/pkg/admission/namespace/lifecycle/admission.go @@ -23,7 +23,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" @@ -59,11 +58,11 @@ func (l *lifecycle) Admit(a admission.Attributes) (err error) { return nil } - defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource()) + defaultVersion, kind, err := api.RESTMapper.VersionAndKindForResource(a.GetResource()) if err != nil { return admission.NewForbidden(a, err) } - mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion) + mapping, err := api.RESTMapper.RESTMapping(kind, defaultVersion) if err != nil { return admission.NewForbidden(a, err) } diff --git a/test/integration/framework/etcd_utils.go b/test/integration/framework/etcd_utils.go index 2104a7bf3cd..c3f8bd0c209 100644 --- a/test/integration/framework/etcd_utils.go +++ b/test/integration/framework/etcd_utils.go @@ -22,6 +22,7 @@ import ( "fmt" "math/rand" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/master" "github.com/GoogleCloudPlatform/kubernetes/pkg/storage" @@ -42,7 +43,7 @@ func NewEtcdClient() *etcd.Client { } func NewEtcdStorage() (storage.Interface, error) { - return master.NewEtcdStorage(NewEtcdClient(), testapi.Version(), etcdtest.PathPrefix()) + return master.NewEtcdStorage(NewEtcdClient(), latest.InterfacesFor, testapi.Version(), etcdtest.PathPrefix()) } func RequireEtcd() { diff --git a/test/integration/framework/master_utils.go b/test/integration/framework/master_utils.go index 3d6a2a590da..eb08d72e4ea 100644 --- a/test/integration/framework/master_utils.go +++ b/test/integration/framework/master_utils.go @@ -26,11 +26,13 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/controller/replication" + explatest "github.com/GoogleCloudPlatform/kubernetes/pkg/expapi/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" @@ -128,19 +130,27 @@ func startMasterOrDie(masterConfig *master.Config) (*master.Master, *httptest.Se var etcdStorage storage.Interface var err error if masterConfig == nil { - etcdStorage, err = master.NewEtcdStorage(NewEtcdClient(), "", etcdtest.PathPrefix()) + etcdClient := NewEtcdClient() + etcdStorage, err = master.NewEtcdStorage(etcdClient, latest.InterfacesFor, latest.Version, etcdtest.PathPrefix()) if err != nil { glog.Fatalf("Failed to create etcd storage for master %v", err) } + expEtcdStorage, err := master.NewEtcdStorage(etcdClient, explatest.InterfacesFor, explatest.Version, etcdtest.PathPrefix()) + if err != nil { + glog.Fatalf("Failed to create etcd storage for master %v", err) + } + masterConfig = &master.Config{ - DatabaseStorage: etcdStorage, - KubeletClient: client.FakeKubeletClient{}, - EnableLogsSupport: false, - EnableProfiling: true, - EnableUISupport: false, - APIPrefix: "/api", - Authorizer: apiserver.NewAlwaysAllowAuthorizer(), - AdmissionControl: admit.NewAlwaysAdmit(), + DatabaseStorage: etcdStorage, + ExpDatabaseStorage: expEtcdStorage, + KubeletClient: client.FakeKubeletClient{}, + EnableLogsSupport: false, + EnableProfiling: true, + EnableUISupport: false, + APIPrefix: "/api", + ExpAPIPrefix: "/experimental", + Authorizer: apiserver.NewAlwaysAllowAuthorizer(), + AdmissionControl: admit.NewAlwaysAdmit(), } } else { etcdStorage = masterConfig.DatabaseStorage @@ -258,20 +268,28 @@ func StartPods(numPods int, host string, restClient *client.Client) error { // TODO: Merge this into startMasterOrDie. func RunAMaster(t *testing.T) (*master.Master, *httptest.Server) { - etcdStorage, err := master.NewEtcdStorage(NewEtcdClient(), testapi.Version(), etcdtest.PathPrefix()) + etcdClient := NewEtcdClient() + etcdStorage, err := master.NewEtcdStorage(etcdClient, latest.InterfacesFor, testapi.Version(), etcdtest.PathPrefix()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expEtcdStorage, err := master.NewEtcdStorage(etcdClient, explatest.InterfacesFor, explatest.Version, etcdtest.PathPrefix()) if err != nil { t.Fatalf("unexpected error: %v", err) } m := master.New(&master.Config{ - DatabaseStorage: etcdStorage, - KubeletClient: client.FakeKubeletClient{}, - EnableLogsSupport: false, - EnableProfiling: true, - EnableUISupport: false, - APIPrefix: "/api", - Authorizer: apiserver.NewAlwaysAllowAuthorizer(), - AdmissionControl: admit.NewAlwaysAdmit(), + DatabaseStorage: etcdStorage, + ExpDatabaseStorage: expEtcdStorage, + KubeletClient: client.FakeKubeletClient{}, + EnableLogsSupport: false, + EnableProfiling: true, + EnableUISupport: false, + APIPrefix: "/api", + ExpAPIPrefix: "/experimental", + EnableExp: true, + Authorizer: apiserver.NewAlwaysAllowAuthorizer(), + AdmissionControl: admit.NewAlwaysAdmit(), }) s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { diff --git a/test/integration/master_test.go b/test/integration/master_test.go new file mode 100644 index 00000000000..dd63c6b4881 --- /dev/null +++ b/test/integration/master_test.go @@ -0,0 +1,39 @@ +// +build integration,!no-etcd + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 integration + +import ( + "net/http" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/test/integration/framework" +) + +func TestExperimentalPrefix(t *testing.T) { + _, s := framework.RunAMaster(t) + defer s.Close() + + resp, err := http.Get(s.URL + "/experimental/") + if err != nil { + t.Fatalf("unexpected error getting experimental prefix: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("got status %v instead of 200 OK", resp.StatusCode) + } +} diff --git a/test/integration/service_account_test.go b/test/integration/service_account_test.go index b768f0e53ec..a45fd760f2f 100644 --- a/test/integration/service_account_test.go +++ b/test/integration/service_account_test.go @@ -33,6 +33,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken" @@ -340,7 +341,7 @@ func startServiceAccountTestServer(t *testing.T) (*client.Client, client.Config, deleteAllEtcdKeys() // Etcd - etcdStorage, err := master.NewEtcdStorage(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix()) + etcdStorage, err := master.NewEtcdStorage(newEtcdClient(), latest.InterfacesFor, testapi.Version(), etcdtest.PathPrefix()) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/test/integration/utils.go b/test/integration/utils.go index f193abaa470..4a7363071c8 100644 --- a/test/integration/utils.go +++ b/test/integration/utils.go @@ -25,6 +25,7 @@ import ( "net/http/httptest" "testing" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" @@ -67,7 +68,7 @@ func deleteAllEtcdKeys() { } func runAMaster(t *testing.T) (*master.Master, *httptest.Server) { - etcdStorage, err := master.NewEtcdStorage(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix()) + etcdStorage, err := master.NewEtcdStorage(newEtcdClient(), latest.InterfacesFor, testapi.Version(), etcdtest.PathPrefix()) if err != nil { t.Fatalf("unexpected error: %v", err) }