Add validation for node creation.

pull/6/head
Rohit Jnagal 2015-03-24 17:24:07 +00:00
parent f3ebe30605
commit 49ff04765b
7 changed files with 230 additions and 14 deletions

View File

@ -37,6 +37,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "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/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "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. // TODO: Write an integration test for the replication controllers watch.
controllerManager.Run(1 * time.Second) 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{}, nodeController := nodeControllerPkg.NewNodeController(nil, "", machineList, nodeResources, cl, fakeKubeletClient{},
record.FromSource(api.EventSource{Component: "controllermanager"}), 10, 5*time.Minute) record.FromSource(api.EventSource{Component: "controllermanager"}), 10, 5*time.Minute)

View File

@ -909,6 +909,28 @@ func ValidateReadOnlyPersistentDisks(volumes []api.Volume) errs.ValidationErrorL
func ValidateMinion(node *api.Node) errs.ValidationErrorList { func ValidateMinion(node *api.Node) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName).Prefix("metadata")...) 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 return allErrs
} }

View File

@ -1823,6 +1823,14 @@ func TestValidateMinion(t *testing.T) {
{Type: api.NodeLegacyHostIP, Address: "something"}, {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{ ObjectMeta: api.ObjectMeta{
@ -1833,6 +1841,13 @@ func TestValidateMinion(t *testing.T) {
{Type: api.NodeLegacyHostIP, Address: "something"}, {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 { for _, successCase := range successCases {
@ -1850,12 +1865,97 @@ func TestValidateMinion(t *testing.T) {
Status: api.NodeStatus{ Status: api.NodeStatus{
Addresses: []api.NodeAddress{}, 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": { "invalid-labels": {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "abc-123", Name: "abc-123",
Labels: invalidSelector, 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 { for k, v := range errorCases {
@ -1865,10 +1965,17 @@ func TestValidateMinion(t *testing.T) {
} }
for i := range errs { for i := range errs {
field := errs[i].(*errors.ValidationError).Field field := errs[i].(*errors.ValidationError).Field
if field != "metadata.name" && expectedFields := map[string]bool{
field != "metadata.labels" && "metadata.name": true,
field != "metadata.annotations" && "metadata.labels": true,
field != "metadata.namespace" { "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]) t.Errorf("%s: missing prefix for: %v", k, errs[i])
} }
} }

View File

@ -520,7 +520,9 @@ func (nc *NodeController) GetStaticNodesWithSpec() (*api.NodeList, error) {
for _, nodeID := range nc.nodes { for _, nodeID := range nc.nodes {
node := api.Node{ node := api.Node{
ObjectMeta: api.ObjectMeta{Name: nodeID}, 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) result.Items = append(result.Items, node)
} }

View File

@ -279,8 +279,14 @@ func TestCreateGetStaticNodesWithSpec(t *testing.T) {
Items: []api.Node{ Items: []api.Node{
{ {
ObjectMeta: api.ObjectMeta{Name: "node0"}, ObjectMeta: api.ObjectMeta{Name: "node0"},
Spec: api.NodeSpec{}, Spec: api.NodeSpec{
Status: api.NodeStatus{}, 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{ Items: []api.Node{
{ {
ObjectMeta: api.ObjectMeta{Name: "node0"}, ObjectMeta: api.ObjectMeta{Name: "node0"},
Spec: api.NodeSpec{}, Spec: api.NodeSpec{
Status: api.NodeStatus{}, 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"}, ObjectMeta: api.ObjectMeta{Name: "node1"},
Spec: api.NodeSpec{}, Spec: api.NodeSpec{
Status: api.NodeStatus{}, 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 { 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() nodes, err := nodeController.GetStaticNodesWithSpec()
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@ -736,6 +760,13 @@ func TestSyncProbedNodeStatus(t *testing.T) {
{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}, {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"}, ObjectMeta: api.ObjectMeta{Name: "node1"},
@ -760,6 +791,13 @@ func TestSyncProbedNodeStatus(t *testing.T) {
{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}, {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 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{ 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{ Fake: client.Fake{
@ -1357,7 +1416,16 @@ func TestMonitorNodeStatusUpdateStatus(t *testing.T) {
} }
func newNode(name string) *api.Node { 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 { func newPod(name, host string) *api.Pod {

View File

@ -24,6 +24,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "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/api/rest/resttest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
@ -61,6 +62,13 @@ func validNewNode() *api.Node {
"name": "foo", "name": "foo",
}, },
}, },
Spec: api.NodeSpec{
ExternalID: "external",
Capacity: api.ResourceList{
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
api.ResourceName(api.ResourceMemory): resource.MustParse("0"),
},
},
} }
} }

View File

@ -133,6 +133,10 @@ var aMinion string = `
"kind": "Minion", "kind": "Minion",
"apiVersion": "v1beta1", "apiVersion": "v1beta1",
"id": "a", "id": "a",
"resources": {
"capacity": { "memory": "10", "cpu": "10"}
},
"externalID": "external",
"hostIP": "10.10.10.10"%s "hostIP": "10.10.10.10"%s
} }
` `