diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index f4a476b4bce..84c8c790f81 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -37,6 +37,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" @@ -217,7 +218,11 @@ func startComponents(firstManifestURL, secondManifestURL, apiVersion string) (st // TODO: Write an integration test for the replication controllers watch. controllerManager.Run(1 * time.Second) - nodeResources := &api.NodeResources{} + nodeResources := &api.NodeResources{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }} nodeController := nodeControllerPkg.NewNodeController(nil, "", machineList, nodeResources, cl, fakeKubeletClient{}, record.FromSource(api.EventSource{Component: "controllermanager"}), 10, 5*time.Minute) diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 75f13bd078e..a5eb689fc8e 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -909,6 +909,28 @@ func ValidateReadOnlyPersistentDisks(volumes []api.Volume) errs.ValidationErrorL func ValidateMinion(node *api.Node) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} allErrs = append(allErrs, ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName).Prefix("metadata")...) + // Capacity is required. Within capacity, memory and cpu resources are required. + if len(node.Spec.Capacity) == 0 { + allErrs = append(allErrs, errs.NewFieldRequired("spec.Capacity")) + } else { + if val, ok := node.Spec.Capacity[api.ResourceMemory]; !ok { + allErrs = append(allErrs, errs.NewFieldRequired("spec.Capacity[memory]")) + } else if val.Value() < 0 { + allErrs = append(allErrs, errs.NewFieldInvalid("spec.Capacity[memory]", val, "memory capacity cannot be negative")) + } + if val, ok := node.Spec.Capacity[api.ResourceCPU]; !ok { + allErrs = append(allErrs, errs.NewFieldRequired("spec.Capacity[cpu]")) + } else if val.Value() < 0 { + allErrs = append(allErrs, errs.NewFieldInvalid("spec.Capacity[cpu]", val, "cpu capacity cannot be negative")) + } + } + + // external ID is required. + if len(node.Spec.ExternalID) == 0 { + allErrs = append(allErrs, errs.NewFieldRequired("spec.ExternalID")) + } + + // TODO(rjnagal): Ignore PodCIDR till its completely implemented. return allErrs } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 2465029f911..b5d0e112564 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -1823,6 +1823,14 @@ func TestValidateMinion(t *testing.T) { {Type: api.NodeLegacyHostIP, Address: "something"}, }, }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + api.ResourceName("my.org/gpu"): resource.MustParse("10"), + }, + }, }, { ObjectMeta: api.ObjectMeta{ @@ -1833,6 +1841,13 @@ func TestValidateMinion(t *testing.T) { {Type: api.NodeLegacyHostIP, Address: "something"}, }, }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("0"), + }, + }, }, } for _, successCase := range successCases { @@ -1850,12 +1865,97 @@ func TestValidateMinion(t *testing.T) { Status: api.NodeStatus{ Addresses: []api.NodeAddress{}, }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, }, "invalid-labels": { ObjectMeta: api.ObjectMeta{ Name: "abc-123", Labels: invalidSelector, }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + }, + "missing-external-id": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Labels: validSelector, + }, + Spec: api.NodeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + }, + "missing-capacity": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Labels: validSelector, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + }, + }, + "missing-memory": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Labels: validSelector, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + }, + }, + }, + "missing-cpu": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Labels: validSelector, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + }, + "invalid-memory": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Labels: validSelector, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("-10G"), + }, + }, + }, + "invalid-cpu": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Labels: validSelector, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("-10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, }, } for k, v := range errorCases { @@ -1865,10 +1965,17 @@ func TestValidateMinion(t *testing.T) { } for i := range errs { field := errs[i].(*errors.ValidationError).Field - if field != "metadata.name" && - field != "metadata.labels" && - field != "metadata.annotations" && - field != "metadata.namespace" { + expectedFields := map[string]bool{ + "metadata.name": true, + "metadata.labels": true, + "metadata.annotations": true, + "metadata.namespace": true, + "spec.Capacity": true, + "spec.Capacity[memory]": true, + "spec.Capacity[cpu]": true, + "spec.ExternalID": true, + } + if expectedFields[field] == false { t.Errorf("%s: missing prefix for: %v", k, errs[i]) } } diff --git a/pkg/cloudprovider/controller/nodecontroller.go b/pkg/cloudprovider/controller/nodecontroller.go index 33edee36e67..e469b505a30 100644 --- a/pkg/cloudprovider/controller/nodecontroller.go +++ b/pkg/cloudprovider/controller/nodecontroller.go @@ -520,7 +520,9 @@ func (nc *NodeController) GetStaticNodesWithSpec() (*api.NodeList, error) { for _, nodeID := range nc.nodes { node := api.Node{ ObjectMeta: api.ObjectMeta{Name: nodeID}, - Spec: api.NodeSpec{Capacity: nc.staticResources.Capacity}, + Spec: api.NodeSpec{ + Capacity: nc.staticResources.Capacity, + ExternalID: nodeID}, } result.Items = append(result.Items, node) } diff --git a/pkg/cloudprovider/controller/nodecontroller_test.go b/pkg/cloudprovider/controller/nodecontroller_test.go index bf7d5430d46..8cac27f68cf 100644 --- a/pkg/cloudprovider/controller/nodecontroller_test.go +++ b/pkg/cloudprovider/controller/nodecontroller_test.go @@ -279,8 +279,14 @@ func TestCreateGetStaticNodesWithSpec(t *testing.T) { Items: []api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, - Spec: api.NodeSpec{}, - Status: api.NodeStatus{}, + Spec: api.NodeSpec{ + ExternalID: "node0", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Status: api.NodeStatus{}, }, }, }, @@ -291,21 +297,39 @@ func TestCreateGetStaticNodesWithSpec(t *testing.T) { Items: []api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, - Spec: api.NodeSpec{}, - Status: api.NodeStatus{}, + Spec: api.NodeSpec{ + ExternalID: "node0", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Status: api.NodeStatus{}, }, { ObjectMeta: api.ObjectMeta{Name: "node1"}, - Spec: api.NodeSpec{}, - Status: api.NodeStatus{}, + Spec: api.NodeSpec{ + ExternalID: "node1", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Status: api.NodeStatus{}, }, }, }, }, } + resources := api.NodeResources{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + } for _, item := range table { - nodeController := NewNodeController(nil, "", item.machines, &api.NodeResources{}, nil, nil, nil, 10, time.Minute) + nodeController := NewNodeController(nil, "", item.machines, &resources, nil, nil, nil, 10, time.Minute) nodes, err := nodeController.GetStaticNodesWithSpec() if err != nil { t.Errorf("unexpected error: %v", err) @@ -736,6 +760,13 @@ func TestSyncProbedNodeStatus(t *testing.T) { {Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}, }, }, + Spec: api.NodeSpec{ + ExternalID: "node0", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, }, { ObjectMeta: api.ObjectMeta{Name: "node1"}, @@ -760,6 +791,13 @@ func TestSyncProbedNodeStatus(t *testing.T) { {Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}, }, }, + Spec: api.NodeSpec{ + ExternalID: "node1", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, }, }, expectedRequestCount: 3, // List + 2xUpdate @@ -1282,6 +1320,13 @@ func TestMonitorNodeStatusUpdateStatus(t *testing.T) { }, }, }, + Spec: api.NodeSpec{ + ExternalID: "node0", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, }, }, Fake: client.Fake{ @@ -1306,6 +1351,13 @@ func TestMonitorNodeStatusUpdateStatus(t *testing.T) { }, }, }, + Spec: api.NodeSpec{ + ExternalID: "node0", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, }, }, }, @@ -1330,6 +1382,13 @@ func TestMonitorNodeStatusUpdateStatus(t *testing.T) { }, }, }, + Spec: api.NodeSpec{ + ExternalID: "node0", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, }, }, Fake: client.Fake{ @@ -1357,7 +1416,16 @@ func TestMonitorNodeStatusUpdateStatus(t *testing.T) { } func newNode(name string) *api.Node { - return &api.Node{ObjectMeta: api.ObjectMeta{Name: name}} + return &api.Node{ + ObjectMeta: api.ObjectMeta{Name: name}, + Spec: api.NodeSpec{ + ExternalID: name, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + } } func newPod(name, host string) *api.Pod { diff --git a/pkg/registry/minion/etcd/etcd_test.go b/pkg/registry/minion/etcd/etcd_test.go index eabf55781a6..9ea955acd99 100644 --- a/pkg/registry/minion/etcd/etcd_test.go +++ b/pkg/registry/minion/etcd/etcd_test.go @@ -24,6 +24,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest/resttest" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" @@ -61,6 +62,13 @@ func validNewNode() *api.Node { "name": "foo", }, }, + Spec: api.NodeSpec{ + ExternalID: "external", + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("0"), + }, + }, } } diff --git a/test/integration/auth_test.go b/test/integration/auth_test.go index f99ea98edbd..5019536d42f 100644 --- a/test/integration/auth_test.go +++ b/test/integration/auth_test.go @@ -133,6 +133,10 @@ var aMinion string = ` "kind": "Minion", "apiVersion": "v1beta1", "id": "a", + "resources": { + "capacity": { "memory": "10", "cpu": "10"} + }, + "externalID": "external", "hostIP": "10.10.10.10"%s } `