Merge pull request #1536 from brendandburns/resource

Add in resource fit as a predicate.  Manually set the available resources.
This commit is contained in:
bgrant0607 2014-10-03 15:29:00 -07:00
commit bed5306434
25 changed files with 280 additions and 103 deletions

View File

@ -27,11 +27,13 @@ import (
"strings" "strings"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master" "github.com/GoogleCloudPlatform/kubernetes/pkg/master"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/golang/glog" "github.com/golang/glog"
@ -52,6 +54,9 @@ var (
machineList util.StringList machineList util.StringList
corsAllowedOriginList util.StringList corsAllowedOriginList util.StringList
allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.") allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.")
// TODO: Discover these by pinging the host machines, and rip out these flags.
nodeMilliCPU = flag.Int("node_milli_cpu", 1000, "The amount of MilliCPU provisioned on each node")
nodeMemory = flag.Int("node_memory", 3*1024*1024*1024, "The amount of memory (in bytes) provisioned on each node")
) )
func init() { func init() {
@ -150,6 +155,12 @@ func main() {
MinionCacheTTL: *minionCacheTTL, MinionCacheTTL: *minionCacheTTL,
MinionRegexp: *minionRegexp, MinionRegexp: *minionRegexp,
PodInfoGetter: podInfoGetter, PodInfoGetter: podInfoGetter,
NodeResources: api.NodeResources{
Capacity: api.ResourceList{
resources.CPU: util.NewIntOrStringFromInt(*nodeMilliCPU),
resources.Memory: util.NewIntOrStringFromInt(*nodeMemory),
},
},
}) })
mux := http.NewServeMux() mux := http.NewServeMux()

View File

@ -13,6 +13,7 @@
"containers": [{ "containers": [{
"name": "php-redis", "name": "php-redis",
"image": "brendanburns/php-redis", "image": "brendanburns/php-redis",
"memory": 10000000,
"ports": [{"containerPort": 80, "hostPort": 8000}] "ports": [{"containerPort": 80, "hostPort": 8000}]
}] }]
} }

View File

@ -26,6 +26,7 @@ import (
"github.com/mitchellh/goamz/aws" "github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2" "github.com/mitchellh/goamz/ec2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
) )
@ -179,3 +180,7 @@ func (aws *AWSCloud) List(filter string) ([]string, error) {
// TODO: Should really use tag query. No need to go regexp. // TODO: Should really use tag query. No need to go regexp.
return aws.getInstancesByRegex(filter) return aws.getInstancesByRegex(filter)
} }
func (v *AWSCloud) GetNodeResources(name string) (*api.NodeResources, error) {
return nil, nil
}

View File

@ -18,6 +18,8 @@ package cloudprovider
import ( import (
"net" "net"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
) )
// Interface is an abstract, pluggable interface for cloud providers. // Interface is an abstract, pluggable interface for cloud providers.
@ -49,6 +51,8 @@ type Instances interface {
IPAddress(name string) (net.IP, error) IPAddress(name string) (net.IP, error)
// List lists instances that match 'filter' which is a regular expression which must match the entire instance name (fqdn) // List lists instances that match 'filter' which is a regular expression which must match the entire instance name (fqdn)
List(filter string) ([]string, error) List(filter string) ([]string, error)
// GetNodeResources gets the resources for a particular node
GetNodeResources(name string) (*api.NodeResources, error)
} }
// Zone represents the location of a particular machine. // Zone represents the location of a particular machine.

View File

@ -20,6 +20,7 @@ import (
"net" "net"
"regexp" "regexp"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
) )
@ -30,6 +31,8 @@ type FakeCloud struct {
Calls []string Calls []string
IP net.IP IP net.IP
Machines []string Machines []string
NodeResources *api.NodeResources
cloudprovider.Zone cloudprovider.Zone
} }
@ -110,3 +113,8 @@ func (f *FakeCloud) GetZone() (cloudprovider.Zone, error) {
f.addCall("get-zone") f.addCall("get-zone")
return f.Zone, f.Err return f.Zone, f.Err
} }
func (f *FakeCloud) GetNodeResources(name string) (*api.NodeResources, error) {
f.addCall("get-node-resources")
return f.NodeResources, f.Err
}

View File

