From d460c01ade2521d13e2dbd681423f9283171fcd4 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Thu, 19 Jun 2014 18:03:48 -0700 Subject: [PATCH 1/3] Add minion registry --- pkg/registry/minion_registry.go | 148 +++++++++++++++++++++++++++ pkg/registry/minion_registry_test.go | 98 ++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 pkg/registry/minion_registry.go create mode 100644 pkg/registry/minion_registry_test.go diff --git a/pkg/registry/minion_registry.go b/pkg/registry/minion_registry.go new file mode 100644 index 0000000000..38f3c28263 --- /dev/null +++ b/pkg/registry/minion_registry.go @@ -0,0 +1,148 @@ +/* +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 registry + +import ( + "encoding/json" + "fmt" + "sort" + "sync" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" +) + +var ErrDoesNotExist = fmt.Errorf("The requested resource does not exist.") + +// Keep track of a set of minions. Safe for concurrent reading/writing. +type MinionRegistry interface { + List() (currentMinions []string, err error) + Insert(minion string) error + Delete(minion string) error + Contains(minion string) (bool, error) +} + +// Initialize a minion registry with a list of minions. +func MakeMinionRegistry(minions []string) MinionRegistry { + m := &minionList{ + minions: stringSet{}, + } + for _, minion := range minions { + m.minions.insert(minion) + } + return m +} + +type empty struct{} +type stringSet map[string]empty + +func (s stringSet) insert(item string) { + s[item] = empty{} +} + +func (s stringSet) delete(item string) { + delete(s, item) +} + +func (s stringSet) has(item string) bool { + _, contained := s[item] + return contained +} + +type minionList struct { + minions stringSet + lock sync.Mutex +} + +func (m *minionList) List() (currentMinions []string, err error) { + m.lock.Lock() + defer m.lock.Unlock() + // Make a copy to avoid any threading issues + for minion := range m.minions { + currentMinions = append(currentMinions, minion) + } + sort.StringSlice(currentMinions).Sort() + return +} + +func (m *minionList) Insert(newMinion string) error { + m.lock.Lock() + defer m.lock.Unlock() + m.minions.insert(newMinion) + return nil +} + +func (m *minionList) Delete(minion string) error { + m.lock.Lock() + defer m.lock.Unlock() + m.minions.delete(minion) + return nil +} + +func (m *minionList) Contains(minion string) (bool, error) { + m.lock.Lock() + defer m.lock.Unlock() + return m.minions.has(minion), nil +} + +// MinionRegistryStorage implements the RESTStorage interface, backed by a MinionRegistry. +type MinionRegistryStorage struct { + registry MinionRegistry +} + +func MakeMinionRegistryStorage(m MinionRegistry) apiserver.RESTStorage { + return &MinionRegistryStorage{ + registry: m, + } +} + +func (storage *MinionRegistryStorage) List(selector labels.Selector) (interface{}, error) { + return storage.registry.List() +} + +func (storage *MinionRegistryStorage) Get(id string) (interface{}, error) { + exists, err := storage.registry.Contains(id) + if !exists { + return nil, ErrDoesNotExist + } + return id, err +} + +func (storage *MinionRegistryStorage) Extract(body string) (interface{}, error) { + var minion string + err := json.Unmarshal([]byte(body), &minion) + return minion, err +} + +func (storage *MinionRegistryStorage) Create(minion interface{}) (<-chan interface{}, error) { + return apiserver.MakeAsync(func() interface{} { return minion }), storage.registry.Insert(minion.(string)) +} + +func (storage *MinionRegistryStorage) Update(minion interface{}) (<-chan interface{}, error) { + return nil, fmt.Errorf("Minions can only be created (inserted) and deleted.") +} + +func (storage *MinionRegistryStorage) Delete(id string) (<-chan interface{}, error) { + exists, err := storage.registry.Contains(id) + if !exists { + return nil, ErrDoesNotExist + } + if err != nil { + return nil, err + } + return apiserver.MakeAsync(func() interface{} { return apiserver.Status{Success: true} }), storage.registry.Delete(id) +} diff --git a/pkg/registry/minion_registry_test.go b/pkg/registry/minion_registry_test.go new file mode 100644 index 0000000000..17b40a1bee --- /dev/null +++ b/pkg/registry/minion_registry_test.go @@ -0,0 +1,98 @@ +/* +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 registry + +import ( + "reflect" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" +) + +func TestMinionRegistry(t *testing.T) { + m := MakeMinionRegistry([]string{"foo", "bar"}) + if has, err := m.Contains("foo"); !has || err != nil { + t.Errorf("missing expected object") + } + if has, err := m.Contains("bar"); !has || err != nil { + t.Errorf("missing expected object") + } + if has, err := m.Contains("baz"); has || err != nil { + t.Errorf("has unexpected object") + } + + if err := m.Insert("baz"); err != nil { + t.Errorf("insert failed") + } + if has, err := m.Contains("baz"); !has || err != nil { + t.Errorf("insert didn't actually insert") + } + + if err := m.Delete("bar"); err != nil { + t.Errorf("delete failed") + } + if has, err := m.Contains("bar"); has || err != nil { + t.Errorf("delete didn't actually delete") + } + + list, err := m.List() + if err != nil { + t.Errorf("got error calling List") + } + if !reflect.DeepEqual(list, []string{"baz", "foo"}) { + t.Errorf("Unexpected list value: %#v", list) + } +} + +func TestMinionRegistryStorage(t *testing.T) { + m := MakeMinionRegistry([]string{"foo", "bar"}) + ms := MakeMinionRegistryStorage(m) + + if obj, err := ms.Get("foo"); err != nil || obj.(string) != "foo" { + t.Errorf("missing expected object") + } + if obj, err := ms.Get("bar"); err != nil || obj.(string) != "bar" { + t.Errorf("missing expected object") + } + if _, err := ms.Get("baz"); err != ErrDoesNotExist { + t.Errorf("has unexpected object") + } + + if _, err := ms.Create("baz"); err != nil { + t.Errorf("insert failed") + } + if obj, err := ms.Get("baz"); err != nil || obj.(string) != "baz" { + t.Errorf("insert didn't actually insert") + } + + if _, err := ms.Delete("bar"); err != nil { + t.Errorf("delete failed") + } + if _, err := ms.Get("bar"); err != ErrDoesNotExist { + t.Errorf("delete didn't actually delete") + } + if _, err := ms.Delete("bar"); err != ErrDoesNotExist { + t.Errorf("delete returned wrong error") + } + + list, err := ms.List(labels.Everything()) + if err != nil { + t.Errorf("got error calling List") + } + if !reflect.DeepEqual(list.(string), []string{"baz", "foo"}) { + t.Errorf("Unexpected list value: %#v", list) + } +} From 79ee5aa250a3b7e7297515ea219dd76e0c12a24d Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Thu, 19 Jun 2014 18:31:38 -0700 Subject: [PATCH 2/3] Implement minion registry. Minions now a first-class object. --- cmd/integration/integration.go | 2 +- pkg/master/master.go | 23 ++++++++++-------- pkg/registry/etcd_registry.go | 16 +++++++++---- pkg/registry/etcd_registry_test.go | 2 +- pkg/registry/minion_registry_test.go | 2 +- pkg/registry/scheduler.go | 34 ++++++++++++++++++--------- pkg/registry/scheduler_test.go | 12 +++++----- pkg/registry/service_registry.go | 12 ++++++---- pkg/registry/service_registry_test.go | 10 ++++---- 9 files changed, 70 insertions(+), 43 deletions(-) diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index 7a78ea10f2..9a5c49196c 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -39,7 +39,7 @@ func main() { servers := []string{"http://localhost:4001"} log.Printf("Creating etcd client pointing to %v", servers) etcdClient := etcd.NewClient(servers) - machineList := []string{"machine"} + machineList := registry.MakeMinionRegistry([]string{"machine"}) reg := registry.MakeEtcdRegistry(etcdClient, machineList) diff --git a/pkg/master/master.go b/pkg/master/master.go index 7150726322..d27cbbddfd 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -34,8 +34,8 @@ type Master struct { podRegistry registry.PodRegistry controllerRegistry registry.ControllerRegistry serviceRegistry registry.ServiceRegistry + minionRegistry registry.MinionRegistry - minions []string random *rand.Rand storage map[string]apiserver.RESTStorage } @@ -46,37 +46,40 @@ func NewMemoryServer(minions []string, cloud cloudprovider.Interface) *Master { podRegistry: registry.MakeMemoryRegistry(), controllerRegistry: registry.MakeMemoryRegistry(), serviceRegistry: registry.MakeMemoryRegistry(), + minionRegistry: registry.MakeMinionRegistry(minions), } - m.init(minions, cloud) + m.init(cloud) return m } // Returns a new apiserver. func New(etcdServers, minions []string, cloud cloudprovider.Interface) *Master { etcdClient := etcd.NewClient(etcdServers) + minionRegistry := registry.MakeMinionRegistry(minions) m := &Master{ - podRegistry: registry.MakeEtcdRegistry(etcdClient, minions), - controllerRegistry: registry.MakeEtcdRegistry(etcdClient, minions), - serviceRegistry: registry.MakeEtcdRegistry(etcdClient, minions), + podRegistry: registry.MakeEtcdRegistry(etcdClient, minionRegistry), + controllerRegistry: registry.MakeEtcdRegistry(etcdClient, minionRegistry), + serviceRegistry: registry.MakeEtcdRegistry(etcdClient, minionRegistry), + minionRegistry: minionRegistry, } - m.init(minions, cloud) + m.init(cloud) return m } -func (m *Master) init(minions []string, cloud cloudprovider.Interface) { +func (m *Master) init(cloud cloudprovider.Interface) { containerInfo := &client.HTTPContainerInfo{ Client: http.DefaultClient, Port: 10250, } - m.minions = minions m.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) podCache := NewPodCache(containerInfo, m.podRegistry, time.Second*30) go podCache.Loop() m.storage = map[string]apiserver.RESTStorage{ - "pods": registry.MakePodRegistryStorage(m.podRegistry, containerInfo, registry.MakeFirstFitScheduler(m.minions, m.podRegistry, m.random), cloud, podCache), + "pods": registry.MakePodRegistryStorage(m.podRegistry, containerInfo, registry.MakeFirstFitScheduler(m.minionRegistry, m.podRegistry, m.random), cloud, podCache), "replicationControllers": registry.MakeControllerRegistryStorage(m.controllerRegistry), - "services": registry.MakeServiceRegistryStorage(m.serviceRegistry, cloud, m.minions), + "services": registry.MakeServiceRegistryStorage(m.serviceRegistry, cloud, m.minionRegistry), + "minions": registry.MakeMinionRegistryStorage(m.minionRegistry), } } diff --git a/pkg/registry/etcd_registry.go b/pkg/registry/etcd_registry.go index a4df170c15..22a86e20ed 100644 --- a/pkg/registry/etcd_registry.go +++ b/pkg/registry/etcd_registry.go @@ -32,7 +32,7 @@ import ( // EtcdRegistry is an implementation of both ControllerRegistry and PodRegistry which is backed with etcd. type EtcdRegistry struct { etcdClient util.EtcdClient - machines []string + machines MinionRegistry manifestFactory ManifestFactory } @@ -40,7 +40,7 @@ type EtcdRegistry struct { // 'client' is the connection to etcd // 'machines' is the list of machines // 'scheduler' is the scheduling algorithm to use. -func MakeEtcdRegistry(client util.EtcdClient, machines []string) *EtcdRegistry { +func MakeEtcdRegistry(client util.EtcdClient, machines MinionRegistry) *EtcdRegistry { registry := &EtcdRegistry{ etcdClient: client, machines: machines, @@ -61,7 +61,11 @@ func (registry *EtcdRegistry) helper() *util.EtcdHelper { func (registry *EtcdRegistry) ListPods(selector labels.Selector) ([]api.Pod, error) { pods := []api.Pod{} - for _, machine := range registry.machines { + machines, err := registry.machines.List() + if err != nil { + return nil, err + } + for _, machine := range machines { var machinePods []api.Pod err := registry.helper().ExtractList("/registry/hosts/"+machine+"/pods", &machinePods) if err != nil { @@ -175,7 +179,11 @@ func (registry *EtcdRegistry) getPodForMachine(machine, podID string) (pod api.P } func (registry *EtcdRegistry) findPod(podID string) (api.Pod, string, error) { - for _, machine := range registry.machines { + machines, err := registry.machines.List() + if err != nil { + return api.Pod{}, "", err + } + for _, machine := range machines { pod, err := registry.getPodForMachine(machine, podID) if err == nil { return pod, machine, nil diff --git a/pkg/registry/etcd_registry_test.go b/pkg/registry/etcd_registry_test.go index 9e794bc683..29de298a38 100644 --- a/pkg/registry/etcd_registry_test.go +++ b/pkg/registry/etcd_registry_test.go @@ -28,7 +28,7 @@ import ( ) func MakeTestEtcdRegistry(client util.EtcdClient, machines []string) *EtcdRegistry { - registry := MakeEtcdRegistry(client, machines) + registry := MakeEtcdRegistry(client, MakeMinionRegistry(machines)) registry.manifestFactory = &BasicManifestFactory{ serviceRegistry: &MockServiceRegistry{}, } diff --git a/pkg/registry/minion_registry_test.go b/pkg/registry/minion_registry_test.go index 17b40a1bee..fbad64426f 100644 --- a/pkg/registry/minion_registry_test.go +++ b/pkg/registry/minion_registry_test.go @@ -92,7 +92,7 @@ func TestMinionRegistryStorage(t *testing.T) { if err != nil { t.Errorf("got error calling List") } - if !reflect.DeepEqual(list.(string), []string{"baz", "foo"}) { + if !reflect.DeepEqual(list.([]string), []string{"baz", "foo"}) { t.Errorf("Unexpected list value: %#v", list) } } diff --git a/pkg/registry/scheduler.go b/pkg/registry/scheduler.go index ba057407bc..e0518259cb 100644 --- a/pkg/registry/scheduler.go +++ b/pkg/registry/scheduler.go @@ -31,11 +31,11 @@ type Scheduler interface { // RandomScheduler choses machines uniformly at random. type RandomScheduler struct { - machines []string + machines MinionRegistry random rand.Rand } -func MakeRandomScheduler(machines []string, random rand.Rand) Scheduler { +func MakeRandomScheduler(machines MinionRegistry, random rand.Rand) Scheduler { return &RandomScheduler{ machines: machines, random: random, @@ -43,35 +43,43 @@ func MakeRandomScheduler(machines []string, random rand.Rand) Scheduler { } func (s *RandomScheduler) Schedule(pod api.Pod) (string, error) { - return s.machines[s.random.Int()%len(s.machines)], nil + machines, err := s.machines.List() + if err != nil { + return "", err + } + return machines[s.random.Int()%len(machines)], nil } // RoundRobinScheduler chooses machines in order. type RoundRobinScheduler struct { - machines []string + machines MinionRegistry currentIndex int } -func MakeRoundRobinScheduler(machines []string) Scheduler { +func MakeRoundRobinScheduler(machines MinionRegistry) Scheduler { return &RoundRobinScheduler{ machines: machines, - currentIndex: 0, + currentIndex: -1, } } func (s *RoundRobinScheduler) Schedule(pod api.Pod) (string, error) { - result := s.machines[s.currentIndex] - s.currentIndex = (s.currentIndex + 1) % len(s.machines) + machines, err := s.machines.List() + if err != nil { + return "", err + } + s.currentIndex = (s.currentIndex + 1) % len(machines) + result := machines[s.currentIndex] return result, nil } type FirstFitScheduler struct { - machines []string + machines MinionRegistry registry PodRegistry random *rand.Rand } -func MakeFirstFitScheduler(machines []string, registry PodRegistry, random *rand.Rand) Scheduler { +func MakeFirstFitScheduler(machines MinionRegistry, registry PodRegistry, random *rand.Rand) Scheduler { return &FirstFitScheduler{ machines: machines, registry: registry, @@ -91,6 +99,10 @@ func (s *FirstFitScheduler) containsPort(pod api.Pod, port api.Port) bool { } func (s *FirstFitScheduler) Schedule(pod api.Pod) (string, error) { + machines, err := s.machines.List() + if err != nil { + return "", err + } machineToPods := map[string][]api.Pod{} pods, err := s.registry.ListPods(labels.Everything()) if err != nil { @@ -101,7 +113,7 @@ func (s *FirstFitScheduler) Schedule(pod api.Pod) (string, error) { machineToPods[host] = append(machineToPods[host], scheduledPod) } var machineOptions []string - for _, machine := range s.machines { + for _, machine := range machines { podFits := true for _, scheduledPod := range machineToPods[machine] { for _, container := range pod.DesiredState.Manifest.Containers { diff --git a/pkg/registry/scheduler_test.go b/pkg/registry/scheduler_test.go index 82da7b4268..e32304a83b 100644 --- a/pkg/registry/scheduler_test.go +++ b/pkg/registry/scheduler_test.go @@ -32,7 +32,7 @@ func expectSchedule(scheduler Scheduler, pod api.Pod, expected string, t *testin } func TestRoundRobinScheduler(t *testing.T) { - scheduler := MakeRoundRobinScheduler([]string{"m1", "m2", "m3", "m4"}) + scheduler := MakeRoundRobinScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3", "m4"})) expectSchedule(scheduler, api.Pod{}, "m1", t) expectSchedule(scheduler, api.Pod{}, "m2", t) expectSchedule(scheduler, api.Pod{}, "m3", t) @@ -41,7 +41,7 @@ func TestRoundRobinScheduler(t *testing.T) { func TestRandomScheduler(t *testing.T) { random := rand.New(rand.NewSource(0)) - scheduler := MakeRandomScheduler([]string{"m1", "m2", "m3", "m4"}, *random) + scheduler := MakeRandomScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3", "m4"}), *random) _, err := scheduler.Schedule(api.Pod{}) expectNoError(t, err) } @@ -49,7 +49,7 @@ func TestRandomScheduler(t *testing.T) { func TestFirstFitSchedulerNothingScheduled(t *testing.T) { mockRegistry := MockPodRegistry{} r := rand.New(rand.NewSource(0)) - scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r) + scheduler := MakeFirstFitScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3"}), &mockRegistry, r) expectSchedule(scheduler, api.Pod{}, "m3", t) } @@ -81,7 +81,7 @@ func TestFirstFitSchedulerFirstScheduled(t *testing.T) { }, } r := rand.New(rand.NewSource(0)) - scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r) + scheduler := MakeFirstFitScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3"}), &mockRegistry, r) expectSchedule(scheduler, makePod("", 8080), "m3", t) } @@ -94,7 +94,7 @@ func TestFirstFitSchedulerFirstScheduledComplicated(t *testing.T) { }, } r := rand.New(rand.NewSource(0)) - scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r) + scheduler := MakeFirstFitScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3"}), &mockRegistry, r) expectSchedule(scheduler, makePod("", 8080, 8081), "m3", t) } @@ -107,7 +107,7 @@ func TestFirstFitSchedulerFirstScheduledImpossible(t *testing.T) { }, } r := rand.New(rand.NewSource(0)) - scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r) + scheduler := MakeFirstFitScheduler(MakeMinionRegistry([]string{"m1", "m2", "m3"}), &mockRegistry, r) _, err := scheduler.Schedule(makePod("", 8080, 8081)) if err == nil { t.Error("Unexpected non-error.") diff --git a/pkg/registry/service_registry.go b/pkg/registry/service_registry.go index b55912054d..419719869d 100644 --- a/pkg/registry/service_registry.go +++ b/pkg/registry/service_registry.go @@ -30,14 +30,14 @@ import ( type ServiceRegistryStorage struct { registry ServiceRegistry cloud cloudprovider.Interface - hosts []string + machines MinionRegistry } -func MakeServiceRegistryStorage(registry ServiceRegistry, cloud cloudprovider.Interface, hosts []string) apiserver.RESTStorage { +func MakeServiceRegistryStorage(registry ServiceRegistry, cloud cloudprovider.Interface, machines MinionRegistry) apiserver.RESTStorage { return &ServiceRegistryStorage{ registry: registry, cloud: cloud, - hosts: hosts, + machines: machines, } } @@ -117,7 +117,11 @@ func (sr *ServiceRegistryStorage) Create(obj interface{}) (<-chan interface{}, e balancer, ok = sr.cloud.TCPLoadBalancer() } if ok && balancer != nil { - err := balancer.CreateTCPLoadBalancer(srv.ID, "us-central1", srv.Port, sr.hosts) + hosts, err := sr.machines.List() + if err != nil { + return nil, err + } + err = balancer.CreateTCPLoadBalancer(srv.ID, "us-central1", srv.Port, hosts) if err != nil { return nil, err } diff --git a/pkg/registry/service_registry_test.go b/pkg/registry/service_registry_test.go index 2b6d09cb52..109a9b7f7c 100644 --- a/pkg/registry/service_registry_test.go +++ b/pkg/registry/service_registry_test.go @@ -29,7 +29,7 @@ func TestServiceRegistry(t *testing.T) { fakeCloud := &cloudprovider.FakeCloud{} machines := []string{"foo", "bar", "baz"} - storage := MakeServiceRegistryStorage(memory, fakeCloud, machines) + storage := MakeServiceRegistryStorage(memory, fakeCloud, MakeMinionRegistry(machines)) svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, @@ -51,7 +51,7 @@ func TestServiceRegistryExternalService(t *testing.T) { fakeCloud := &cloudprovider.FakeCloud{} machines := []string{"foo", "bar", "baz"} - storage := MakeServiceRegistryStorage(memory, fakeCloud, machines) + storage := MakeServiceRegistryStorage(memory, fakeCloud, MakeMinionRegistry(machines)) svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, @@ -76,7 +76,7 @@ func TestServiceRegistryExternalServiceError(t *testing.T) { } machines := []string{"foo", "bar", "baz"} - storage := MakeServiceRegistryStorage(memory, fakeCloud, machines) + storage := MakeServiceRegistryStorage(memory, fakeCloud, MakeMinionRegistry(machines)) svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, @@ -99,7 +99,7 @@ func TestServiceRegistryDelete(t *testing.T) { fakeCloud := &cloudprovider.FakeCloud{} machines := []string{"foo", "bar", "baz"} - storage := MakeServiceRegistryStorage(memory, fakeCloud, machines) + storage := MakeServiceRegistryStorage(memory, fakeCloud, MakeMinionRegistry(machines)) svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, @@ -123,7 +123,7 @@ func TestServiceRegistryDeleteExternal(t *testing.T) { fakeCloud := &cloudprovider.FakeCloud{} machines := []string{"foo", "bar", "baz"} - storage := MakeServiceRegistryStorage(memory, fakeCloud, machines) + storage := MakeServiceRegistryStorage(memory, fakeCloud, MakeMinionRegistry(machines)) svc := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, From c12a3773f51498f1049400b9b197280f8f38b7f2 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Fri, 20 Jun 2014 14:01:16 -0700 Subject: [PATCH 3/3] Add minion types, address other comments --- pkg/api/helper.go | 2 ++ pkg/api/types.go | 14 +++++++++++++ pkg/registry/minion_registry.go | 30 +++++++++++++++++++--------- pkg/registry/minion_registry_test.go | 19 +++++++++++++----- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/pkg/api/helper.go b/pkg/api/helper.go index b4fe0c42d9..d4bd0425db 100644 --- a/pkg/api/helper.go +++ b/pkg/api/helper.go @@ -34,6 +34,8 @@ func init() { ReplicationController{}, ServiceList{}, Service{}, + MinionList{}, + Minion{}, Status{}, ) } diff --git a/pkg/api/types.go b/pkg/api/types.go index fdbd8eb90a..a9d15a3f47 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -175,6 +175,20 @@ type Endpoints struct { Endpoints []string } +// Information about a single Minion; the name of the minion according to etcd +// is in JSONBase.ID. +type Minion struct { + JSONBase `json:",inline" yaml:",inline"` + // Queried from cloud provider, if available. + HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"` +} + +// A list of minions. +type MinionList struct { + JSONBase `json:",inline" yaml:",inline"` + Minions []Minion `json:"minions,omitempty" yaml:"minions,omitempty"` +} + // Status is a return value for calls that don't return other objects. // Arguably, this could go in apiserver, but I'm including it here so clients needn't // import both. diff --git a/pkg/registry/minion_registry.go b/pkg/registry/minion_registry.go index 38f3c28263..99e4667464 100644 --- a/pkg/registry/minion_registry.go +++ b/pkg/registry/minion_registry.go @@ -17,11 +17,11 @@ limitations under the License. package registry import ( - "encoding/json" "fmt" "sort" "sync" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) @@ -71,7 +71,7 @@ type minionList struct { func (m *minionList) List() (currentMinions []string, err error) { m.lock.Lock() defer m.lock.Unlock() - // Make a copy to avoid any threading issues + // Convert from map to []string for minion := range m.minions { currentMinions = append(currentMinions, minion) } @@ -110,8 +110,20 @@ func MakeMinionRegistryStorage(m MinionRegistry) apiserver.RESTStorage { } } +func (storage *MinionRegistryStorage) toApiMinion(name string) api.Minion { + return api.Minion{JSONBase: api.JSONBase{ID: name}} +} + func (storage *MinionRegistryStorage) List(selector labels.Selector) (interface{}, error) { - return storage.registry.List() + nameList, err := storage.registry.List() + if err != nil { + return nil, err + } + var list api.MinionList + for _, name := range nameList { + list.Minions = append(list.Minions, storage.toApiMinion(name)) + } + return list, nil } func (storage *MinionRegistryStorage) Get(id string) (interface{}, error) { @@ -119,17 +131,17 @@ func (storage *MinionRegistryStorage) Get(id string) (interface{}, error) { if !exists { return nil, ErrDoesNotExist } - return id, err + return storage.toApiMinion(id), err } -func (storage *MinionRegistryStorage) Extract(body string) (interface{}, error) { - var minion string - err := json.Unmarshal([]byte(body), &minion) +func (storage *MinionRegistryStorage) Extract(body []byte) (interface{}, error) { + var minion api.Minion + err := api.DecodeInto(body, &minion) return minion, err } func (storage *MinionRegistryStorage) Create(minion interface{}) (<-chan interface{}, error) { - return apiserver.MakeAsync(func() interface{} { return minion }), storage.registry.Insert(minion.(string)) + return apiserver.MakeAsync(func() interface{} { return minion }), storage.registry.Insert(minion.(api.Minion).ID) } func (storage *MinionRegistryStorage) Update(minion interface{}) (<-chan interface{}, error) { @@ -144,5 +156,5 @@ func (storage *MinionRegistryStorage) Delete(id string) (<-chan interface{}, err if err != nil { return nil, err } - return apiserver.MakeAsync(func() interface{} { return apiserver.Status{Success: true} }), storage.registry.Delete(id) + return apiserver.MakeAsync(func() interface{} { return api.Status{Status: api.StatusSuccess} }), storage.registry.Delete(id) } diff --git a/pkg/registry/minion_registry_test.go b/pkg/registry/minion_registry_test.go index fbad64426f..a46936f228 100644 --- a/pkg/registry/minion_registry_test.go +++ b/pkg/registry/minion_registry_test.go @@ -13,12 +13,14 @@ 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 registry import ( "reflect" "testing" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) @@ -61,20 +63,20 @@ func TestMinionRegistryStorage(t *testing.T) { m := MakeMinionRegistry([]string{"foo", "bar"}) ms := MakeMinionRegistryStorage(m) - if obj, err := ms.Get("foo"); err != nil || obj.(string) != "foo" { + if obj, err := ms.Get("foo"); err != nil || obj.(api.Minion).ID != "foo" { t.Errorf("missing expected object") } - if obj, err := ms.Get("bar"); err != nil || obj.(string) != "bar" { + if obj, err := ms.Get("bar"); err != nil || obj.(api.Minion).ID != "bar" { t.Errorf("missing expected object") } if _, err := ms.Get("baz"); err != ErrDoesNotExist { t.Errorf("has unexpected object") } - if _, err := ms.Create("baz"); err != nil { + if _, err := ms.Create(api.Minion{JSONBase: api.JSONBase{ID: "baz"}}); err != nil { t.Errorf("insert failed") } - if obj, err := ms.Get("baz"); err != nil || obj.(string) != "baz" { + if obj, err := ms.Get("baz"); err != nil || obj.(api.Minion).ID != "baz" { t.Errorf("insert didn't actually insert") } @@ -92,7 +94,14 @@ func TestMinionRegistryStorage(t *testing.T) { if err != nil { t.Errorf("got error calling List") } - if !reflect.DeepEqual(list.([]string), []string{"baz", "foo"}) { + expect := []api.Minion{ + { + JSONBase: api.JSONBase{ID: "baz"}, + }, { + JSONBase: api.JSONBase{ID: "foo"}, + }, + } + if !reflect.DeepEqual(list.(api.MinionList).Minions, expect) { t.Errorf("Unexpected list value: %#v", list) } }