Create MinionController to sync minions from cloudprovider (pkg cloudprovider/controller).

pull/6/head
Deyuan Deng 2014-10-08 19:14:37 -04:00
parent f603785698
commit ec46e94dc2
10 changed files with 350 additions and 475 deletions

View File

@ -0,0 +1,19 @@
/*
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 controller contains code for syncing cloud instances with
// minion registry
package controller

View File

@ -0,0 +1,109 @@
/*
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 controller
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
"github.com/golang/glog"
)
type MinionController struct {
cloud cloudprovider.Interface
matchRE string
staticResources *api.NodeResources
registry minion.Registry
}
// NewMinionController returns a new minion controller to sync instances from cloudprovider.
func NewMinionController(cloud cloudprovider.Interface, matchRE string, staticResources *api.NodeResources, registry minion.Registry) (*MinionController, error) {
return &MinionController{
cloud: cloud,
matchRE: matchRE,
staticResources: staticResources,
registry: registry,
}, nil
}
// Sync syncs list of instances from cloudprovider to master etcd registry.
func (s *MinionController) Sync() error {
matches, err := s.cloudMinions()
if err != nil {
return err
}
minions, err := s.registry.ListMinions(nil)
if err != nil {
return err
}
minionMap := make(map[string]*api.Minion)
for _, minion := range minions.Items {
minionMap[minion.ID] = &minion
}
// Create or delete minions from registry.
for _, match := range matches.Items {
if _, ok := minionMap[match.ID]; !ok {
glog.Infof("Create minion in registry: %s", match.ID)
err = s.registry.CreateMinion(nil, &match)
if err != nil {
return err
}
}
delete(minionMap, match.ID)
}
for minionID := range minionMap {
glog.Infof("Delete minion from registry: %s", minionID)
err = s.registry.DeleteMinion(nil, minionID)
if err != nil {
return err
}
}
return nil
}
// cloudMinions constructs and returns api.MinionList from cloudprovider.
func (s *MinionController) cloudMinions() (*api.MinionList, error) {
instances, ok := s.cloud.Instances()
if !ok {
return nil, fmt.Errorf("cloud doesn't support instances")
}
matches, err := instances.List(s.matchRE)
if err != nil {
return nil, err
}
result := &api.MinionList{
Items: make([]api.Minion, len(matches)),
}
for i := range matches {
result.Items[i].ID = matches[i]
resources, err := instances.GetNodeResources(matches[i])
if err != nil {
return nil, err
}
if resources == nil {
resources = s.staticResources
}
if resources != nil {
result.Items[i].NodeResources = *resources
}
}
return result, nil
}

View File

@ -0,0 +1,183 @@
/*
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 controller
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
fake_cloud "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake"
etcdregistry "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
etcd "github.com/coreos/go-etcd/etcd"
)
func NewTestEtcdRegistry(client tools.EtcdClient) *etcdregistry.Registry {
registry := etcdregistry.NewRegistry(tools.EtcdHelper{client, latest.Codec, tools.RuntimeVersionAdapter{latest.ResourceVersioner}},
&pod.BasicManifestFactory{
ServiceRegistry: &registrytest.ServiceRegistry{},
})
return registry
}
func TestSyncCreateMinion(t *testing.T) {
ctx := api.NewContext()
fakeClient := tools.NewFakeEtcdClient(t)
m1 := runtime.EncodeOrDie(latest.Codec, &api.Minion{TypeMeta: api.TypeMeta{ID: "m1"}})
m2 := runtime.EncodeOrDie(latest.Codec, &api.Minion{TypeMeta: api.TypeMeta{ID: "m2"}})
fakeClient.Set("/registry/minions/m1", m1, 0)
fakeClient.Set("/registry/minions/m2", m2, 0)
fakeClient.ExpectNotFoundGet("/registry/minions/m3")
fakeClient.Data["/registry/minions"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{
{Value: m1},
{Value: m2},
},
},
},
E: nil,
}
registry := NewTestEtcdRegistry(fakeClient)
instances := []string{"m1", "m2", "m3"}
fakeCloud := fake_cloud.FakeCloud{
Machines: instances,
}
minionController, err := NewMinionController(&fakeCloud, ".*", nil, registry)
if err != nil {
t.Errorf("Unexpected error")
}
minion, err := registry.GetMinion(ctx, "m3")
if minion != nil {
t.Errorf("Unexpected contains")
}
err = minionController.Sync()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
minion, err = registry.GetMinion(ctx, "m3")
if minion == nil {
t.Errorf("Unexpected !contains")
}
}
func TestSyncDeleteMinion(t *testing.T) {
ctx := api.NewContext()
fakeClient := tools.NewFakeEtcdClient(t)
m1 := runtime.EncodeOrDie(latest.Codec, &api.Minion{TypeMeta: api.TypeMeta{ID: "m1"}})
m2 := runtime.EncodeOrDie(latest.Codec, &api.Minion{TypeMeta: api.TypeMeta{ID: "m2"}})
m3 := runtime.EncodeOrDie(latest.Codec, &api.Minion{TypeMeta: api.TypeMeta{ID: "m3"}})
fakeClient.Set("/registry/minions/m1", m1, 0)
fakeClient.Set("/registry/minions/m2", m2, 0)
fakeClient.Set("/registry/minions/m3", m3, 0)
fakeClient.Data["/registry/minions"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{
{Value: m1},
{Value: m2},
{Value: m3},
},
},
},
E: nil,
}
registry := NewTestEtcdRegistry(fakeClient)
instances := []string{"m1", "m2"}
fakeCloud := fake_cloud.FakeCloud{
Machines: instances,
}
minionController, err := NewMinionController(&fakeCloud, ".*", nil, registry)
if err != nil {
t.Errorf("Unexpected error")
}
minion, err := registry.GetMinion(ctx, "m3")
if minion == nil {
t.Errorf("Unexpected !contains")
}
err = minionController.Sync()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
minion, err = registry.GetMinion(ctx, "m3")
if minion != nil {
t.Errorf("Unexpected contains")
}
}
func TestSyncMinionRegexp(t *testing.T) {
ctx := api.NewContext()
fakeClient := tools.NewFakeEtcdClient(t)
fakeClient.Data["/registry/minions"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{},
},
},
E: nil,
}
registry := NewTestEtcdRegistry(fakeClient)
instances := []string{"m1", "m2", "n1", "n2"}
fakeCloud := fake_cloud.FakeCloud{
Machines: instances,
}
minionController, err := NewMinionController(&fakeCloud, "m[0-9]+", nil, registry)
if err != nil {
t.Errorf("Unexpected error")
}
err = minionController.Sync()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
var minion *api.Minion
fakeClient.ExpectNotFoundGet("/registry/minions/n1")
fakeClient.ExpectNotFoundGet("/registry/minions/n2")
minion, err = registry.GetMinion(ctx, "m1")
if minion == nil {
t.Errorf("Unexpected !contains")
}
minion, err = registry.GetMinion(ctx, "m2")
if minion == nil {
t.Errorf("Unexpected !contains")
}
minion, err = registry.GetMinion(ctx, "n1")
if minion != nil {
t.Errorf("Unexpected !contains")
}
minion, err = registry.GetMinion(ctx, "n2")
if minion != nil {
t.Errorf("Unexpected !contains")
}
}

View File

@ -27,6 +27,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
cloudcontroller "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/binding"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/controller"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
@ -39,7 +40,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog"
)
@ -100,56 +100,50 @@ func New(c *Config) *Master {
minionRegistry: minionRegistry,
client: c.Client,
}
m.init(c.Cloud, c.PodInfoGetter)
m.init(c)
return m
}
func makeMinionRegistry(c *Config) minion.Registry {
var minionRegistry minion.Registry
if c.Cloud != nil && len(c.MinionRegexp) > 0 {
var err error
minionRegistry, err = minion.NewCloudRegistry(c.Cloud, c.MinionRegexp, &c.NodeResources)
if err != nil {
glog.Errorf("Failed to initalize cloud minion registry reverting to static registry (%#v)", err)
}
var minionRegistry minion.Registry = etcd.NewRegistry(c.EtcdHelper, nil)
if c.HealthCheckMinions {
minionRegistry = minion.NewHealthyRegistry(minionRegistry, &http.Client{})
}
if minionRegistry == nil {
minionRegistry = etcd.NewRegistry(c.EtcdHelper, nil)
return minionRegistry
}
// init initializes master.
func (m *Master) init(c *Config) {
podCache := NewPodCache(c.PodInfoGetter, m.podRegistry)
go util.Forever(func() { podCache.UpdateAllContainers() }, time.Second*30)
if c.Cloud != nil && len(c.MinionRegexp) > 0 {
// TODO: Move minion controller to its own code.
minionController, err := cloudcontroller.NewMinionController(c.Cloud, c.MinionRegexp, &c.NodeResources, m.minionRegistry)
if err != nil {
glog.Errorf("Failed to initalize minion controller (%#v)", err)
}
// TODO: Create a Run() method on controller to invoke Sync().
go util.OnceAndForever(func() { minionController.Sync() }, c.MinionCacheTTL)
} else {
for _, minionID := range c.Minions {
minionRegistry.CreateMinion(nil, &api.Minion{
m.minionRegistry.CreateMinion(nil, &api.Minion{
TypeMeta: api.TypeMeta{ID: minionID},
NodeResources: c.NodeResources,
})
}
}
if c.HealthCheckMinions {
minionRegistry = minion.NewHealthyRegistry(minionRegistry, &http.Client{})
}
if c.MinionCacheTTL > 0 {
cachingMinionRegistry, err := minion.NewCachingRegistry(minionRegistry, c.MinionCacheTTL)
if err != nil {
glog.Errorf("Failed to initialize caching layer, ignoring cache.")
} else {
minionRegistry = cachingMinionRegistry
}
}
return minionRegistry
}
func (m *Master) init(cloud cloudprovider.Interface, podInfoGetter client.PodInfoGetter) {
podCache := NewPodCache(podInfoGetter, m.podRegistry)
go util.Forever(func() { podCache.UpdateAllContainers() }, time.Second*30)
m.storage = map[string]apiserver.RESTStorage{
"pods": pod.NewREST(&pod.RESTConfig{
CloudProvider: cloud,
CloudProvider: c.Cloud,
PodCache: podCache,
PodInfoGetter: podInfoGetter,
PodInfoGetter: c.PodInfoGetter,
Registry: m.podRegistry,
Minions: m.client,
}),
"replicationControllers": controller.NewREST(m.controllerRegistry, m.podRegistry),
"services": service.NewREST(m.serviceRegistry, cloud, m.minionRegistry),
"services": service.NewREST(m.serviceRegistry, c.Cloud, m.minionRegistry),
"endpoints": endpoint.NewREST(m.endpointRegistry),
"minions": minion.NewREST(m.minionRegistry),
"events": event.NewREST(m.eventRegistry),

View File

@ -1,118 +0,0 @@
/*
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 minion
import (
"sync"
"sync/atomic"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
type Clock interface {
Now() time.Time
}
type SystemClock struct{}
func (SystemClock) Now() time.Time {
return time.Now()
}
type CachingRegistry struct {
delegate Registry
ttl time.Duration
nodes *api.MinionList
lastUpdate int64
lock sync.RWMutex
clock Clock
}
func NewCachingRegistry(delegate Registry, ttl time.Duration) (Registry, error) {
list, err := delegate.ListMinions(nil)
if err != nil {
return nil, err
}
return &CachingRegistry{
delegate: delegate,
ttl: ttl,
nodes: list,
lastUpdate: time.Now().Unix(),
clock: SystemClock{},
}, nil
}
func (r *CachingRegistry) GetMinion(ctx api.Context, nodeID string) (*api.Minion, error) {
if r.expired() {
if err := r.refresh(ctx, false); err != nil {
return nil, err
}
}
r.lock.RLock()
defer r.lock.RUnlock()
for _, node := range r.nodes.Items {
if node.ID == nodeID {
return &node, nil
}
}
return nil, ErrDoesNotExist
}
func (r *CachingRegistry) DeleteMinion(ctx api.Context, nodeID string) error {
if err := r.delegate.DeleteMinion(ctx, nodeID); err != nil {
return err
}
return r.refresh(ctx, true)
}
func (r *CachingRegistry) CreateMinion(ctx api.Context, minion *api.Minion) error {
if err := r.delegate.CreateMinion(ctx, minion); err != nil {
return err
}
return r.refresh(ctx, true)
}
func (r *CachingRegistry) ListMinions(ctx api.Context) (*api.MinionList, error) {
if r.expired() {
if err := r.refresh(ctx, false); err != nil {
return r.nodes, err
}
}
return r.nodes, nil
}
func (r *CachingRegistry) expired() bool {
var unix int64
atomic.SwapInt64(&unix, r.lastUpdate)
return r.clock.Now().Sub(time.Unix(r.lastUpdate, 0)) > r.ttl
}
// refresh updates the current store. It double checks expired under lock with the assumption
// of optimistic concurrency with the other functions.
func (r *CachingRegistry) refresh(ctx api.Context, force bool) error {
r.lock.Lock()
defer r.lock.Unlock()
if force || r.expired() {
var err error
r.nodes, err = r.delegate.ListMinions(ctx)
time := r.clock.Now()
atomic.SwapInt64(&r.lastUpdate, time.Unix())
return err
}
return nil
}

View File

@ -1,137 +0,0 @@
/*
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 minion
import (
"reflect"
"testing"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
)
type fakeClock struct {
now time.Time
}
func (f *fakeClock) Now() time.Time {
return f.now
}
func TestCachingHit(t *testing.T) {
ctx := api.NewContext()
fakeClock := fakeClock{
now: time.Unix(0, 0),
}
fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"}, api.NodeResources{})
expected := registrytest.MakeMinionList([]string{"m1", "m2", "m3"}, api.NodeResources{})
cache := CachingRegistry{
delegate: fakeRegistry,
ttl: 1 * time.Second,
clock: &fakeClock,
lastUpdate: fakeClock.Now().Unix(),
nodes: expected,
}
list, err := cache.ListMinions(ctx)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(list, expected) {
t.Errorf("expected: %v, got %v", expected, list)
}
}
func TestCachingMiss(t *testing.T) {
ctx := api.NewContext()
fakeClock := fakeClock{
now: time.Unix(0, 0),
}
fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"}, api.NodeResources{})
expected := registrytest.MakeMinionList([]string{"m1", "m2", "m3"}, api.NodeResources{})
cache := CachingRegistry{
delegate: fakeRegistry,
ttl: 1 * time.Second,
clock: &fakeClock,
lastUpdate: fakeClock.Now().Unix(),
nodes: expected,
}
fakeClock.now = time.Unix(3, 0)
list, err := cache.ListMinions(ctx)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(list, &fakeRegistry.Minions) {
t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list)
}
}
func TestCachingInsert(t *testing.T) {
ctx := api.NewContext()
fakeClock := fakeClock{
now: time.Unix(0, 0),
}
fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"}, api.NodeResources{})
expected := registrytest.MakeMinionList([]string{"m1", "m2", "m3"}, api.NodeResources{})
cache := CachingRegistry{
delegate: fakeRegistry,
ttl: 1 * time.Second,
clock: &fakeClock,
lastUpdate: fakeClock.Now().Unix(),
nodes: expected,
}
err := cache.CreateMinion(ctx, &api.Minion{
TypeMeta: api.TypeMeta{ID: "foo"},
})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
list, err := cache.ListMinions(ctx)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(list, &fakeRegistry.Minions) {
t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list)
}
}
func TestCachingDelete(t *testing.T) {
ctx := api.NewContext()
fakeClock := fakeClock{
now: time.Unix(0, 0),
}
fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"}, api.NodeResources{})
expected := registrytest.MakeMinionList([]string{"m1", "m2", "m3"}, api.NodeResources{})
cache := CachingRegistry{
delegate: fakeRegistry,
ttl: 1 * time.Second,
clock: &fakeClock,
lastUpdate: fakeClock.Now().Unix(),
nodes: expected,
}
err := cache.DeleteMinion(ctx, "m2")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
list, err := cache.ListMinions(ctx)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(list, &fakeRegistry.Minions) {
t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list)
}
}

View File

@ -1,88 +0,0 @@
/*
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 minion
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
)
type CloudRegistry struct {
cloud cloudprovider.Interface
matchRE string
staticResources *api.NodeResources
}
func NewCloudRegistry(cloud cloudprovider.Interface, matchRE string, staticResources *api.NodeResources) (*CloudRegistry, error) {
return &CloudRegistry{
cloud: cloud,
matchRE: matchRE,
staticResources: staticResources,
}, nil
}
func (r *CloudRegistry) GetMinion(ctx api.Context, nodeID string) (*api.Minion, error) {
instances, err := r.ListMinions(ctx)
if err != nil {
return nil, err
}
for _, node := range instances.Items {
if node.ID == nodeID {
return &node, nil
}
}
return nil, ErrDoesNotExist
}
func (r CloudRegistry) DeleteMinion(ctx api.Context, nodeID string) error {
return fmt.Errorf("unsupported")
}
func (r CloudRegistry) CreateMinion(ctx api.Context, minion *api.Minion) error {
return fmt.Errorf("unsupported")
}
func (r *CloudRegistry) ListMinions(ctx api.Context) (*api.MinionList, error) {
instances, ok := r.cloud.Instances()
if !ok {
return nil, fmt.Errorf("cloud doesn't support instances")
}
matches, err := instances.List(r.matchRE)
if err != nil {
return nil, err
}
result := &api.MinionList{
Items: make([]api.Minion, len(matches)),
}
for ix := range matches {
result.Items[ix].ID = matches[ix]
resources, err := instances.GetNodeResources(matches[ix])
if err != nil {
return nil, err
}
if resources == nil {
resources = r.staticResources
}
if resources != nil {
result.Items[ix].NodeResources = *resources
}
}
return result, err
}

View File

@ -1,99 +0,0 @@
/*
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 minion
import (
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
fake_cloud "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
)
func TestCloudList(t *testing.T) {
ctx := api.NewContext()
instances := []string{"m1", "m2"}
fakeCloud := fake_cloud.FakeCloud{
Machines: instances,
}
registry, err := NewCloudRegistry(&fakeCloud, ".*", nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
list, err := registry.ListMinions(ctx)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(list, registrytest.MakeMinionList(instances, api.NodeResources{})) {
t.Errorf("Unexpected inequality: %#v, %#v", list, instances)
}
}
func TestCloudGet(t *testing.T) {
ctx := api.NewContext()
instances := []string{"m1", "m2"}
fakeCloud := fake_cloud.FakeCloud{
Machines: instances,
}
registry, err := NewCloudRegistry(&fakeCloud, ".*", nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
minion, err := registry.GetMinion(ctx, "m1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if minion == nil {
t.Errorf("Unexpected !contains")
}
minion, err = registry.GetMinion(ctx, "m100")
if err == nil {
t.Errorf("unexpected non error")
}
if minion != nil {
t.Errorf("Unexpected contains")
}
}
func TestCloudListRegexp(t *testing.T) {
ctx := api.NewContext()
instances := []string{"m1", "m2", "n1", "n2"}
fakeCloud := fake_cloud.FakeCloud{
Machines: instances,
}
registry, err := NewCloudRegistry(&fakeCloud, "m[0-9]+", nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
list, err := registry.ListMinions(ctx)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedList := registrytest.MakeMinionList([]string{"m1", "m2"}, api.NodeResources{})
if !reflect.DeepEqual(list, expectedList) {
t.Errorf("Unexpected inequality: %#v, %#v", list, expectedList)
}
}

View File

@ -76,7 +76,6 @@ func (r *MinionRegistry) DeleteMinion(ctx api.Context, minionID string) error {
defer r.Unlock()
var newList []api.Minion
for _, node := range r.Minions.Items {
if node.ID != minionID {
newList = append(newList, api.Minion{TypeMeta: api.TypeMeta{ID: node.ID}})
}

View File

@ -60,6 +60,19 @@ func Forever(f func(), period time.Duration) {
}
}
// OnceAndForever runs f first then loops forever running f every d. Catches any panics, and keeps going.
func OnceAndForever(f func(), period time.Duration) {
defer HandleCrash()
f()
for {
func() {
defer HandleCrash()
f()
}()
time.Sleep(period)
}
}
// EncodeJSON returns obj marshalled as a JSON string, ignoring any errors.
func EncodeJSON(obj interface{}) string {
data, _ := json.Marshal(obj)