@ -29,7 +29,10 @@ import (
"code.google.com/p/goauth2/compute/serviceaccount" "code.google.com/p/goauth2/compute/serviceaccount"
compute "code.google.com/p/google-api-go-client/compute/v1" compute "code.google.com/p/google-api-go-client/compute/v1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog" "github.com/golang/glog"
) )
@ -254,6 +257,48 @@ func (gce *GCECloud) List(filter string) ([]string, error) {
return instances, nil return instances, nil
} }
func makeResources(cpu float32, memory float32) *api.NodeResources {
return &api.NodeResources{
Capacity: api.ResourceList{
resources.CPU: util.NewIntOrStringFromInt(int(cpu * 1000)),
resources.Memory: util.NewIntOrStringFromInt(int(memory * 1024 * 1024 * 1024)),
},
}
}
func canonicalizeMachineType(machineType string) string {
ix := strings.LastIndex(machineType, "/")
return machineType[ix+1:]
}
func (gce *GCECloud) GetNodeResources(name string) (*api.NodeResources, error) {
instance := canonicalizeInstanceName(name)
instanceCall := gce.service.Instances.Get(gce.projectID, gce.zone, instance)
res, err := instanceCall.Do()
if err != nil {
return nil, err
}
switch canonicalizeMachineType(res.MachineType) {
case "f1-micro":
return makeResources(1, 0.6), nil
case "g1-small":
return makeResources(1, 1.70), nil
case "n1-standard-1":
return makeResources(1, 3.75), nil
case "n1-standard-2":
return makeResources(2, 7.5), nil
case "n1-standard-4":
return makeResources(4, 15), nil
case "n1-standard-8":
return makeResources(8, 30), nil
case "n1-standard-16":
return makeResources(16, 30), nil
default:
glog.Errorf("unknown machine: %s", res.MachineType)
return nil, nil
}
}
func (gce *GCECloud) GetZone() (cloudprovider.Zone, error) { func (gce *GCECloud) GetZone() (cloudprovider.Zone, error) {
region, err := getGceRegion(gce.zone) region, err := getGceRegion(gce.zone)
if err != nil { if err != nil {

View File

@ -28,6 +28,7 @@ import (
"strings" "strings"
"code.google.com/p/gcfg" "code.google.com/p/gcfg"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
) )
@ -154,3 +155,7 @@ func (v *OVirtCloud) List(filter string) ([]string, error) {
return getInstancesFromXml(response.Body) return getInstancesFromXml(response.Body)
} }
func (v *OVirtCloud) GetNodeResources(name string) (*api.NodeResources, error) {
return nil, nil
}

View File

@ -27,6 +27,7 @@ import (
neturl "net/url" neturl "net/url"
"sort" "sort"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
) )
@ -209,3 +210,7 @@ func (v *VagrantCloud) List(filter string) ([]string, error) {
return instances, nil return instances, nil
} }
func (v *VagrantCloud) GetNodeResources(name string) (*api.NodeResources, error) {
return nil, nil
}

View File

