From 8460e3913dcecda734fd69f414a5083b163a7228 Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Thu, 27 Aug 2015 10:17:41 -0700 Subject: [PATCH] move daemon controller to the experimental api --- pkg/api/helpers.go | 1 - pkg/api/latest/latest.go | 1 - pkg/api/register.go | 4 - pkg/api/testing/fuzzer.go | 2 +- pkg/api/types.go | 50 --- pkg/api/v1/defaults.go | 15 - pkg/api/v1/defaults_test.go | 58 --- pkg/api/v1/register.go | 4 - pkg/api/v1/types.go | 63 --- pkg/api/validation/validation.go | 66 --- pkg/api/validation/validation_test.go | 412 ----------------- pkg/client/unversioned/cache/listers.go | 13 +- pkg/client/unversioned/cache/listers_test.go | 31 +- pkg/client/unversioned/client.go | 5 - pkg/client/unversioned/daemon.go | 30 +- pkg/client/unversioned/daemon_test.go | 25 +- pkg/client/unversioned/experimental.go | 5 + .../unversioned/resource_quotas_test.go | 4 - .../unversioned/testclient/fake_daemons.go | 49 ++- pkg/expapi/register.go | 4 + pkg/expapi/types.go | 48 ++ pkg/expapi/v1/defaults.go | 18 +- pkg/expapi/v1/defaults_test.go | 104 +++++ pkg/expapi/v1/register.go | 4 + pkg/expapi/v1/types.go | 61 +++ pkg/expapi/validation/validation.go | 67 +++ pkg/expapi/validation/validation_test.go | 413 ++++++++++++++++++ pkg/registry/daemon/etcd/etcd.go | 7 +- pkg/registry/daemon/etcd/etcd_test.go | 47 +- pkg/registry/daemon/strategy.go | 21 +- 30 files changed, 840 insertions(+), 792 deletions(-) create mode 100644 pkg/expapi/v1/defaults_test.go diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index e79e6e202ed..af641fe911a 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -83,7 +83,6 @@ var standardResources = util.NewStringSet( string(ResourceQuotas), string(ResourceServices), string(ResourceReplicationControllers), - string(ResourceDaemon), string(ResourceSecrets), string(ResourcePersistentVolumeClaims), string(ResourceStorage)) diff --git a/pkg/api/latest/latest.go b/pkg/api/latest/latest.go index 29ace24796b..39bd0a319a9 100644 --- a/pkg/api/latest/latest.go +++ b/pkg/api/latest/latest.go @@ -95,7 +95,6 @@ func init() { "PodExecOptions", "PodAttachOptions", "PodProxyOptions", - "Daemon", "ThirdPartyResource", "ThirdPartyResourceData", "ThirdPartyResourceList") diff --git a/pkg/api/register.go b/pkg/api/register.go index 6a16cbacb9a..654de0868f6 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -32,8 +32,6 @@ func init() { &PodTemplateList{}, &ReplicationControllerList{}, &ReplicationController{}, - &DaemonList{}, - &Daemon{}, &ServiceList{}, &Service{}, &NodeList{}, @@ -85,8 +83,6 @@ func (*PodTemplate) IsAnAPIObject() {} func (*PodTemplateList) IsAnAPIObject() {} func (*ReplicationController) IsAnAPIObject() {} func (*ReplicationControllerList) IsAnAPIObject() {} -func (*Daemon) IsAnAPIObject() {} -func (*DaemonList) IsAnAPIObject() {} func (*Service) IsAnAPIObject() {} func (*ServiceList) IsAnAPIObject() {} func (*Endpoints) IsAnAPIObject() {} diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index 919f183427e..5bf8fd6d4e0 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -120,7 +120,7 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer { c.FuzzNoCustom(j) // fuzz self without calling this function again //j.TemplateRef = nil // this is required for round trip }, - func(j *api.DaemonSpec, c fuzz.Continue) { + func(j *expapi.DaemonSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again }, func(j *api.List, c fuzz.Continue) { diff --git a/pkg/api/types.go b/pkg/api/types.go index 9eee47f50c9..4b0afe6723f 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1058,54 +1058,6 @@ type ReplicationControllerList struct { Items []ReplicationController `json:"items"` } -// DaemonSpec is the specification of a daemon. -type DaemonSpec struct { - // Selector is a label query over pods that are managed by the daemon. - Selector map[string]string `json:"selector"` - - // Template is the object that describes the pod that will be created. - // The Daemon will create exactly one copy of this pod on every node - // that matches the template's node selector (or on every node if no node - // selector is specified). - Template *PodTemplateSpec `json:"template,omitempty"` -} - -// DaemonStatus represents the current status of a daemon. -type DaemonStatus struct { - // CurrentNumberScheduled is the number of nodes that are running exactly 1 copy of the - // daemon and are supposed to run the daemon. - CurrentNumberScheduled int `json:"currentNumberScheduled"` - - // NumberMisscheduled is the number of nodes that are running the daemon, but are - // not supposed to run the daemon. - NumberMisscheduled int `json:"numberMisscheduled"` - - // DesiredNumberScheduled is the total number of nodes that should be running the daemon - // (including nodes correctly running the daemon). - DesiredNumberScheduled int `json:"desiredNumberScheduled"` -} - -// Daemon represents the configuration of a daemon. -type Daemon struct { - TypeMeta `json:",inline"` - ObjectMeta `json:"metadata,omitempty"` - - // Spec defines the desired behavior of this daemon. - Spec DaemonSpec `json:"spec,omitempty"` - - // Status is the current status of this daemon. This data may be - // out of date by some window of time. - Status DaemonStatus `json:"status,omitempty"` -} - -// DaemonList is a collection of daemon. -type DaemonList struct { - TypeMeta `json:",inline"` - ListMeta `json:"metadata,omitempty"` - - Items []Daemon `json:"items"` -} - const ( // ClusterIPNone - do not assign a cluster IP // no proxying required and no environment variables should be created for pods @@ -1978,8 +1930,6 @@ const ( ResourceServices ResourceName = "services" // ReplicationControllers, number ResourceReplicationControllers ResourceName = "replicationcontrollers" - // Daemon, number - ResourceDaemon ResourceName = "daemon" // ResourceQuotas, number ResourceQuotas ResourceName = "resourcequotas" // ResourceSecrets, number diff --git a/pkg/api/v1/defaults.go b/pkg/api/v1/defaults.go index eee2a647c0c..3148b83ff09 100644 --- a/pkg/api/v1/defaults.go +++ b/pkg/api/v1/defaults.go @@ -44,21 +44,6 @@ func addDefaultingFuncs() { *obj.Spec.Replicas = 1 } }, - func(obj *Daemon) { - var labels map[string]string - if obj.Spec.Template != nil { - labels = obj.Spec.Template.Labels - } - // TODO: support templates defined elsewhere when we support them in the API - if labels != nil { - if len(obj.Spec.Selector) == 0 { - obj.Spec.Selector = labels - } - if len(obj.Labels) == 0 { - obj.Labels = labels - } - } - }, func(obj *Volume) { if util.AllPtrFieldsNil(&obj.VolumeSource) { obj.VolumeSource = VolumeSource{ diff --git a/pkg/api/v1/defaults_test.go b/pkg/api/v1/defaults_test.go index f417176c2de..008ca2c2ef3 100644 --- a/pkg/api/v1/defaults_test.go +++ b/pkg/api/v1/defaults_test.go @@ -155,64 +155,6 @@ func TestSetDefaultReplicationController(t *testing.T) { } } -func TestSetDefaultDaemon(t *testing.T) { - tests := []struct { - dc *versioned.Daemon - expectLabelsChange bool - }{ - { - dc: &versioned.Daemon{ - Spec: versioned.DaemonSpec{ - Template: &versioned.PodTemplateSpec{ - ObjectMeta: versioned.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectLabelsChange: true, - }, - { - dc: &versioned.Daemon{ - ObjectMeta: versioned.ObjectMeta{ - Labels: map[string]string{ - "bar": "foo", - }, - }, - Spec: versioned.DaemonSpec{ - Template: &versioned.PodTemplateSpec{ - ObjectMeta: versioned.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - }, - }, - }, - expectLabelsChange: false, - }, - } - - for _, test := range tests { - dc := test.dc - obj2 := roundTrip(t, runtime.Object(dc)) - dc2, ok := obj2.(*versioned.Daemon) - if !ok { - t.Errorf("unexpected object: %v", dc2) - t.FailNow() - } - if test.expectLabelsChange != reflect.DeepEqual(dc2.Labels, dc2.Spec.Template.Labels) { - if test.expectLabelsChange { - t.Errorf("expected: %v, got: %v", dc2.Spec.Template.Labels, dc2.Labels) - } else { - t.Errorf("unexpected equality: %v", dc.Labels) - } - } - } -} - func newInt(val int) *int { p := new(int) *p = val diff --git a/pkg/api/v1/register.go b/pkg/api/v1/register.go index 6dd94cf2d61..c5d705cd27c 100644 --- a/pkg/api/v1/register.go +++ b/pkg/api/v1/register.go @@ -47,8 +47,6 @@ func addKnownTypes() { &PodTemplateList{}, &ReplicationController{}, &ReplicationControllerList{}, - &DaemonList{}, - &Daemon{}, &Service{}, &ServiceList{}, &Endpoints{}, @@ -100,8 +98,6 @@ func (*PodTemplate) IsAnAPIObject() {} func (*PodTemplateList) IsAnAPIObject() {} func (*ReplicationController) IsAnAPIObject() {} func (*ReplicationControllerList) IsAnAPIObject() {} -func (*Daemon) IsAnAPIObject() {} -func (*DaemonList) IsAnAPIObject() {} func (*Service) IsAnAPIObject() {} func (*ServiceList) IsAnAPIObject() {} func (*Endpoints) IsAnAPIObject() {} diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index 45b100c87dc..c78a5da964a 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -1365,67 +1365,6 @@ type ReplicationControllerList struct { Items []ReplicationController `json:"items"` } -// DaemonSpec is the specification of a daemon. -type DaemonSpec struct { - // Selector is a label query over pods that are managed by the daemon. - // Must match in order to be controlled. - // If empty, defaulted to labels on Pod template. - // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors - Selector map[string]string `json:"selector,omitempty"` - - // Template is the object that describes the pod that will be created. - // The Daemon will create exactly one copy of this pod on every node - // that matches the template's node selector (or on every node if no node - // selector is specified). - // More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#pod-template - Template *PodTemplateSpec `json:"template,omitempty"` -} - -// DaemonStatus represents the current status of a daemon. -type DaemonStatus struct { - // CurrentNumberScheduled is the number of nodes that are running exactly 1 copy of the - // daemon and are supposed to run the daemon. - CurrentNumberScheduled int `json:"currentNumberScheduled"` - - // NumberMisscheduled is the number of nodes that are running the daemon, but are - // not supposed to run the daemon. - NumberMisscheduled int `json:"numberMisscheduled"` - - // DesiredNumberScheduled is the total number of nodes that should be running the daemon - // (including nodes correctly running the daemon). - DesiredNumberScheduled int `json:"desiredNumberScheduled"` -} - -// Daemon represents the configuration of a daemon. -type Daemon struct { - TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - ObjectMeta `json:"metadata,omitempty"` - - // Spec defines the specification of the desired behavior of this daemon. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status - Spec DaemonSpec `json:"spec,omitempty"` - - // Status is the current status of this daemon. This data may be - // out of date by some window of time. - // Populated by the system. - // Read-only. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status - Status DaemonStatus `json:"status,omitempty"` -} - -// DaemonList is a collection of daemon. -type DaemonList struct { - TypeMeta `json:",inline"` - // Standard list metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - ListMeta `json:"metadata,omitempty"` - - // Items is a list of daemons. - Items []Daemon `json:"items"` -} - // Session Affinity Type string type ServiceAffinity string @@ -2370,8 +2309,6 @@ const ( ResourceServices ResourceName = "services" // ReplicationControllers, number ResourceReplicationControllers ResourceName = "replicationcontrollers" - // Daemon, number - ResourceDaemon ResourceName = "daemon" // ResourceQuotas, number ResourceQuotas ResourceName = "resourcequotas" // ResourceSecrets, number diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index f87ceff2c54..44d06a0697e 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -112,13 +112,6 @@ func ValidateReplicationControllerName(name string, prefix bool) (bool, string) return NameIsDNSSubdomain(name, prefix) } -// ValidateDaemonName can be used to check whether the given daemon name is valid. -// Prefix indicates this name will be used as part of generation, in which case -// trailing dashes are allowed. -func ValidateDaemonName(name string, prefix bool) (bool, string) { - return NameIsDNSSubdomain(name, prefix) -} - // ValidateServiceName can be used to check whether the given service name is valid. // Prefix indicates this name will be used as part of generation, in which case // trailing dashes are allowed. @@ -1244,65 +1237,6 @@ func ValidateReplicationControllerSpec(spec *api.ReplicationControllerSpec) errs return allErrs } -// ValidateDaemon tests if required fields in the daemon are set. -func ValidateDaemon(controller *api.Daemon) errs.ValidationErrorList { - allErrs := errs.ValidationErrorList{} - allErrs = append(allErrs, ValidateObjectMeta(&controller.ObjectMeta, true, ValidateReplicationControllerName).Prefix("metadata")...) - allErrs = append(allErrs, ValidateDaemonSpec(&controller.Spec).Prefix("spec")...) - return allErrs -} - -// ValidateDaemonUpdate tests if required fields in the daemon are set. -func ValidateDaemonUpdate(oldController, controller *api.Daemon) errs.ValidationErrorList { - allErrs := errs.ValidationErrorList{} - allErrs = append(allErrs, ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...) - allErrs = append(allErrs, ValidateDaemonSpec(&controller.Spec).Prefix("spec")...) - allErrs = append(allErrs, ValidateDaemonTemplateUpdate(oldController.Spec.Template, controller.Spec.Template).Prefix("spec.template")...) - return allErrs -} - -// ValidateDaemonTemplateUpdate tests that certain fields in the daemon's pod template are not updated. -func ValidateDaemonTemplateUpdate(oldPodTemplate, podTemplate *api.PodTemplateSpec) errs.ValidationErrorList { - allErrs := errs.ValidationErrorList{} - podSpec := podTemplate.Spec - // podTemplate.Spec is not a pointer, so we can modify NodeSelector and NodeName directly. - podSpec.NodeSelector = oldPodTemplate.Spec.NodeSelector - podSpec.NodeName = oldPodTemplate.Spec.NodeName - // In particular, we do not allow updates to container images at this point. - if !api.Semantic.DeepEqual(oldPodTemplate.Spec, podSpec) { - // TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff - allErrs = append(allErrs, errs.NewFieldInvalid("spec", "content of spec is not printed out, please refer to the \"details\"", "may not update fields other than spec.nodeSelector")) - } - return allErrs -} - -// ValidateDaemonSpec tests if required fields in the daemon spec are set. -func ValidateDaemonSpec(spec *api.DaemonSpec) errs.ValidationErrorList { - allErrs := errs.ValidationErrorList{} - - selector := labels.Set(spec.Selector).AsSelector() - if selector.Empty() { - allErrs = append(allErrs, errs.NewFieldRequired("selector")) - } - - if spec.Template == nil { - allErrs = append(allErrs, errs.NewFieldRequired("template")) - } else { - labels := labels.Set(spec.Template.Labels) - if !selector.Matches(labels) { - allErrs = append(allErrs, errs.NewFieldInvalid("template.metadata.labels", spec.Template.Labels, "selector does not match template")) - } - allErrs = append(allErrs, ValidatePodTemplateSpec(spec.Template).Prefix("template")...) - // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid. - allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes).Prefix("template.spec.volumes")...) - // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). - if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { - allErrs = append(allErrs, errs.NewFieldValueNotSupported("template.spec.restartPolicy", spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) - } - } - return allErrs -} - // ValidatePodTemplateSpec validates the spec of a pod template func ValidatePodTemplateSpec(spec *api.PodTemplateSpec) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 75d4c099972..5d518cba3e8 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -2365,418 +2365,6 @@ func TestValidateReplicationController(t *testing.T) { } } -func TestValidateDaemonUpdate(t *testing.T) { - validSelector := map[string]string{"a": "b"} - validSelector2 := map[string]string{"c": "d"} - invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - - validPodSpecAbc := api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - } - validPodSpecDef := api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - } - validPodSpecNodeSelector := api.PodSpec{ - NodeSelector: validSelector, - NodeName: "xyz", - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - } - validPodSpecVolume := api.PodSpec{ - Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - } - - validPodTemplateAbc := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validSelector, - }, - Spec: validPodSpecAbc, - }, - } - validPodTemplateNodeSelector := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validSelector, - }, - Spec: validPodSpecNodeSelector, - }, - } - validPodTemplateAbc2 := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validSelector2, - }, - Spec: validPodSpecAbc, - }, - } - validPodTemplateDef := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validSelector2, - }, - Spec: validPodSpecDef, - }, - } - invalidPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - ObjectMeta: api.ObjectMeta{ - Labels: invalidSelector, - }, - }, - } - readWriteVolumePodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validSelector, - }, - Spec: validPodSpecVolume, - }, - } - - type dcUpdateTest struct { - old api.Daemon - update api.Daemon - } - successCases := []dcUpdateTest{ - { - old: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - update: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - }, - { - old: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - update: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector2, - Template: &validPodTemplateAbc2.Template, - }, - }, - }, - { - old: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - update: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateNodeSelector.Template, - }, - }, - }, - } - for _, successCase := range successCases { - successCase.old.ObjectMeta.ResourceVersion = "1" - successCase.update.ObjectMeta.ResourceVersion = "1" - if errs := ValidateDaemonUpdate(&successCase.old, &successCase.update); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - errorCases := map[string]dcUpdateTest{ - "change daemon name": { - old: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - update: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - }, - "invalid selector": { - old: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - update: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: invalidSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - }, - "invalid pod": { - old: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - update: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &invalidPodTemplate.Template, - }, - }, - }, - "change container image": { - old: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - update: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateDef.Template, - }, - }, - }, - "read-write volume": { - old: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplateAbc.Template, - }, - }, - update: api.Daemon{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &readWriteVolumePodTemplate.Template, - }, - }, - }, - } - for testName, errorCase := range errorCases { - if errs := ValidateDaemonUpdate(&errorCase.old, &errorCase.update); len(errs) == 0 { - t.Errorf("expected failure: %s", testName) - } - } -} - -func TestValidateDaemon(t *testing.T) { - validSelector := map[string]string{"a": "b"} - validPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validSelector, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - }, - } - invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} - invalidPodTemplate := api.PodTemplate{ - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - }, - ObjectMeta: api.ObjectMeta{ - Labels: invalidSelector, - }, - }, - } - successCases := []api.Daemon{ - { - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - { - ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - } - for _, successCase := range successCases { - if errs := ValidateDaemon(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := map[string]api.Daemon{ - "zero-length ID": { - ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - "missing-namespace": { - ObjectMeta: api.ObjectMeta{Name: "abc-123"}, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - "empty selector": { - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Template: &validPodTemplate.Template, - }, - }, - "selector_doesnt_match": { - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: map[string]string{"foo": "bar"}, - Template: &validPodTemplate.Template, - }, - }, - "invalid manifest": { - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Spec: api.DaemonSpec{ - Selector: validSelector, - }, - }, - "invalid_label": { - ObjectMeta: api.ObjectMeta{ - Name: "abc-123", - Namespace: api.NamespaceDefault, - Labels: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - "invalid_label 2": { - ObjectMeta: api.ObjectMeta{ - Name: "abc-123", - Namespace: api.NamespaceDefault, - Labels: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: api.DaemonSpec{ - Template: &invalidPodTemplate.Template, - }, - }, - "invalid_annotation": { - ObjectMeta: api.ObjectMeta{ - Name: "abc-123", - Namespace: api.NamespaceDefault, - Annotations: map[string]string{ - "NoUppercaseOrSpecialCharsLike=Equals": "bar", - }, - }, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &validPodTemplate.Template, - }, - }, - "invalid restart policy 1": { - ObjectMeta: api.ObjectMeta{ - Name: "abc-123", - Namespace: api.NamespaceDefault, - }, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - ObjectMeta: api.ObjectMeta{ - Labels: validSelector, - }, - }, - }, - }, - "invalid restart policy 2": { - ObjectMeta: api.ObjectMeta{ - Name: "abc-123", - Namespace: api.NamespaceDefault, - }, - Spec: api.DaemonSpec{ - Selector: validSelector, - Template: &api.PodTemplateSpec{ - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyNever, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - ObjectMeta: api.ObjectMeta{ - Labels: validSelector, - }, - }, - }, - }, - } - for k, v := range errorCases { - errs := ValidateDaemon(&v) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } - for i := range errs { - field := errs[i].(*errors.ValidationError).Field - if !strings.HasPrefix(field, "spec.template.") && - field != "metadata.name" && - field != "metadata.namespace" && - field != "spec.selector" && - field != "spec.template" && - field != "GCEPersistentDisk.ReadOnly" && - field != "spec.template.labels" && - field != "metadata.annotations" && - field != "metadata.labels" { - t.Errorf("%s: missing prefix for: %v", k, errs[i]) - } - } - } -} - func TestValidateNode(t *testing.T) { validSelector := map[string]string{"a": "b"} invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} diff --git a/pkg/client/unversioned/cache/listers.go b/pkg/client/unversioned/cache/listers.go index 9cdf07862a5..0f92daf59c0 100644 --- a/pkg/client/unversioned/cache/listers.go +++ b/pkg/client/unversioned/cache/listers.go @@ -21,6 +21,7 @@ import ( "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" "k8s.io/kubernetes/pkg/labels" ) @@ -231,7 +232,7 @@ type StoreToDaemonLister struct { } // Exists checks if the given dc exists in the store. -func (s *StoreToDaemonLister) Exists(daemon *api.Daemon) (bool, error) { +func (s *StoreToDaemonLister) Exists(daemon *expapi.Daemon) (bool, error) { _, exists, err := s.Store.Get(daemon) if err != nil { return false, err @@ -241,17 +242,17 @@ func (s *StoreToDaemonLister) Exists(daemon *api.Daemon) (bool, error) { // StoreToDaemonLister lists all daemons in the store. // TODO: converge on the interface in pkg/client -func (s *StoreToDaemonLister) List() (daemons []api.Daemon, err error) { +func (s *StoreToDaemonLister) List() (daemons []expapi.Daemon, err error) { for _, c := range s.Store.List() { - daemons = append(daemons, *(c.(*api.Daemon))) + daemons = append(daemons, *(c.(*expapi.Daemon))) } return daemons, nil } // GetPodDaemons returns a list of daemons managing a pod. Returns an error iff no matching daemons are found. -func (s *StoreToDaemonLister) GetPodDaemons(pod *api.Pod) (daemons []api.Daemon, err error) { +func (s *StoreToDaemonLister) GetPodDaemons(pod *api.Pod) (daemons []expapi.Daemon, err error) { var selector labels.Selector - var daemonController api.Daemon + var daemonController expapi.Daemon if len(pod.Labels) == 0 { err = fmt.Errorf("No daemons found for pod %v because it has no labels", pod.Name) @@ -259,7 +260,7 @@ func (s *StoreToDaemonLister) GetPodDaemons(pod *api.Pod) (daemons []api.Daemon, } for _, m := range s.Store.List() { - daemonController = *m.(*api.Daemon) + daemonController = *m.(*expapi.Daemon) if daemonController.Namespace != pod.Namespace { continue } diff --git a/pkg/client/unversioned/cache/listers_test.go b/pkg/client/unversioned/cache/listers_test.go index ecc99de6657..112b10416d2 100644 --- a/pkg/client/unversioned/cache/listers_test.go +++ b/pkg/client/unversioned/cache/listers_test.go @@ -20,6 +20,7 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/util" ) @@ -159,44 +160,44 @@ func TestStoreToDaemonLister(t *testing.T) { store := NewStore(MetaNamespaceKeyFunc) lister := StoreToDaemonLister{store} testCases := []struct { - inDCs []*api.Daemon - list func() ([]api.Daemon, error) + inDCs []*expapi.Daemon + list func() ([]expapi.Daemon, error) outDCNames util.StringSet expectErr bool }{ // Basic listing { - inDCs: []*api.Daemon{ + inDCs: []*expapi.Daemon{ {ObjectMeta: api.ObjectMeta{Name: "basic"}}, }, - list: func() ([]api.Daemon, error) { + list: func() ([]expapi.Daemon, error) { return lister.List() }, outDCNames: util.NewStringSet("basic"), }, // Listing multiple controllers { - inDCs: []*api.Daemon{ + inDCs: []*expapi.Daemon{ {ObjectMeta: api.ObjectMeta{Name: "basic"}}, {ObjectMeta: api.ObjectMeta{Name: "complex"}}, {ObjectMeta: api.ObjectMeta{Name: "complex2"}}, }, - list: func() ([]api.Daemon, error) { + list: func() ([]expapi.Daemon, error) { return lister.List() }, outDCNames: util.NewStringSet("basic", "complex", "complex2"), }, // No pod labels { - inDCs: []*api.Daemon{ + inDCs: []*expapi.Daemon{ { ObjectMeta: api.ObjectMeta{Name: "basic", Namespace: "ns"}, - Spec: api.DaemonSpec{ + Spec: expapi.DaemonSpec{ Selector: map[string]string{"foo": "baz"}, }, }, }, - list: func() ([]api.Daemon, error) { + list: func() ([]expapi.Daemon, error) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "pod1", Namespace: "ns"}, } @@ -207,12 +208,12 @@ func TestStoreToDaemonLister(t *testing.T) { }, // No RC selectors { - inDCs: []*api.Daemon{ + inDCs: []*expapi.Daemon{ { ObjectMeta: api.ObjectMeta{Name: "basic", Namespace: "ns"}, }, }, - list: func() ([]api.Daemon, error) { + list: func() ([]expapi.Daemon, error) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod1", @@ -227,21 +228,21 @@ func TestStoreToDaemonLister(t *testing.T) { }, // Matching labels to selectors and namespace { - inDCs: []*api.Daemon{ + inDCs: []*expapi.Daemon{ { ObjectMeta: api.ObjectMeta{Name: "foo"}, - Spec: api.DaemonSpec{ + Spec: expapi.DaemonSpec{ Selector: map[string]string{"foo": "bar"}, }, }, { ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"}, - Spec: api.DaemonSpec{ + Spec: expapi.DaemonSpec{ Selector: map[string]string{"foo": "bar"}, }, }, }, - list: func() ([]api.Daemon, error) { + list: func() ([]expapi.Daemon, error) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod1", diff --git a/pkg/client/unversioned/client.go b/pkg/client/unversioned/client.go index 5a6d23417ec..778ac013c78 100644 --- a/pkg/client/unversioned/client.go +++ b/pkg/client/unversioned/client.go @@ -33,7 +33,6 @@ type Interface interface { PodsNamespacer PodTemplatesNamespacer ReplicationControllersNamespacer - DaemonsNamespacer ServicesNamespacer EndpointsNamespacer VersionInterface @@ -53,10 +52,6 @@ func (c *Client) ReplicationControllers(namespace string) ReplicationControllerI return newReplicationControllers(c, namespace) } -func (c *Client) Daemons(namespace string) DaemonInterface { - return newDaemons(c, namespace) -} - func (c *Client) Nodes() NodeInterface { return newNodes(c) } diff --git a/pkg/client/unversioned/daemon.go b/pkg/client/unversioned/daemon.go index 052ba95a54a..3b99fcdd3b0 100644 --- a/pkg/client/unversioned/daemon.go +++ b/pkg/client/unversioned/daemon.go @@ -17,7 +17,7 @@ limitations under the License. package unversioned import ( - "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/watch" @@ -29,50 +29,50 @@ type DaemonsNamespacer interface { } type DaemonInterface interface { - List(selector labels.Selector) (*api.DaemonList, error) - Get(name string) (*api.Daemon, error) - Create(ctrl *api.Daemon) (*api.Daemon, error) - Update(ctrl *api.Daemon) (*api.Daemon, error) + List(selector labels.Selector) (*expapi.DaemonList, error) + Get(name string) (*expapi.Daemon, error) + Create(ctrl *expapi.Daemon) (*expapi.Daemon, error) + Update(ctrl *expapi.Daemon) (*expapi.Daemon, error) Delete(name string) error Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) } // daemons implements DaemonsNamespacer interface type daemons struct { - r *Client + r *ExperimentalClient ns string } -func newDaemons(c *Client, namespace string) *daemons { +func newDaemons(c *ExperimentalClient, namespace string) *daemons { return &daemons{c, namespace} } // Ensure statically that daemons implements DaemonInterface. var _ DaemonInterface = &daemons{} -func (c *daemons) List(selector labels.Selector) (result *api.DaemonList, err error) { - result = &api.DaemonList{} +func (c *daemons) List(selector labels.Selector) (result *expapi.DaemonList, err error) { + result = &expapi.DaemonList{} err = c.r.Get().Namespace(c.ns).Resource("daemons").LabelsSelectorParam(selector).Do().Into(result) return } // Get returns information about a particular daemon. -func (c *daemons) Get(name string) (result *api.Daemon, err error) { - result = &api.Daemon{} +func (c *daemons) Get(name string) (result *expapi.Daemon, err error) { + result = &expapi.Daemon{} err = c.r.Get().Namespace(c.ns).Resource("daemons").Name(name).Do().Into(result) return } // Create creates a new daemon. -func (c *daemons) Create(daemon *api.Daemon) (result *api.Daemon, err error) { - result = &api.Daemon{} +func (c *daemons) Create(daemon *expapi.Daemon) (result *expapi.Daemon, err error) { + result = &expapi.Daemon{} err = c.r.Post().Namespace(c.ns).Resource("daemons").Body(daemon).Do().Into(result) return } // Update updates an existing daemon. -func (c *daemons) Update(daemon *api.Daemon) (result *api.Daemon, err error) { - result = &api.Daemon{} +func (c *daemons) Update(daemon *expapi.Daemon) (result *expapi.Daemon, err error) { + result = &expapi.Daemon{} err = c.r.Put().Namespace(c.ns).Resource("daemons").Name(daemon.Name).Body(daemon).Do().Into(result) return } diff --git a/pkg/client/unversioned/daemon_test.go b/pkg/client/unversioned/daemon_test.go index 6e5ecbf38f0..1a30c1b3725 100644 --- a/pkg/client/unversioned/daemon_test.go +++ b/pkg/client/unversioned/daemon_test.go @@ -20,7 +20,8 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/expapi/testapi" "k8s.io/kubernetes/pkg/labels" ) @@ -36,8 +37,8 @@ func TestListDaemons(t *testing.T) { Path: testapi.ResourcePath(getDCResourceName(), ns, ""), }, Response: Response{StatusCode: 200, - Body: &api.DaemonList{ - Items: []api.Daemon{ + Body: &expapi.DaemonList{ + Items: []expapi.Daemon{ { ObjectMeta: api.ObjectMeta{ Name: "foo", @@ -46,7 +47,7 @@ func TestListDaemons(t *testing.T) { "name": "baz", }, }, - Spec: api.DaemonSpec{ + Spec: expapi.DaemonSpec{ Template: &api.PodTemplateSpec{}, }, }, @@ -65,7 +66,7 @@ func TestGetDaemon(t *testing.T) { Request: testRequest{Method: "GET", Path: testapi.ResourcePath(getDCResourceName(), ns, "foo"), Query: buildQueryValues(nil)}, Response: Response{ StatusCode: 200, - Body: &api.Daemon{ + Body: &expapi.Daemon{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{ @@ -73,7 +74,7 @@ func TestGetDaemon(t *testing.T) { "name": "baz", }, }, - Spec: api.DaemonSpec{ + Spec: expapi.DaemonSpec{ Template: &api.PodTemplateSpec{}, }, }, @@ -96,14 +97,14 @@ func TestGetDaemonWithNoName(t *testing.T) { func TestUpdateDaemon(t *testing.T) { ns := api.NamespaceDefault - requestController := &api.Daemon{ + requestController := &expapi.Daemon{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, } c := &testClient{ Request: testRequest{Method: "PUT", Path: testapi.ResourcePath(getDCResourceName(), ns, "foo"), Query: buildQueryValues(nil)}, Response: Response{ StatusCode: 200, - Body: &api.Daemon{ + Body: &expapi.Daemon{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{ @@ -111,7 +112,7 @@ func TestUpdateDaemon(t *testing.T) { "name": "baz", }, }, - Spec: api.DaemonSpec{ + Spec: expapi.DaemonSpec{ Template: &api.PodTemplateSpec{}, }, }, @@ -133,14 +134,14 @@ func TestDeleteDaemon(t *testing.T) { func TestCreateDaemon(t *testing.T) { ns := api.NamespaceDefault - requestController := &api.Daemon{ + requestController := &expapi.Daemon{ ObjectMeta: api.ObjectMeta{Name: "foo"}, } c := &testClient{ Request: testRequest{Method: "POST", Path: testapi.ResourcePath(getDCResourceName(), ns, ""), Body: requestController, Query: buildQueryValues(nil)}, Response: Response{ StatusCode: 200, - Body: &api.Daemon{ + Body: &expapi.Daemon{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{ @@ -148,7 +149,7 @@ func TestCreateDaemon(t *testing.T) { "name": "baz", }, }, - Spec: api.DaemonSpec{ + Spec: expapi.DaemonSpec{ Template: &api.PodTemplateSpec{}, }, }, diff --git a/pkg/client/unversioned/experimental.go b/pkg/client/unversioned/experimental.go index 512385dd983..d993e629258 100644 --- a/pkg/client/unversioned/experimental.go +++ b/pkg/client/unversioned/experimental.go @@ -34,6 +34,7 @@ type ExperimentalInterface interface { VersionInterface HorizontalPodAutoscalersNamespacer ScaleNamespacer + DaemonsNamespacer } // ExperimentalClient is used to interact with experimental Kubernetes features. @@ -80,6 +81,10 @@ func (c *ExperimentalClient) Scales(namespace string) ScaleInterface { return newScales(c, namespace) } +func (c *ExperimentalClient) Daemons(namespace string) DaemonInterface { + return newDaemons(c, namespace) +} + // NewExperimental creates a new ExperimentalClient for the given config. This client // provides access to experimental Kubernetes features. // Experimental features are not supported and may be changed or removed in diff --git a/pkg/client/unversioned/resource_quotas_test.go b/pkg/client/unversioned/resource_quotas_test.go index b527a48e969..72df0528e53 100644 --- a/pkg/client/unversioned/resource_quotas_test.go +++ b/pkg/client/unversioned/resource_quotas_test.go @@ -45,7 +45,6 @@ func TestResourceQuotaCreate(t *testing.T) { api.ResourcePods: resource.MustParse("10"), api.ResourceServices: resource.MustParse("10"), api.ResourceReplicationControllers: resource.MustParse("10"), - api.ResourceDaemon: resource.MustParse("10"), api.ResourceQuotas: resource.MustParse("10"), }, }, @@ -78,7 +77,6 @@ func TestResourceQuotaGet(t *testing.T) { api.ResourcePods: resource.MustParse("10"), api.ResourceServices: resource.MustParse("10"), api.ResourceReplicationControllers: resource.MustParse("10"), - api.ResourceDaemon: resource.MustParse("10"), api.ResourceQuotas: resource.MustParse("10"), }, }, @@ -135,7 +133,6 @@ func TestResourceQuotaUpdate(t *testing.T) { api.ResourcePods: resource.MustParse("10"), api.ResourceServices: resource.MustParse("10"), api.ResourceReplicationControllers: resource.MustParse("10"), - api.ResourceDaemon: resource.MustParse("10"), api.ResourceQuotas: resource.MustParse("10"), }, }, @@ -163,7 +160,6 @@ func TestResourceQuotaStatusUpdate(t *testing.T) { api.ResourcePods: resource.MustParse("10"), api.ResourceServices: resource.MustParse("10"), api.ResourceReplicationControllers: resource.MustParse("10"), - api.ResourceDaemon: resource.MustParse("10"), api.ResourceQuotas: resource.MustParse("10"), }, }, diff --git a/pkg/client/unversioned/testclient/fake_daemons.go b/pkg/client/unversioned/testclient/fake_daemons.go index f484b51e5c8..05716e0547d 100644 --- a/pkg/client/unversioned/testclient/fake_daemons.go +++ b/pkg/client/unversioned/testclient/fake_daemons.go @@ -17,8 +17,8 @@ limitations under the License. package testclient import ( - "k8s.io/kubernetes/pkg/api" kClientLib "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/expapi" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/watch" @@ -31,40 +31,43 @@ type FakeDaemons struct { Namespace string } -const ( - GetDaemonAction = "get-daemon" - UpdateDaemonAction = "update-daemon" - WatchDaemonAction = "watch-daemon" - DeleteDaemonAction = "delete-daemon" - ListDaemonAction = "list-daemons" - CreateDaemonAction = "create-daemon" -) - // Ensure statically that FakeDaemons implements DaemonInterface. var _ kClientLib.DaemonInterface = &FakeDaemons{} -func (c *FakeDaemons) Get(name string) (*api.Daemon, error) { - obj, err := c.Fake.Invokes(NewGetAction("daemons", c.Namespace, name), &api.Daemon{}) - return obj.(*api.Daemon), err +func (c *FakeDaemons) Get(name string) (*expapi.Daemon, error) { + obj, err := c.Fake.Invokes(NewGetAction("daemons", c.Namespace, name), &expapi.Daemon{}) + if obj == nil { + return nil, err + } + return obj.(*expapi.Daemon), err } -func (c *FakeDaemons) List(label labels.Selector) (*api.DaemonList, error) { - obj, err := c.Fake.Invokes(NewListAction("daemons", c.Namespace, label, nil), &api.DaemonList{}) - return obj.(*api.DaemonList), err +func (c *FakeDaemons) List(label labels.Selector) (*expapi.DaemonList, error) { + obj, err := c.Fake.Invokes(NewListAction("daemons", c.Namespace, label, nil), &expapi.DaemonList{}) + if obj == nil { + return nil, err + } + return obj.(*expapi.DaemonList), err } -func (c *FakeDaemons) Create(daemon *api.Daemon) (*api.Daemon, error) { - obj, err := c.Fake.Invokes(NewCreateAction("daemons", c.Namespace, daemon), &api.Daemon{}) - return obj.(*api.Daemon), err +func (c *FakeDaemons) Create(daemon *expapi.Daemon) (*expapi.Daemon, error) { + obj, err := c.Fake.Invokes(NewCreateAction("daemons", c.Namespace, daemon), &expapi.Daemon{}) + if obj == nil { + return nil, err + } + return obj.(*expapi.Daemon), err } -func (c *FakeDaemons) Update(daemon *api.Daemon) (*api.Daemon, error) { - obj, err := c.Fake.Invokes(NewUpdateAction("daemons", c.Namespace, daemon), &api.Daemon{}) - return obj.(*api.Daemon), err +func (c *FakeDaemons) Update(daemon *expapi.Daemon) (*expapi.Daemon, error) { + obj, err := c.Fake.Invokes(NewUpdateAction("daemons", c.Namespace, daemon), &expapi.Daemon{}) + if obj == nil { + return nil, err + } + return obj.(*expapi.Daemon), err } func (c *FakeDaemons) Delete(name string) error { - _, err := c.Fake.Invokes(NewDeleteAction("daemons", c.Namespace, name), &api.Daemon{}) + _, err := c.Fake.Invokes(NewDeleteAction("daemons", c.Namespace, name), &expapi.Daemon{}) return err } diff --git a/pkg/expapi/register.go b/pkg/expapi/register.go index fab01b04a5f..7c0997e40ad 100644 --- a/pkg/expapi/register.go +++ b/pkg/expapi/register.go @@ -36,6 +36,8 @@ func addKnownTypes() { &Scale{}, &ThirdPartyResource{}, &ThirdPartyResourceList{}, + &DaemonList{}, + &Daemon{}, ) } @@ -47,3 +49,5 @@ func (*ReplicationControllerDummy) IsAnAPIObject() {} func (*Scale) IsAnAPIObject() {} func (*ThirdPartyResource) IsAnAPIObject() {} func (*ThirdPartyResourceList) IsAnAPIObject() {} +func (*Daemon) IsAnAPIObject() {} +func (*DaemonList) IsAnAPIObject() {} diff --git a/pkg/expapi/types.go b/pkg/expapi/types.go index 911aff286c9..52375606558 100644 --- a/pkg/expapi/types.go +++ b/pkg/expapi/types.go @@ -271,3 +271,51 @@ type DeploymentList struct { Items []Deployment `json:"items"` } + +// DaemonSpec is the specification of a daemon. +type DaemonSpec struct { + // Selector is a label query over pods that are managed by the daemon. + Selector map[string]string `json:"selector"` + + // Template is the object that describes the pod that will be created. + // The Daemon will create exactly one copy of this pod on every node + // that matches the template's node selector (or on every node if no node + // selector is specified). + Template *api.PodTemplateSpec `json:"template,omitempty"` +} + +// DaemonStatus represents the current status of a daemon. +type DaemonStatus struct { + // CurrentNumberScheduled is the number of nodes that are running exactly 1 copy of the + // daemon and are supposed to run the daemon. + CurrentNumberScheduled int `json:"currentNumberScheduled"` + + // NumberMisscheduled is the number of nodes that are running the daemon, but are + // not supposed to run the daemon. + NumberMisscheduled int `json:"numberMisscheduled"` + + // DesiredNumberScheduled is the total number of nodes that should be running the daemon + // (including nodes correctly running the daemon). + DesiredNumberScheduled int `json:"desiredNumberScheduled"` +} + +// Daemon represents the configuration of a daemon. +type Daemon struct { + api.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired behavior of this daemon. + Spec DaemonSpec `json:"spec,omitempty"` + + // Status is the current status of this daemon. This data may be + // out of date by some window of time. + Status DaemonStatus `json:"status,omitempty"` +} + +// DaemonList is a collection of daemon. +type DaemonList struct { + api.TypeMeta `json:",inline"` + api.ListMeta `json:"metadata,omitempty"` + + Items []Daemon `json:"items"` +} diff --git a/pkg/expapi/v1/defaults.go b/pkg/expapi/v1/defaults.go index e1d02ef1a1d..f1869111d89 100644 --- a/pkg/expapi/v1/defaults.go +++ b/pkg/expapi/v1/defaults.go @@ -24,5 +24,21 @@ func addDefaultingFuncs() { if len(obj.APIGroup) == 0 { obj.APIGroup = "experimental" } - }) + }, + func(obj *Daemon) { + var labels map[string]string + if obj.Spec.Template != nil { + labels = obj.Spec.Template.Labels + } + // TODO: support templates defined elsewhere when we support them in the API + if labels != nil { + if len(obj.Spec.Selector) == 0 { + obj.Spec.Selector = labels + } + if len(obj.Labels) == 0 { + obj.Labels = labels + } + } + }, + ) } diff --git a/pkg/expapi/v1/defaults_test.go b/pkg/expapi/v1/defaults_test.go new file mode 100644 index 00000000000..104c57ca7fb --- /dev/null +++ b/pkg/expapi/v1/defaults_test.go @@ -0,0 +1,104 @@ +/* +Copyright 2015 The Kubernetes Authors 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 v1 + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/runtime" +) + +func TestSetDefaultDaemon(t *testing.T) { + tests := []struct { + dc *Daemon + expectLabelsChange bool + }{ + { + dc: &Daemon{ + Spec: DaemonSpec{ + Template: &v1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectLabelsChange: true, + }, + { + dc: &Daemon{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{ + "bar": "foo", + }, + }, + Spec: DaemonSpec{ + Template: &v1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + }, + }, + }, + expectLabelsChange: false, + }, + } + + for _, test := range tests { + dc := test.dc + obj2 := roundTrip(t, runtime.Object(dc)) + dc2, ok := obj2.(*Daemon) + if !ok { + t.Errorf("unexpected object: %v", dc2) + t.FailNow() + } + if test.expectLabelsChange != reflect.DeepEqual(dc2.Labels, dc2.Spec.Template.Labels) { + if test.expectLabelsChange { + t.Errorf("expected: %v, got: %v", dc2.Spec.Template.Labels, dc2.Labels) + } else { + t.Errorf("unexpected equality: %v", dc.Labels) + } + } + } +} + +func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { + data, err := v1.Codec.Encode(obj) + if err != nil { + t.Errorf("%v\n %#v", err, obj) + return nil + } + obj2, err := api.Codec.Decode(data) + if err != nil { + t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj) + return nil + } + obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) + err = api.Scheme.Convert(obj2, obj3) + if err != nil { + t.Errorf("%v\nSource: %#v", err, obj2) + return nil + } + return obj3 +} diff --git a/pkg/expapi/v1/register.go b/pkg/expapi/v1/register.go index d303a8f79b8..2beed0adc0c 100644 --- a/pkg/expapi/v1/register.go +++ b/pkg/expapi/v1/register.go @@ -40,6 +40,8 @@ func addKnownTypes() { &Scale{}, &ThirdPartyResource{}, &ThirdPartyResourceList{}, + &DaemonList{}, + &Daemon{}, ) } @@ -51,3 +53,5 @@ func (*ReplicationControllerDummy) IsAnAPIObject() {} func (*Scale) IsAnAPIObject() {} func (*ThirdPartyResource) IsAnAPIObject() {} func (*ThirdPartyResourceList) IsAnAPIObject() {} +func (*Daemon) IsAnAPIObject() {} +func (*DaemonList) IsAnAPIObject() {} diff --git a/pkg/expapi/v1/types.go b/pkg/expapi/v1/types.go index 29ad4829f3b..2a19aa868d5 100644 --- a/pkg/expapi/v1/types.go +++ b/pkg/expapi/v1/types.go @@ -257,3 +257,64 @@ type DeploymentList struct { Items []Deployment `json:"items" description:"list of deployments"` } + +// DaemonSpec is the specification of a daemon. +type DaemonSpec struct { + // Selector is a label query over pods that are managed by the daemon. + // Must match in order to be controlled. + // If empty, defaulted to labels on Pod template. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors + Selector map[string]string `json:"selector,omitempty"` + + // Template is the object that describes the pod that will be created. + // The Daemon will create exactly one copy of this pod on every node + // that matches the template's node selector (or on every node if no node + // selector is specified). + // More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#pod-template + Template *v1.PodTemplateSpec `json:"template,omitempty"` +} + +// DaemonStatus represents the current status of a daemon. +type DaemonStatus struct { + // CurrentNumberScheduled is the number of nodes that are running exactly 1 copy of the + // daemon and are supposed to run the daemon. + CurrentNumberScheduled int `json:"currentNumberScheduled"` + + // NumberMisscheduled is the number of nodes that are running the daemon, but are + // not supposed to run the daemon. + NumberMisscheduled int `json:"numberMisscheduled"` + + // DesiredNumberScheduled is the total number of nodes that should be running the daemon + // (including nodes correctly running the daemon). + DesiredNumberScheduled int `json:"desiredNumberScheduled"` +} + +// Daemon represents the configuration of a daemon. +type Daemon struct { + v1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + v1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the specification of the desired behavior of this daemon. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + Spec DaemonSpec `json:"spec,omitempty"` + + // Status is the current status of this daemon. This data may be + // out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + Status DaemonStatus `json:"status,omitempty"` +} + +// DaemonList is a collection of daemon. +type DaemonList struct { + v1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + v1.ListMeta `json:"metadata,omitempty"` + + // Items is a list of daemons. + Items []Daemon `json:"items"` +} diff --git a/pkg/expapi/validation/validation.go b/pkg/expapi/validation/validation.go index 6ae820750a2..7a402272ed7 100644 --- a/pkg/expapi/validation/validation.go +++ b/pkg/expapi/validation/validation.go @@ -20,6 +20,7 @@ import ( "k8s.io/kubernetes/pkg/api" apivalidation "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/util" errs "k8s.io/kubernetes/pkg/util/fielderrors" ) @@ -89,3 +90,69 @@ func ValidateThirdPartyResource(obj *expapi.ThirdPartyResource) errs.ValidationE } return allErrs } + +// ValidateDaemon tests if required fields in the daemon are set. +func ValidateDaemon(controller *expapi.Daemon) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&controller.ObjectMeta, true, apivalidation.ValidateReplicationControllerName).Prefix("metadata")...) + allErrs = append(allErrs, ValidateDaemonSpec(&controller.Spec).Prefix("spec")...) + return allErrs +} + +// ValidateDaemonUpdate tests if required fields in the daemon are set. +func ValidateDaemonUpdate(oldController, controller *expapi.Daemon) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...) + allErrs = append(allErrs, ValidateDaemonSpec(&controller.Spec).Prefix("spec")...) + allErrs = append(allErrs, ValidateDaemonTemplateUpdate(oldController.Spec.Template, controller.Spec.Template).Prefix("spec.template")...) + return allErrs +} + +// ValidateDaemonTemplateUpdate tests that certain fields in the daemon's pod template are not updated. +func ValidateDaemonTemplateUpdate(oldPodTemplate, podTemplate *api.PodTemplateSpec) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + podSpec := podTemplate.Spec + // podTemplate.Spec is not a pointer, so we can modify NodeSelector and NodeName directly. + podSpec.NodeSelector = oldPodTemplate.Spec.NodeSelector + podSpec.NodeName = oldPodTemplate.Spec.NodeName + // In particular, we do not allow updates to container images at this point. + if !api.Semantic.DeepEqual(oldPodTemplate.Spec, podSpec) { + // TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff + allErrs = append(allErrs, errs.NewFieldInvalid("spec", "content of spec is not printed out, please refer to the \"details\"", "may not update fields other than spec.nodeSelector")) + } + return allErrs +} + +// ValidateDaemonSpec tests if required fields in the daemon spec are set. +func ValidateDaemonSpec(spec *expapi.DaemonSpec) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + + selector := labels.Set(spec.Selector).AsSelector() + if selector.Empty() { + allErrs = append(allErrs, errs.NewFieldRequired("selector")) + } + + if spec.Template == nil { + allErrs = append(allErrs, errs.NewFieldRequired("template")) + } else { + labels := labels.Set(spec.Template.Labels) + if !selector.Matches(labels) { + allErrs = append(allErrs, errs.NewFieldInvalid("template.metadata.labels", spec.Template.Labels, "selector does not match template")) + } + allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(spec.Template).Prefix("template")...) + // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid. + allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes).Prefix("template.spec.volumes")...) + // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). + if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { + allErrs = append(allErrs, errs.NewFieldValueNotSupported("template.spec.restartPolicy", spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) + } + } + return allErrs +} + +// ValidateDaemonName can be used to check whether the given daemon name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +func ValidateDaemonName(name string, prefix bool) (bool, string) { + return apivalidation.NameIsDNSSubdomain(name, prefix) +} diff --git a/pkg/expapi/validation/validation_test.go b/pkg/expapi/validation/validation_test.go index 1de65ab74a5..082a84c0139 100644 --- a/pkg/expapi/validation/validation_test.go +++ b/pkg/expapi/validation/validation_test.go @@ -23,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/expapi" + errors "k8s.io/kubernetes/pkg/util/fielderrors" ) func TestValidateHorizontalPodAutoscaler(t *testing.T) { @@ -127,3 +128,415 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { } } } + +func TestValidateDaemonUpdate(t *testing.T) { + validSelector := map[string]string{"a": "b"} + validSelector2 := map[string]string{"c": "d"} + invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + + validPodSpecAbc := api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + } + validPodSpecDef := api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + } + validPodSpecNodeSelector := api.PodSpec{ + NodeSelector: validSelector, + NodeName: "xyz", + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + } + validPodSpecVolume := api.PodSpec{ + Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + } + + validPodTemplateAbc := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + Spec: validPodSpecAbc, + }, + } + validPodTemplateNodeSelector := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + Spec: validPodSpecNodeSelector, + }, + } + validPodTemplateAbc2 := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector2, + }, + Spec: validPodSpecAbc, + }, + } + validPodTemplateDef := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector2, + }, + Spec: validPodSpecDef, + }, + } + invalidPodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + ObjectMeta: api.ObjectMeta{ + Labels: invalidSelector, + }, + }, + } + readWriteVolumePodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + Spec: validPodSpecVolume, + }, + } + + type dcUpdateTest struct { + old expapi.Daemon + update expapi.Daemon + } + successCases := []dcUpdateTest{ + { + old: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + update: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + }, + { + old: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + update: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector2, + Template: &validPodTemplateAbc2.Template, + }, + }, + }, + { + old: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + update: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateNodeSelector.Template, + }, + }, + }, + } + for _, successCase := range successCases { + successCase.old.ObjectMeta.ResourceVersion = "1" + successCase.update.ObjectMeta.ResourceVersion = "1" + if errs := ValidateDaemonUpdate(&successCase.old, &successCase.update); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + errorCases := map[string]dcUpdateTest{ + "change daemon name": { + old: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + update: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + }, + "invalid selector": { + old: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + update: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: invalidSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + }, + "invalid pod": { + old: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + update: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &invalidPodTemplate.Template, + }, + }, + }, + "change container image": { + old: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + update: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateDef.Template, + }, + }, + }, + "read-write volume": { + old: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplateAbc.Template, + }, + }, + update: expapi.Daemon{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &readWriteVolumePodTemplate.Template, + }, + }, + }, + } + for testName, errorCase := range errorCases { + if errs := ValidateDaemonUpdate(&errorCase.old, &errorCase.update); len(errs) == 0 { + t.Errorf("expected failure: %s", testName) + } + } +} + +func TestValidateDaemon(t *testing.T) { + validSelector := map[string]string{"a": "b"} + validPodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + } + invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + invalidPodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + ObjectMeta: api.ObjectMeta{ + Labels: invalidSelector, + }, + }, + } + successCases := []expapi.Daemon{ + { + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + } + for _, successCase := range successCases { + if errs := ValidateDaemon(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]expapi.Daemon{ + "zero-length ID": { + ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + "missing-namespace": { + ObjectMeta: api.ObjectMeta{Name: "abc-123"}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + "empty selector": { + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Template: &validPodTemplate.Template, + }, + }, + "selector_doesnt_match": { + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: map[string]string{"foo": "bar"}, + Template: &validPodTemplate.Template, + }, + }, + "invalid manifest": { + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + }, + }, + "invalid_label": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Namespace: api.NamespaceDefault, + Labels: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + "invalid_label 2": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Namespace: api.NamespaceDefault, + Labels: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: expapi.DaemonSpec{ + Template: &invalidPodTemplate.Template, + }, + }, + "invalid_annotation": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Namespace: api.NamespaceDefault, + Annotations: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + }, + "invalid restart policy 1": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Namespace: api.NamespaceDefault, + }, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &api.PodTemplateSpec{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + }, + }, + }, + "invalid restart policy 2": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Namespace: api.NamespaceDefault, + }, + Spec: expapi.DaemonSpec{ + Selector: validSelector, + Template: &api.PodTemplateSpec{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyNever, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + }, + }, + }, + } + for k, v := range errorCases { + errs := ValidateDaemon(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } + for i := range errs { + field := errs[i].(*errors.ValidationError).Field + if !strings.HasPrefix(field, "spec.template.") && + field != "metadata.name" && + field != "metadata.namespace" && + field != "spec.selector" && + field != "spec.template" && + field != "GCEPersistentDisk.ReadOnly" && + field != "spec.template.labels" && + field != "metadata.annotations" && + field != "metadata.labels" { + t.Errorf("%s: missing prefix for: %v", k, errs[i]) + } + } + } +} diff --git a/pkg/registry/daemon/etcd/etcd.go b/pkg/registry/daemon/etcd/etcd.go index 1887be2e623..1248480b498 100644 --- a/pkg/registry/daemon/etcd/etcd.go +++ b/pkg/registry/daemon/etcd/etcd.go @@ -18,6 +18,7 @@ package etcd import ( "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/registry/daemon" @@ -39,10 +40,10 @@ var daemonPrefix = "/daemons" // NewREST returns a RESTStorage object that will work against daemons. func NewREST(s storage.Interface) *REST { store := &etcdgeneric.Etcd{ - NewFunc: func() runtime.Object { return &api.Daemon{} }, + NewFunc: func() runtime.Object { return &expapi.Daemon{} }, // NewListFunc returns an object capable of storing results of an etcd list. - NewListFunc: func() runtime.Object { return &api.DaemonList{} }, + NewListFunc: func() runtime.Object { return &expapi.DaemonList{} }, // Produces a path that etcd understands, to the root of the resource // by combining the namespace in the context with the given prefix KeyRootFunc: func(ctx api.Context) string { @@ -55,7 +56,7 @@ func NewREST(s storage.Interface) *REST { }, // Retrieve the name field of a daemon ObjectNameFunc: func(obj runtime.Object) (string, error) { - return obj.(*api.Daemon).Name, nil + return obj.(*expapi.Daemon).Name, nil }, // Used to match objects based on labels/fields for list and watch PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { diff --git a/pkg/registry/daemon/etcd/etcd_test.go b/pkg/registry/daemon/etcd/etcd_test.go index 6c411d33930..85ae8dec8ac 100755 --- a/pkg/registry/daemon/etcd/etcd_test.go +++ b/pkg/registry/daemon/etcd/etcd_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/latest" "k8s.io/kubernetes/pkg/api/rest/resttest" + "k8s.io/kubernetes/pkg/expapi" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" @@ -46,13 +47,13 @@ func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) { } // createController is a helper function that returns a controller with the updated resource version. -func createController(storage *REST, dc api.Daemon, t *testing.T) (api.Daemon, error) { +func createController(storage *REST, dc expapi.Daemon, t *testing.T) (expapi.Daemon, error) { ctx := api.WithNamespace(api.NewContext(), dc.Namespace) obj, err := storage.Create(ctx, &dc) if err != nil { t.Errorf("Failed to create controller, %v", err) } - newDc := obj.(*api.Daemon) + newDc := obj.(*expapi.Daemon) return *newDc, nil } @@ -75,12 +76,12 @@ var validPodTemplate = api.PodTemplate{ }, } -var validControllerSpec = api.DaemonSpec{ +var validControllerSpec = expapi.DaemonSpec{ Selector: validPodTemplate.Template.Labels, Template: &validPodTemplate.Template, } -var validController = api.Daemon{ +var validController = expapi.Daemon{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "default"}, Spec: validControllerSpec, } @@ -90,8 +91,8 @@ func TestCreate(t *testing.T) { test := resttest.New(t, storage, fakeClient.SetError) test.TestCreate( // valid - &api.Daemon{ - Spec: api.DaemonSpec{ + &expapi.Daemon{ + Spec: expapi.DaemonSpec{ Selector: map[string]string{"a": "b"}, Template: &validPodTemplate.Template, }, @@ -103,8 +104,8 @@ func TestCreate(t *testing.T) { return registrytest.GetObject(fakeClient, storage.KeyFunc, storage.NewFunc, ctx, obj) }, // invalid - &api.Daemon{ - Spec: api.DaemonSpec{ + &expapi.Daemon{ + Spec: expapi.DaemonSpec{ Selector: map[string]string{}, Template: &validPodTemplate.Template, }, @@ -133,7 +134,7 @@ func TestEtcdGetController(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } - controller, ok := ctrl.(*api.Daemon) + controller, ok := ctrl.(*expapi.Daemon) if !ok { t.Errorf("Expected a controller, got %#v", ctrl) } @@ -151,20 +152,20 @@ func TestEtcdControllerValidatesUpdate(t *testing.T) { t.Errorf("Failed to create controller, cannot proceed with test.") } - updaters := []func(dc api.Daemon) (runtime.Object, bool, error){ - func(dc api.Daemon) (runtime.Object, bool, error) { + updaters := []func(dc expapi.Daemon) (runtime.Object, bool, error){ + func(dc expapi.Daemon) (runtime.Object, bool, error) { dc.UID = "newUID" return storage.Update(ctx, &dc) }, - func(dc api.Daemon) (runtime.Object, bool, error) { + func(dc expapi.Daemon) (runtime.Object, bool, error) { dc.Name = "" return storage.Update(ctx, &dc) }, - func(dc api.Daemon) (runtime.Object, bool, error) { + func(dc expapi.Daemon) (runtime.Object, bool, error) { dc.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure return storage.Update(ctx, &dc) }, - func(dc api.Daemon) (runtime.Object, bool, error) { + func(dc expapi.Daemon) (runtime.Object, bool, error) { dc.Spec.Selector = map[string]string{} return storage.Update(ctx, &dc) }, @@ -224,7 +225,7 @@ func TestEtcdGetControllerDifferentNamespace(t *testing.T) { fakeClient.Set(key2, runtime.EncodeOrDie(latest.Codec, &otherNsController), 0) obj, err := storage.Get(ctx1, validController.Name) - ctrl1, _ := obj.(*api.Daemon) + ctrl1, _ := obj.(*expapi.Daemon) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -236,7 +237,7 @@ func TestEtcdGetControllerDifferentNamespace(t *testing.T) { } obj, err = storage.Get(ctx2, validController.Name) - ctrl2, _ := obj.(*api.Daemon) + ctrl2, _ := obj.(*expapi.Daemon) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -320,7 +321,7 @@ func TestEtcdListControllers(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } - controllers, _ := objList.(*api.DaemonList) + controllers, _ := objList.(*expapi.DaemonList) if len(controllers.Items) != 2 || controllers.Items[0].Name != validController.Name || controllers.Items[1].Name != controller.Name { t.Errorf("Unexpected controller list: %#v", controllers) } @@ -340,7 +341,7 @@ func TestEtcdListControllersNotFound(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } - controllers, _ := objList.(*api.DaemonList) + controllers, _ := objList.(*expapi.DaemonList) if len(controllers.Items) != 0 { t.Errorf("Unexpected controller list: %#v", controllers) } @@ -376,7 +377,7 @@ func TestEtcdListControllersLabelsMatch(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } - controllers, _ := objList.(*api.DaemonList) + controllers, _ := objList.(*expapi.DaemonList) if len(controllers.Items) != 1 || controllers.Items[0].Name != controller.Name || !testLabels.Matches(labels.Set(controllers.Items[0].Labels)) { t.Errorf("Unexpected controller list: %#v for query with labels %#v", @@ -430,7 +431,7 @@ func TestEtcdWatchControllersMatch(t *testing.T) { // The watcher above is waiting for these Labels, on receiving them it should // apply the ControllerStatus decorator, which lists pods, causing a query against // the /registry/pods endpoint of the etcd client. - controller := &api.Daemon{ + controller := &expapi.Daemon{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: validController.Spec.Selector, @@ -476,13 +477,13 @@ func TestEtcdWatchControllersFields(t *testing.T) { etcdstorage.EtcdCAS, etcdstorage.EtcdDelete} - controller := &api.Daemon{ + controller := &expapi.Daemon{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: validController.Spec.Selector, Namespace: "default", }, - Status: api.DaemonStatus{ + Status: expapi.DaemonStatus{ CurrentNumberScheduled: 2, NumberMisscheduled: 1, DesiredNumberScheduled: 4, @@ -549,7 +550,7 @@ func TestEtcdWatchControllersNotMatch(t *testing.T) { } fakeClient.WaitForWatchCompletion() - controller := &api.Daemon{ + controller := &expapi.Daemon{ ObjectMeta: api.ObjectMeta{ Name: "bar", Labels: map[string]string{ diff --git a/pkg/registry/daemon/strategy.go b/pkg/registry/daemon/strategy.go index 7adea048c8e..2a9e7cb3796 100644 --- a/pkg/registry/daemon/strategy.go +++ b/pkg/registry/daemon/strategy.go @@ -21,7 +21,8 @@ import ( "reflect" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/expapi/validation" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/registry/generic" @@ -45,16 +46,16 @@ func (daemonStrategy) NamespaceScoped() bool { // PrepareForCreate clears the status of a daemon before creation. func (daemonStrategy) PrepareForCreate(obj runtime.Object) { - daemon := obj.(*api.Daemon) - daemon.Status = api.DaemonStatus{} + daemon := obj.(*expapi.Daemon) + daemon.Status = expapi.DaemonStatus{} daemon.Generation = 1 } // PrepareForUpdate clears fields that are not allowed to be set by end users on update. func (daemonStrategy) PrepareForUpdate(obj, old runtime.Object) { - newDaemon := obj.(*api.Daemon) - oldDaemon := old.(*api.Daemon) + newDaemon := obj.(*expapi.Daemon) + oldDaemon := old.(*expapi.Daemon) // Any changes to the spec increment the generation number, any changes to the // status should reflect the generation number of the corresponding object. We push @@ -74,7 +75,7 @@ func (daemonStrategy) PrepareForUpdate(obj, old runtime.Object) { // Validate validates a new daemon. func (daemonStrategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList { - daemon := obj.(*api.Daemon) + daemon := obj.(*expapi.Daemon) return validation.ValidateDaemon(daemon) } @@ -86,8 +87,8 @@ func (daemonStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (daemonStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList { - validationErrorList := validation.ValidateDaemon(obj.(*api.Daemon)) - updateErrorList := validation.ValidateDaemonUpdate(old.(*api.Daemon), obj.(*api.Daemon)) + validationErrorList := validation.ValidateDaemon(obj.(*expapi.Daemon)) + updateErrorList := validation.ValidateDaemonUpdate(old.(*expapi.Daemon), obj.(*expapi.Daemon)) return append(validationErrorList, updateErrorList...) } @@ -97,7 +98,7 @@ func (daemonStrategy) AllowUnconditionalUpdate() bool { } // DaemonToSelectableFields returns a field set that represents the object. -func DaemonToSelectableFields(daemon *api.Daemon) fields.Set { +func DaemonToSelectableFields(daemon *expapi.Daemon) fields.Set { return fields.Set{ "metadata.name": daemon.Name, } @@ -111,7 +112,7 @@ func MatchDaemon(label labels.Selector, field fields.Selector) generic.Matcher { Label: label, Field: field, GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { - daemon, ok := obj.(*api.Daemon) + daemon, ok := obj.(*expapi.Daemon) if !ok { return nil, nil, fmt.Errorf("given object is not a daemon.") }