diff --git a/cmd/apiserver/apiserver.go b/cmd/apiserver/apiserver.go index 85e0b3cdba3..5fb67926249 100644 --- a/cmd/apiserver/apiserver.go +++ b/cmd/apiserver/apiserver.go @@ -75,23 +75,13 @@ func main() { glog.Fatalf("-etcd_servers flag is required.") } - var cloud cloudprovider.Interface - switch *cloudProvider { - case "gce": - var err error - cloud, err = cloudprovider.NewGCECloud() - if err != nil { - glog.Fatalf("Couldn't connect to GCE cloud: %#v", err) - } - case "vagrant": - var err error - cloud, err = cloudprovider.NewVagrantCloud() - if err != nil { - glog.Fatalf("Couldn't connect to vagrant cloud: %#v", err) - } - default: + cloud, err := cloudprovider.GetCloudProvider(*cloudProvider) + if err != nil { + glog.Fatalf("Couldn't init cloud provider %q: %#v", *cloudProvider, err) + } + if cloud == nil { if len(*cloudProvider) > 0 { - glog.Infof("Unknown cloud provider: %s", *cloudProvider) + glog.Fatalf("Unknown cloud provider: %s", *cloudProvider) } else { glog.Info("No cloud provider specified.") } diff --git a/cmd/apiserver/plugins.go b/cmd/apiserver/plugins.go new file mode 100644 index 00000000000..9463332c590 --- /dev/null +++ b/cmd/apiserver/plugins.go @@ -0,0 +1,25 @@ +/* +Copyright 2014 Google Inc. 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 main + +// This file exists to force the desired plugin implementations to be linked. +// This should probably be part of some configuration fed into the build for a +// given binary target. +import ( + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant" +) diff --git a/pkg/cloudprovider/fake_cloud.go b/pkg/cloudprovider/fake/fake.go similarity index 89% rename from pkg/cloudprovider/fake_cloud.go rename to pkg/cloudprovider/fake/fake.go index da16f336fdd..3b4fe7c1f4d 100644 --- a/pkg/cloudprovider/fake_cloud.go +++ b/pkg/cloudprovider/fake/fake.go @@ -14,11 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cloudprovider +package fake_cloud import ( "net" "regexp" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" ) // FakeCloud is a test-double implementation of Interface, TCPLoadBalancer and Instances. It is useful for testing. @@ -28,7 +30,7 @@ type FakeCloud struct { Calls []string IP net.IP Machines []string - Zone + cloudprovider.Zone } func (f *FakeCloud) addCall(desc string) { @@ -43,18 +45,18 @@ func (f *FakeCloud) ClearCalls() { // TCPLoadBalancer returns a fake implementation of TCPLoadBalancer. // // Actually it just returns f itself. -func (f *FakeCloud) TCPLoadBalancer() (TCPLoadBalancer, bool) { +func (f *FakeCloud) TCPLoadBalancer() (cloudprovider.TCPLoadBalancer, bool) { return f, true } // Instances returns a fake implementation of Instances. // // Actually it just returns f itself. -func (f *FakeCloud) Instances() (Instances, bool) { +func (f *FakeCloud) Instances() (cloudprovider.Instances, bool) { return f, true } -func (f *FakeCloud) Zones() (Zones, bool) { +func (f *FakeCloud) Zones() (cloudprovider.Zones, bool) { return f, true } @@ -104,7 +106,7 @@ func (f *FakeCloud) List(filter string) ([]string, error) { return result, f.Err } -func (f *FakeCloud) GetZone() (Zone, error) { +func (f *FakeCloud) GetZone() (cloudprovider.Zone, error) { f.addCall("get-zone") return f.Zone, f.Err } diff --git a/pkg/cloudprovider/gce.go b/pkg/cloudprovider/gce/gce.go similarity index 91% rename from pkg/cloudprovider/gce.go rename to pkg/cloudprovider/gce/gce.go index f657f7b3c11..f8e4c048ea1 100644 --- a/pkg/cloudprovider/gce.go +++ b/pkg/cloudprovider/gce/gce.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cloudprovider +package gce_cloud import ( "fmt" @@ -28,6 +28,7 @@ import ( "code.google.com/p/goauth2/compute/serviceaccount" compute "code.google.com/p/google-api-go-client/compute/v1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" ) // GCECloud is an implementation of Interface, TCPLoadBalancer and Instances for Google Compute Engine. @@ -38,6 +39,10 @@ type GCECloud struct { instanceRE string } +func init() { + cloudprovider.RegisterCloudProvider("gce", func() (cloudprovider.Interface, error) { return newGCECloud() }) +} + func getProjectAndZone() (string, string, error) { client := http.Client{} url := "http://metadata/computeMetadata/v1/instance/zone" @@ -62,8 +67,8 @@ func getProjectAndZone() (string, string, error) { return parts[1], parts[3], nil } -// NewGCECloud creates a new instance of GCECloud. -func NewGCECloud() (*GCECloud, error) { +// newGCECloud creates a new instance of GCECloud. +func newGCECloud() (*GCECloud, error) { projectID, zone, err := getProjectAndZone() if err != nil { return nil, err @@ -84,17 +89,17 @@ func NewGCECloud() (*GCECloud, error) { } // TCPLoadBalancer returns an implementation of TCPLoadBalancer for Google Compute Engine. -func (gce *GCECloud) TCPLoadBalancer() (TCPLoadBalancer, bool) { +func (gce *GCECloud) TCPLoadBalancer() (cloudprovider.TCPLoadBalancer, bool) { return gce, true } // Instances returns an implementation of Instances for Google Compute Engine. -func (gce *GCECloud) Instances() (Instances, bool) { +func (gce *GCECloud) Instances() (cloudprovider.Instances, bool) { return gce, true } // Zones returns an implementation of Zones for Google Compute Engine. -func (gce *GCECloud) Zones() (Zones, bool) { +func (gce *GCECloud) Zones() (cloudprovider.Zones, bool) { return gce, true } @@ -237,12 +242,12 @@ func (gce *GCECloud) List(filter string) ([]string, error) { return instances, nil } -func (gce *GCECloud) GetZone() (Zone, error) { +func (gce *GCECloud) GetZone() (cloudprovider.Zone, error) { region, err := getGceRegion(gce.zone) if err != nil { - return Zone{}, err + return cloudprovider.Zone{}, err } - return Zone{ + return cloudprovider.Zone{ FailureDomain: gce.zone, Region: region, }, nil diff --git a/pkg/cloudprovider/gce_test.go b/pkg/cloudprovider/gce/gce_test.go similarity index 97% rename from pkg/cloudprovider/gce_test.go rename to pkg/cloudprovider/gce/gce_test.go index b542d1d32da..32345c72dfc 100644 --- a/pkg/cloudprovider/gce_test.go +++ b/pkg/cloudprovider/gce/gce_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cloudprovider +package gce_cloud import ( "testing" diff --git a/pkg/cloudprovider/plugins.go b/pkg/cloudprovider/plugins.go new file mode 100644 index 00000000000..cac60f630eb --- /dev/null +++ b/pkg/cloudprovider/plugins.go @@ -0,0 +1,56 @@ +/* +Copyright 2014 Google Inc. 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 cloudprovider + +import ( + "sync" + + "github.com/golang/glog" +) + +// Factory is a function that returns a cloudprovider.Interface. +type Factory func() (Interface, error) + +// All registered cloud providers. +var providersMutex sync.Mutex +var providers = make(map[string]Factory) + +// RegisterCloudProvider registers a cloudprovider.Factory by name. This +// is expected to happen during app startup. +func RegisterCloudProvider(name string, cloud Factory) { + providersMutex.Lock() + defer providersMutex.Unlock() + _, found := providers[name] + if found { + glog.Fatalf("Cloud provider %q was registered twice", name) + } + glog.Infof("Registered cloud provider %q", name) + providers[name] = cloud +} + +// GetCloudProvider creates an instance of the named cloud provider, or nil if +// the name is not known. The error return is only used if the named provider +// was known but failed to initialize. +func GetCloudProvider(name string) (Interface, error) { + providersMutex.Lock() + defer providersMutex.Unlock() + f, found := providers[name] + if !found { + return nil, nil + } + return f() +} diff --git a/pkg/cloudprovider/vagrant.go b/pkg/cloudprovider/vagrant/vagrant.go similarity index 89% rename from pkg/cloudprovider/vagrant.go rename to pkg/cloudprovider/vagrant/vagrant.go index 6488776a3ac..ed79cdcf7a3 100644 --- a/pkg/cloudprovider/vagrant.go +++ b/pkg/cloudprovider/vagrant/vagrant.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cloudprovider +package vagrant_cloud import ( "encoding/json" @@ -24,6 +24,8 @@ import ( "net/http" neturl "net/url" "sort" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" ) // VagrantCloud is an implementation of Interface, TCPLoadBalancer and Instances for developer managed Vagrant cluster @@ -34,6 +36,10 @@ type VagrantCloud struct { saltAuth string } +func init() { + cloudprovider.RegisterCloudProvider("vagrant", func() (cloudprovider.Interface, error) { return newVagrantCloud() }) +} + // SaltToken is an authorization token required by Salt REST API type SaltToken struct { Token string `json:"token"` @@ -61,8 +67,8 @@ type SaltMinionsResponse struct { Minions []SaltMinions `json:"return"` } -// NewVagrantCloud creates a new instance of VagrantCloud configured to talk to the Salt REST API. -func NewVagrantCloud() (*VagrantCloud, error) { +// newVagrantCloud creates a new instance of VagrantCloud configured to talk to the Salt REST API. +func newVagrantCloud() (*VagrantCloud, error) { return &VagrantCloud{ saltURL: "http://127.0.0.1:8000", saltUser: "vagrant", @@ -72,17 +78,17 @@ func NewVagrantCloud() (*VagrantCloud, error) { } // TCPLoadBalancer returns an implementation of TCPLoadBalancer for Vagrant cloud -func (v *VagrantCloud) TCPLoadBalancer() (TCPLoadBalancer, bool) { +func (v *VagrantCloud) TCPLoadBalancer() (cloudprovider.TCPLoadBalancer, bool) { return nil, false } // Instances returns an implementation of Instances for Vagrant cloud -func (v *VagrantCloud) Instances() (Instances, bool) { +func (v *VagrantCloud) Instances() (cloudprovider.Instances, bool) { return v, true } // Zones returns an implementation of Zones for Vagrant cloud -func (v *VagrantCloud) Zones() (Zones, bool) { +func (v *VagrantCloud) Zones() (cloudprovider.Zones, bool) { return nil, false } diff --git a/pkg/cloudprovider/vagrant_test.go b/pkg/cloudprovider/vagrant/vagrant_test.go similarity index 99% rename from pkg/cloudprovider/vagrant_test.go rename to pkg/cloudprovider/vagrant/vagrant_test.go index 0877f678379..e0c4c20d5ad 100644 --- a/pkg/cloudprovider/vagrant_test.go +++ b/pkg/cloudprovider/vagrant/vagrant_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cloudprovider +package vagrant_cloud import ( "net/http" diff --git a/pkg/registry/minion/cloud_registry_test.go b/pkg/registry/minion/cloud_registry_test.go index 1b1fb08e917..d325b91b259 100644 --- a/pkg/registry/minion/cloud_registry_test.go +++ b/pkg/registry/minion/cloud_registry_test.go @@ -20,12 +20,12 @@ import ( "reflect" "testing" - "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" ) func TestCloudList(t *testing.T) { instances := []string{"m1", "m2"} - fakeCloud := cloudprovider.FakeCloud{ + fakeCloud := fake_cloud.FakeCloud{ Machines: instances, } registry, err := NewCloudRegistry(&fakeCloud, ".*") @@ -45,7 +45,7 @@ func TestCloudList(t *testing.T) { func TestCloudContains(t *testing.T) { instances := []string{"m1", "m2"} - fakeCloud := cloudprovider.FakeCloud{ + fakeCloud := fake_cloud.FakeCloud{ Machines: instances, } registry, err := NewCloudRegistry(&fakeCloud, ".*") @@ -74,7 +74,7 @@ func TestCloudContains(t *testing.T) { func TestCloudListRegexp(t *testing.T) { instances := []string{"m1", "m2", "n1", "n2"} - fakeCloud := cloudprovider.FakeCloud{ + fakeCloud := fake_cloud.FakeCloud{ Machines: instances, } registry, err := NewCloudRegistry(&fakeCloud, "m[0-9]+") diff --git a/pkg/registry/pod/storage_test.go b/pkg/registry/pod/storage_test.go index dfa76274942..d047ae4e703 100644 --- a/pkg/registry/pod/storage_test.go +++ b/pkg/registry/pod/storage_test.go @@ -23,7 +23,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" @@ -228,7 +228,7 @@ func TestGetPod(t *testing.T) { } func TestGetPodCloud(t *testing.T) { - fakeCloud := &cloudprovider.FakeCloud{} + fakeCloud := &fake_cloud.FakeCloud{} podRegistry := registrytest.NewPodRegistry(nil) podRegistry.Pod = &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} storage := RegistryStorage{ diff --git a/pkg/registry/service/storage_test.go b/pkg/registry/service/storage_test.go index fa92505343d..e86f499efac 100644 --- a/pkg/registry/service/storage_test.go +++ b/pkg/registry/service/storage_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" @@ -30,7 +30,7 @@ import ( func TestServiceRegistryCreate(t *testing.T) { registry := registrytest.NewServiceRegistry() - fakeCloud := &cloudprovider.FakeCloud{} + fakeCloud := &fake_cloud.FakeCloud{} machines := []string{"foo", "bar", "baz"} storage := NewRegistryStorage(registry, fakeCloud, minion.NewRegistry(machines)) svc := &api.Service{ @@ -136,7 +136,7 @@ func TestServiceStorageValidatesUpdate(t *testing.T) { func TestServiceRegistryExternalService(t *testing.T) { registry := registrytest.NewServiceRegistry() - fakeCloud := &cloudprovider.FakeCloud{} + fakeCloud := &fake_cloud.FakeCloud{} machines := []string{"foo", "bar", "baz"} storage := NewRegistryStorage(registry, fakeCloud, minion.NewRegistry(machines)) svc := &api.Service{ @@ -160,7 +160,7 @@ func TestServiceRegistryExternalService(t *testing.T) { func TestServiceRegistryExternalServiceError(t *testing.T) { registry := registrytest.NewServiceRegistry() - fakeCloud := &cloudprovider.FakeCloud{ + fakeCloud := &fake_cloud.FakeCloud{ Err: fmt.Errorf("test error"), } machines := []string{"foo", "bar", "baz"} @@ -182,7 +182,7 @@ func TestServiceRegistryExternalServiceError(t *testing.T) { func TestServiceRegistryDelete(t *testing.T) { registry := registrytest.NewServiceRegistry() - fakeCloud := &cloudprovider.FakeCloud{} + fakeCloud := &fake_cloud.FakeCloud{} machines := []string{"foo", "bar", "baz"} storage := NewRegistryStorage(registry, fakeCloud, minion.NewRegistry(machines)) svc := api.Service{ @@ -202,7 +202,7 @@ func TestServiceRegistryDelete(t *testing.T) { func TestServiceRegistryDeleteExternal(t *testing.T) { registry := registrytest.NewServiceRegistry() - fakeCloud := &cloudprovider.FakeCloud{} + fakeCloud := &fake_cloud.FakeCloud{} machines := []string{"foo", "bar", "baz"} storage := NewRegistryStorage(registry, fakeCloud, minion.NewRegistry(machines)) svc := api.Service{ @@ -245,7 +245,7 @@ func TestServiceRegistryMakeLinkVariables(t *testing.T) { func TestServiceRegistryGet(t *testing.T) { registry := registrytest.NewServiceRegistry() - fakeCloud := &cloudprovider.FakeCloud{} + fakeCloud := &fake_cloud.FakeCloud{} machines := []string{"foo", "bar", "baz"} storage := NewRegistryStorage(registry, fakeCloud, minion.NewRegistry(machines)) registry.CreateService(api.Service{ @@ -263,7 +263,7 @@ func TestServiceRegistryGet(t *testing.T) { func TestServiceRegistryList(t *testing.T) { registry := registrytest.NewServiceRegistry() - fakeCloud := &cloudprovider.FakeCloud{} + fakeCloud := &fake_cloud.FakeCloud{} machines := []string{"foo", "bar", "baz"} storage := NewRegistryStorage(registry, fakeCloud, minion.NewRegistry(machines)) registry.CreateService(api.Service{