@ -20,6 +20,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
@ -52,6 +53,7 @@ type Config struct {
MinionCacheTTL time.Duration MinionCacheTTL time.Duration
MinionRegexp string MinionRegexp string
PodInfoGetter client.PodInfoGetter PodInfoGetter client.PodInfoGetter
NodeResources api.NodeResources
} }
// Master contains state for a Kubernetes cluster master/api server. // Master contains state for a Kubernetes cluster master/api server.
@ -104,13 +106,13 @@ func makeMinionRegistry(c *Config) minion.Registry {
var minionRegistry minion.Registry var minionRegistry minion.Registry
if c.Cloud != nil && len(c.MinionRegexp) > 0 { if c.Cloud != nil && len(c.MinionRegexp) > 0 {
var err error var err error
minionRegistry, err = minion.NewCloudRegistry(c.Cloud, c.MinionRegexp) minionRegistry, err = minion.NewCloudRegistry(c.Cloud, c.MinionRegexp, &c.NodeResources)
if err != nil { if err != nil {
glog.Errorf("Failed to initalize cloud minion registry reverting to static registry (%#v)", err) glog.Errorf("Failed to initalize cloud minion registry reverting to static registry (%#v)", err)
} }
} }
if minionRegistry == nil { if minionRegistry == nil {
minionRegistry = minion.NewRegistry(c.Minions) minionRegistry = minion.NewRegistry(c.Minions, c.NodeResources)
} }
if c.HealthCheckMinions { if c.HealthCheckMinions {
minionRegistry = minion.NewHealthyRegistry(minionRegistry, &http.Client{}) minionRegistry = minion.NewHealthyRegistry(minionRegistry, &http.Client{})

View File

@ -20,6 +20,8 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
) )
type Clock interface { type Clock interface {
@ -35,7 +37,7 @@ func (SystemClock) Now() time.Time {
type CachingRegistry struct { type CachingRegistry struct {
delegate Registry delegate Registry
ttl time.Duration ttl time.Duration
minions []string nodes *api.MinionList
lastUpdate int64 lastUpdate int64
lock sync.RWMutex lock sync.RWMutex
clock Clock clock Clock
@ -49,13 +51,13 @@ func NewCachingRegistry(delegate Registry, ttl time.Duration) (Registry, error)
return &CachingRegistry{ return &CachingRegistry{
delegate: delegate, delegate: delegate,
ttl: ttl, ttl: ttl,
minions: list, nodes: list,
lastUpdate: time.Now().Unix(), lastUpdate: time.Now().Unix(),
clock: SystemClock{}, clock: SystemClock{},
}, nil }, nil
} }
func (r *CachingRegistry) Contains(minion string) (bool, error) { func (r *CachingRegistry) Contains(nodeID string) (bool, error) {
if r.expired() { if r.expired() {
if err := r.refresh(false); err != nil { if err := r.refresh(false); err != nil {
return false, err return false, err
@ -64,8 +66,8 @@ func (r *CachingRegistry) Contains(minion string) (bool, error) {
// block updates in the middle of a contains. // block updates in the middle of a contains.
r.lock.RLock() r.lock.RLock()
defer r.lock.RUnlock() defer r.lock.RUnlock()
for _, name := range r.minions { for _, node := range r.nodes.Items {
if name == minion { if node.ID == nodeID {
return true, nil return true, nil
} }
} }
@ -86,13 +88,13 @@ func (r *CachingRegistry) Insert(minion string) error {
return r.refresh(true) return r.refresh(true)
} }
func (r *CachingRegistry) List() ([]string, error) { func (r *CachingRegistry) List() (*api.MinionList, error) {
if r.expired() { if r.expired() {
if err := r.refresh(false); err != nil { if err := r.refresh(false); err != nil {
return r.minions, err return r.nodes, err
} }
} }
return r.minions, nil return r.nodes, nil
} }
func (r *CachingRegistry) expired() bool { func (r *CachingRegistry) expired() bool {
@ -108,7 +110,7 @@ func (r *CachingRegistry) refresh(force bool) error {
defer r.lock.Unlock() defer r.lock.Unlock()
if force || r.expired() { if force || r.expired() {
var err error var err error
r.minions, err = r.delegate.List() r.nodes, err = r.delegate.List()
time := r.clock.Now() time := r.clock.Now()
atomic.SwapInt64(&r.lastUpdate, time.Unix()) atomic.SwapInt64(&r.lastUpdate, time.Unix())
return err return err

View File

@ -37,13 +37,13 @@ func TestCachingHit(t *testing.T) {
now: time.Unix(0, 0), now: time.Unix(0, 0),
} }
fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"}) fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"})
expected := []string{"m1", "m2", "m3"} expected := registrytest.MakeMinionList([]string{"m1", "m2", "m3"})
cache := CachingRegistry{ cache := CachingRegistry{
delegate: fakeRegistry, delegate: fakeRegistry,
ttl: 1 * time.Second, ttl: 1 * time.Second,
clock: &fakeClock, clock: &fakeClock,
lastUpdate: fakeClock.Now().Unix(), lastUpdate: fakeClock.Now().Unix(),
minions: expected, nodes: expected,
} }
list, err := cache.List() list, err := cache.List()
if err != nil { if err != nil {
@ -59,20 +59,20 @@ func TestCachingMiss(t *testing.T) {
now: time.Unix(0, 0), now: time.Unix(0, 0),
} }
fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"}) fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"})
expected := []string{"m1", "m2", "m3"} expected := registrytest.MakeMinionList([]string{"m1", "m2", "m3"})
cache := CachingRegistry{ cache := CachingRegistry{
delegate: fakeRegistry, delegate: fakeRegistry,
ttl: 1 * time.Second, ttl: 1 * time.Second,
clock: &fakeClock, clock: &fakeClock,
lastUpdate: fakeClock.Now().Unix(), lastUpdate: fakeClock.Now().Unix(),
minions: expected, nodes: expected,
} }
fakeClock.now = time.Unix(3, 0) fakeClock.now = time.Unix(3, 0)
list, err := cache.List() list, err := cache.List()
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(list, fakeRegistry.Minions) { if !reflect.DeepEqual(list, &fakeRegistry.Minions) {
t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list) t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list)
} }
} }
@ -82,13 +82,13 @@ func TestCachingInsert(t *testing.T) {
now: time.Unix(0, 0), now: time.Unix(0, 0),
} }
fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"}) fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"})
expected := []string{"m1", "m2", "m3"} expected := registrytest.MakeMinionList([]string{"m1", "m2", "m3"})
cache := CachingRegistry{ cache := CachingRegistry{
delegate: fakeRegistry, delegate: fakeRegistry,
ttl: 1 * time.Second, ttl: 1 * time.Second,
clock: &fakeClock, clock: &fakeClock,
lastUpdate: fakeClock.Now().Unix(), lastUpdate: fakeClock.Now().Unix(),
minions: expected, nodes: expected,
} }
err := cache.Insert("foo") err := cache.Insert("foo")
if err != nil { if err != nil {
@ -98,7 +98,7 @@ func TestCachingInsert(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(list, fakeRegistry.Minions) { if !reflect.DeepEqual(list, &fakeRegistry.Minions) {
t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list) t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list)
} }
} }
@ -108,13 +108,13 @@ func TestCachingDelete(t *testing.T) {
now: time.Unix(0, 0), now: time.Unix(0, 0),
} }
fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"}) fakeRegistry := registrytest.NewMinionRegistry([]string{"m1", "m2"})
expected := []string{"m1", "m2", "m3"} expected := registrytest.MakeMinionList([]string{"m1", "m2", "m3"})
cache := CachingRegistry{ cache := CachingRegistry{
delegate: fakeRegistry, delegate: fakeRegistry,
ttl: 1 * time.Second, ttl: 1 * time.Second,
clock: &fakeClock, clock: &fakeClock,
lastUpdate: fakeClock.Now().Unix(), lastUpdate: fakeClock.Now().Unix(),
minions: expected, nodes: expected,
} }
err := cache.Delete("m2") err := cache.Delete("m2")
if err != nil { if err != nil {
@ -124,7 +124,7 @@ func TestCachingDelete(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(list, fakeRegistry.Minions) { if !reflect.DeepEqual(list, &fakeRegistry.Minions) {
t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list) t.Errorf("expected: %v, got %v", fakeRegistry.Minions, list)
} }
} }

View File

@ -19,28 +19,31 @@ package minion
import ( import (
"fmt" "fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
) )
type CloudRegistry struct { type CloudRegistry struct {
cloud cloudprovider.Interface cloud cloudprovider.Interface
matchRE string matchRE string
staticResources *api.NodeResources
} }
func NewCloudRegistry(cloud cloudprovider.Interface, matchRE string) (*CloudRegistry, error) { func NewCloudRegistry(cloud cloudprovider.Interface, matchRE string, staticResources *api.NodeResources) (*CloudRegistry, error) {
return &CloudRegistry{ return &CloudRegistry{
cloud: cloud, cloud: cloud,
matchRE: matchRE, matchRE: matchRE,
staticResources: staticResources,
}, nil }, nil
} }
func (r *CloudRegistry) Contains(minion string) (bool, error) { func (r *CloudRegistry) Contains(nodeID string) (bool, error) {
instances, err := r.List() instances, err := r.List()
if err != nil { if err != nil {
return false, err return false, err
} }
for _, name := range instances { for _, node := range instances.Items {
if name == minion { if node.ID == nodeID {
return true, nil return true, nil
} }
} }
@ -55,10 +58,30 @@ func (r CloudRegistry) Insert(minion string) error {
return fmt.Errorf("unsupported") return fmt.Errorf("unsupported")
} }
func (r *CloudRegistry) List() ([]string, error) { func (r *CloudRegistry) List() (*api.MinionList, error) {
instances, ok := r.cloud.Instances() instances, ok := r.cloud.Instances()
if !ok { if !ok {
return nil, fmt.Errorf("cloud doesn't support instances") return nil, fmt.Errorf("cloud doesn't support instances")
} }
return instances.List(r.matchRE) 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

@ -21,6 +21,7 @@ import (
"testing" "testing"
fake_cloud "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" fake_cloud "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
) )
func TestCloudList(t *testing.T) { func TestCloudList(t *testing.T) {
@ -28,7 +29,7 @@ func TestCloudList(t *testing.T) {
fakeCloud := fake_cloud.FakeCloud{ fakeCloud := fake_cloud.FakeCloud{
Machines: instances, Machines: instances,
} }
registry, err := NewCloudRegistry(&fakeCloud, ".*") registry, err := NewCloudRegistry(&fakeCloud, ".*", nil)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -38,7 +39,7 @@ func TestCloudList(t *testing.T) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(list, instances) { if !reflect.DeepEqual(list, registrytest.MakeMinionList(instances)) {
t.Errorf("Unexpected inequality: %#v, %#v", list, instances) t.Errorf("Unexpected inequality: %#v, %#v", list, instances)
} }
} }
@ -48,7 +49,7 @@ func TestCloudContains(t *testing.T) {
fakeCloud := fake_cloud.FakeCloud{ fakeCloud := fake_cloud.FakeCloud{
Machines: instances, Machines: instances,
} }
registry, err := NewCloudRegistry(&fakeCloud, ".*") registry, err := NewCloudRegistry(&fakeCloud, ".*", nil)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -77,7 +78,7 @@ func TestCloudListRegexp(t *testing.T) {
fakeCloud := fake_cloud.FakeCloud{ fakeCloud := fake_cloud.FakeCloud{
Machines: instances, Machines: instances,
} }
registry, err := NewCloudRegistry(&fakeCloud, "m[0-9]+") registry, err := NewCloudRegistry(&fakeCloud, "m[0-9]+", nil)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -87,7 +88,7 @@ func TestCloudListRegexp(t *testing.T) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
expectedList := []string{"m1", "m2"} expectedList := registrytest.MakeMinionList([]string{"m1", "m2"})
if !reflect.DeepEqual(list, expectedList) { if !reflect.DeepEqual(list, expectedList) {
t.Errorf("Unexpected inequality: %#v, %#v", list, expectedList) t.Errorf("Unexpected inequality: %#v, %#v", list, expectedList)
} }

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/health" "github.com/GoogleCloudPlatform/kubernetes/pkg/health"
"github.com/golang/glog" "github.com/golang/glog"
@ -65,20 +66,20 @@ func (r *HealthyRegistry) Insert(minion string) error {
return r.delegate.Insert(minion) return r.delegate.Insert(minion)
} }
func (r *HealthyRegistry) List() (currentMinions []string, err error) { func (r *HealthyRegistry) List() (currentMinions *api.MinionList, err error) {
var result []string result := &api.MinionList{}
list, err := r.delegate.List() list, err := r.delegate.List()
if err != nil { if err != nil {
return result, err return result, err
} }
for _, minion := range list { for _, minion := range list.Items {
status, err := health.DoHTTPCheck(r.makeMinionURL(minion), r.client) status, err := health.DoHTTPCheck(r.makeMinionURL(minion.ID), r.client)
if err != nil { if err != nil {
glog.Errorf("%s failed health check with error: %s", minion, err) glog.Errorf("%s failed health check with error: %s", minion, err)
continue continue
} }
if status == health.Healthy { if status == health.Healthy {
result = append(result, minion) result.Items = append(result.Items, minion)
} else { } else {
glog.Errorf("%s failed a health check, ignoring.", minion) glog.Errorf("%s failed a health check, ignoring.", minion)
} }

View File

@ -49,7 +49,7 @@ func TestBasicDelegation(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(list, mockMinionRegistry.Minions) { if !reflect.DeepEqual(list, &mockMinionRegistry.Minions) {
t.Errorf("Expected %v, Got %v", mockMinionRegistry.Minions, list) t.Errorf("Expected %v, Got %v", mockMinionRegistry.Minions, list)
} }
err = healthy.Insert("foo") err = healthy.Insert("foo")
@ -96,7 +96,7 @@ func TestFiltering(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(list, expected) { if !reflect.DeepEqual(list, registrytest.MakeMinionList(expected)) {
t.Errorf("Expected %v, Got %v", expected, list) t.Errorf("Expected %v, Got %v", expected, list)
} }
ok, err := healthy.Contains("m1") ok, err := healthy.Contains("m1")

View File

@ -18,9 +18,9 @@ package minion
import ( import (
"fmt" "fmt"
"sort"
"sync" "sync"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
@ -28,16 +28,17 @@ var ErrDoesNotExist = fmt.Errorf("The requested resource does not exist.")
// Registry keeps track of a set of minions. Safe for concurrent reading/writing. // Registry keeps track of a set of minions. Safe for concurrent reading/writing.
type Registry interface { type Registry interface {
List() (currentMinions []string, err error) List() (currentMinions *api.MinionList, err error)
Insert(minion string) error Insert(minion string) error
Delete(minion string) error Delete(minion string) error
Contains(minion string) (bool, error) Contains(minion string) (bool, error)
} }
// NewRegistry initializes a minion registry with a list of minions. // NewRegistry initializes a minion registry with a list of minions.
func NewRegistry(minions []string) Registry { func NewRegistry(minions []string, nodeResources api.NodeResources) Registry {
m := &minionList{ m := &minionList{
minions: util.StringSet{}, minions: util.StringSet{},
nodeResources: nodeResources,
} }
for _, minion := range minions { for _, minion := range minions {
m.minions.Insert(minion) m.minions.Insert(minion)
@ -48,6 +49,7 @@ func NewRegistry(minions []string) Registry {
type minionList struct { type minionList struct {
minions util.StringSet minions util.StringSet
lock sync.Mutex lock sync.Mutex
nodeResources api.NodeResources
} }
func (m *minionList) Contains(minion string) (bool, error) { func (m *minionList) Contains(minion string) (bool, error) {
@ -70,12 +72,17 @@ func (m *minionList) Insert(newMinion string) error {
return nil return nil
} }
func (m *minionList) List() (currentMinions []string, err error) { func (m *minionList) List() (currentMinions *api.MinionList, err error) {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
minions := []api.Minion{}
for minion := range m.minions { for minion := range m.minions {
currentMinions = append(currentMinions, minion) minions = append(minions, api.Minion{
JSONBase: api.JSONBase{ID: minion},
NodeResources: m.nodeResources,
})
} }
sort.StringSlice(currentMinions).Sort() return &api.MinionList{
return Items: minions,
}, nil
} }

View File

@ -17,12 +17,13 @@ limitations under the License.
package minion package minion
import ( import (
"reflect"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
) )
func TestRegistry(t *testing.T) { func TestRegistry(t *testing.T) {
m := NewRegistry([]string{"foo", "bar"}) m := NewRegistry([]string{"foo", "bar"}, api.NodeResources{})
if has, err := m.Contains("foo"); !has || err != nil { if has, err := m.Contains("foo"); !has || err != nil {
t.Errorf("missing expected object") t.Errorf("missing expected object")
} }
@ -48,7 +49,16 @@ func TestRegistry(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("got error calling List") t.Errorf("got error calling List")
} }
if !reflect.DeepEqual(list, []string{"baz", "foo"}) { if len(list.Items) != 2 || !contains(list, "foo") || !contains(list, "baz") {
t.Errorf("Unexpected list value: %#v", list) t.Errorf("unexpected %v", list)
} }
} }
func contains(nodes *api.MinionList, nodeID string) bool {
for _, node := range nodes.Items {
if node.ID == nodeID {
return true
}
}
return false
}

View File

@ -87,15 +87,7 @@ func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
} }
func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
nameList, err := rs.registry.List() return rs.registry.List()
if err != nil {
return nil, err
}
var list api.MinionList
for _, name := range nameList {
list.Items = append(list.Items, *rs.toApiMinion(name))
}
return &list, nil
} }
func (*REST) New() runtime.Object { func (*REST) New() runtime.Object {

View File

@ -17,7 +17,6 @@ limitations under the License.
package minion package minion
import ( import (
"reflect"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -25,7 +24,7 @@ import (
) )
func TestMinionREST(t *testing.T) { func TestMinionREST(t *testing.T) {
m := NewRegistry([]string{"foo", "bar"}) m := NewRegistry([]string{"foo", "bar"}, api.NodeResources{})
ms := NewREST(m) ms := NewREST(m)
ctx := api.NewContext() ctx := api.NewContext()
if obj, err := ms.Get(ctx, "foo"); err != nil || obj.(*api.Minion).ID != "foo" { if obj, err := ms.Get(ctx, "foo"); err != nil || obj.(*api.Minion).ID != "foo" {
@ -78,7 +77,8 @@ func TestMinionREST(t *testing.T) {
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
}, },
} }
if !reflect.DeepEqual(list.(*api.MinionList).Items, expect) { nodeList := list.(*api.MinionList)
if len(expect) != len(nodeList.Items) || !contains(nodeList, "foo") || !contains(nodeList, "baz") {
t.Errorf("Unexpected list value: %#v", list) t.Errorf("Unexpected list value: %#v", list)
} }
} }

View File

@ -16,40 +16,54 @@ limitations under the License.
package registrytest package registrytest
import "sync" import (
"sync"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
type MinionRegistry struct { type MinionRegistry struct {
Err error Err error
Minion string Minion string
Minions []string Minions api.MinionList
sync.Mutex sync.Mutex
} }
func MakeMinionList(minions []string) *api.MinionList {
list := api.MinionList{
Items: make([]api.Minion, len(minions)),
}
for i := range minions {
list.Items[i].ID = minions[i]
}
return &list
}
func NewMinionRegistry(minions []string) *MinionRegistry { func NewMinionRegistry(minions []string) *MinionRegistry {
return &MinionRegistry{ return &MinionRegistry{
Minions: minions, Minions: *MakeMinionList(minions),
} }
} }
func (r *MinionRegistry) List() ([]string, error) { func (r *MinionRegistry) List() (*api.MinionList, error) {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
return r.Minions, r.Err return &r.Minions, r.Err
} }
func (r *MinionRegistry) Insert(minion string) error { func (r *MinionRegistry) Insert(minion string) error {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
r.Minion = minion r.Minion = minion
r.Minions = append(r.Minions, minion) r.Minions.Items = append(r.Minions.Items, api.Minion{JSONBase: api.JSONBase{ID: minion}})
return r.Err return r.Err
} }
func (r *MinionRegistry) Contains(minion string) (bool, error) { func (r *MinionRegistry) Contains(nodeID string) (bool, error) {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
for _, name := range r.Minions { for _, node := range r.Minions.Items {
if name == minion { if node.ID == nodeID {
return true, r.Err return true, r.Err
} }
} }
@ -59,12 +73,12 @@ func (r *MinionRegistry) Contains(minion string) (bool, error) {
func (r *MinionRegistry) Delete(minion string) error { func (r *MinionRegistry) Delete(minion string) error {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
var newList []string var newList []api.Minion
for _, name := range r.Minions { for _, node := range r.Minions.Items {
if name != minion { if node.ID != minion {
newList = append(newList, name) newList = append(newList, api.Minion{JSONBase: api.JSONBase{ID: minion}})
} }
} }
r.Minions = newList r.Minions.Items = newList
return r.Err return r.Err
} }

View File

@ -84,7 +84,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = balancer.CreateTCPLoadBalancer(srv.ID, zone.Region, srv.Port, hosts) err = balancer.CreateTCPLoadBalancer(srv.ID, zone.Region, srv.Port, hostsFromMinionList(hosts))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -97,6 +97,14 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
}), nil }), nil
} }
func hostsFromMinionList(list *api.MinionList) []string {
result := make([]string, len(list.Items))
for ix := range list.Items {
result[ix] = list.Items[ix].ID
}
return result
}
func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
service, err := rs.registry.GetService(ctx, id) service, err := rs.registry.GetService(ctx, id)
if err != nil { if err != nil {

View File

@ -34,7 +34,7 @@ func TestServiceRegistryCreate(t *testing.T) {
registry := registrytest.NewServiceRegistry() registry := registrytest.NewServiceRegistry()
fakeCloud := &cloud.FakeCloud{} fakeCloud := &cloud.FakeCloud{}
machines := []string{"foo", "bar", "baz"} machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines)) storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines, api.NodeResources{}))
svc := &api.Service{ svc := &api.Service{
Port: 6502, Port: 6502,
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
@ -156,7 +156,7 @@ func TestServiceRegistryExternalService(t *testing.T) {
registry := registrytest.NewServiceRegistry() registry := registrytest.NewServiceRegistry()
fakeCloud := &cloud.FakeCloud{} fakeCloud := &cloud.FakeCloud{}
machines := []string{"foo", "bar", "baz"} machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines)) storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines, api.NodeResources{}))
svc := &api.Service{ svc := &api.Service{
Port: 6502, Port: 6502,
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
@ -183,7 +183,7 @@ func TestServiceRegistryExternalServiceError(t *testing.T) {
Err: fmt.Errorf("test error"), Err: fmt.Errorf("test error"),
} }
machines := []string{"foo", "bar", "baz"} machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines)) storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines, api.NodeResources{}))
svc := &api.Service{ svc := &api.Service{
Port: 6502, Port: 6502,
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
@ -206,7 +206,7 @@ func TestServiceRegistryDelete(t *testing.T) {
registry := registrytest.NewServiceRegistry() registry := registrytest.NewServiceRegistry()
fakeCloud := &cloud.FakeCloud{} fakeCloud := &cloud.FakeCloud{}
machines := []string{"foo", "bar", "baz"} machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines)) storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines, api.NodeResources{}))
svc := &api.Service{ svc := &api.Service{
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
@ -227,7 +227,7 @@ func TestServiceRegistryDeleteExternal(t *testing.T) {
registry := registrytest.NewServiceRegistry() registry := registrytest.NewServiceRegistry()
fakeCloud := &cloud.FakeCloud{} fakeCloud := &cloud.FakeCloud{}
machines := []string{"foo", "bar", "baz"} machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines)) storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines, api.NodeResources{}))
svc := &api.Service{ svc := &api.Service{
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
@ -314,7 +314,7 @@ func TestServiceRegistryGet(t *testing.T) {
registry := registrytest.NewServiceRegistry() registry := registrytest.NewServiceRegistry()
fakeCloud := &cloud.FakeCloud{} fakeCloud := &cloud.FakeCloud{}
machines := []string{"foo", "bar", "baz"} machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines)) storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines, api.NodeResources{}))
registry.CreateService(ctx, &api.Service{ registry.CreateService(ctx, &api.Service{
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
@ -334,7 +334,7 @@ func TestServiceRegistryResourceLocation(t *testing.T) {
registry.Endpoints = api.Endpoints{Endpoints: []string{"foo:80"}} registry.Endpoints = api.Endpoints{Endpoints: []string{"foo:80"}}
fakeCloud := &cloud.FakeCloud{} fakeCloud := &cloud.FakeCloud{}
machines := []string{"foo", "bar", "baz"} machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines)) storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines, api.NodeResources{}))
registry.CreateService(ctx, &api.Service{ registry.CreateService(ctx, &api.Service{
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
@ -363,7 +363,7 @@ func TestServiceRegistryList(t *testing.T) {
registry := registrytest.NewServiceRegistry() registry := registrytest.NewServiceRegistry()
fakeCloud := &cloud.FakeCloud{} fakeCloud := &cloud.FakeCloud{}
machines := []string{"foo", "bar", "baz"} machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines)) storage := NewREST(registry, fakeCloud, minion.NewRegistry(machines, api.NodeResources{}))
registry.CreateService(ctx, &api.Service{ registry.CreateService(ctx, &api.Service{
JSONBase: api.JSONBase{ID: "foo"}, JSONBase: api.JSONBase{ID: "foo"},
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},

View File

@ -17,6 +17,8 @@ limitations under the License.
package scheduler package scheduler
import ( import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resources" "github.com/GoogleCloudPlatform/kubernetes/pkg/resources"
@ -24,7 +26,20 @@ import (
) )
type NodeInfo interface { type NodeInfo interface {
GetNodeInfo(nodeName string) (api.Minion, error) GetNodeInfo(nodeID string) (*api.Minion, error)
}
type StaticNodeInfo struct {
*api.MinionList
}
func (nodes StaticNodeInfo) GetNodeInfo(nodeID string) (*api.Minion, error) {
for ix := range nodes.Items {
if nodes.Items[ix].ID == nodeID {
return &nodes.Items[ix], nil
}
}
return nil, fmt.Errorf("failed to find node: %s", nodeID)
} }
type ResourceFit struct { type ResourceFit struct {
@ -75,6 +90,13 @@ func (r *ResourceFit) PodFitsResources(pod api.Pod, existingPods []api.Pod, node
return fitsCPU && fitsMemory, nil return fitsCPU && fitsMemory, nil
} }
func NewResourceFitPredicate(info NodeInfo) FitPredicate {
fit := &ResourceFit{
info: info,
}
return fit.PodFitsResources
}
func PodFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) { func PodFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
for _, scheduledPod := range existingPods { for _, scheduledPod := range existingPods {
for _, container := range pod.DesiredState.Manifest.Containers { for _, container := range pod.DesiredState.Manifest.Containers {

View File

@ -26,8 +26,9 @@ import (
type FakeNodeInfo api.Minion type FakeNodeInfo api.Minion
func (n FakeNodeInfo) GetNodeInfo(nodeName string) (api.Minion, error) { func (n FakeNodeInfo) GetNodeInfo(nodeName string) (*api.Minion, error) {
return api.Minion(n), nil node := api.Minion(n)
return &node, nil
} }
func makeResources(milliCPU int, memory int) api.NodeResources { func makeResources(milliCPU int, memory int) api.NodeResources {

View File

@ -62,9 +62,19 @@ func (factory *ConfigFactory) Create() *scheduler.Config {
} }
r := rand.New(rand.NewSource(time.Now().UnixNano())) r := rand.New(rand.NewSource(time.Now().UnixNano()))
nodes, err := factory.Client.ListMinions()
if err != nil {
glog.Errorf("failed to obtain minion information, aborting")
return nil
}
algo := algorithm.NewGenericScheduler( algo := algorithm.NewGenericScheduler(
[]algorithm.FitPredicate{
// Fit is defined based on the absence of port conflicts. // Fit is defined based on the absence of port conflicts.
[]algorithm.FitPredicate{algorithm.PodFitsPorts}, algorithm.PodFitsPorts,
// Fit is determined by resource availability
algorithm.NewResourceFitPredicate(algorithm.StaticNodeInfo{nodes}),
},
// All nodes where things fit are equally likely (Random) // All nodes where things fit are equally likely (Random)
algorithm.EqualPriority, algorithm.EqualPriority,
&storeToPodLister{podCache}, r) &storeToPodLister{podCache}, r)