diff --git a/cmd/kube-apiserver/app/aggregator.go b/cmd/kube-apiserver/app/aggregator.go index 28bb181b71e..d9dc4868efd 100644 --- a/cmd/kube-apiserver/app/aggregator.go +++ b/cmd/kube-apiserver/app/aggregator.go @@ -205,6 +205,7 @@ var apiVersionPriorities = map[schema.GroupVersion]priority{ // to my knowledge, nothing below here collides {Group: "apps", Version: "v1beta1"}: {group: 17800, version: 1}, {Group: "apps", Version: "v1beta2"}: {group: 17800, version: 1}, + {Group: "apps", Version: "v1"}: {group: 17800, version: 15}, {Group: "authentication.k8s.io", Version: "v1"}: {group: 17700, version: 15}, {Group: "authentication.k8s.io", Version: "v1beta1"}: {group: 17700, version: 9}, {Group: "authorization.k8s.io", Version: "v1"}: {group: 17600, version: 15}, diff --git a/hack/lib/init.sh b/hack/lib/init.sh index f22a4aeef0d..3d3047eebc3 100755 --- a/hack/lib/init.sh +++ b/hack/lib/init.sh @@ -56,6 +56,7 @@ admissionregistration.k8s.io/v1alpha1 \ admission.k8s.io/v1alpha1 \ apps/v1beta1 \ apps/v1beta2 \ +apps/v1 \ authentication.k8s.io/v1 \ authentication.k8s.io/v1beta1 \ authorization.k8s.io/v1 \ diff --git a/hack/update-generated-protobuf-dockerized.sh b/hack/update-generated-protobuf-dockerized.sh index 1e740be1e2e..f78850cac9d 100755 --- a/hack/update-generated-protobuf-dockerized.sh +++ b/hack/update-generated-protobuf-dockerized.sh @@ -57,6 +57,7 @@ PACKAGES=( k8s.io/api/batch/v2alpha1 k8s.io/api/apps/v1beta1 k8s.io/api/apps/v1beta2 + k8s.io/api/apps/v1 k8s.io/api/authentication/v1 k8s.io/api/authentication/v1beta1 k8s.io/api/rbac/v1alpha1 diff --git a/pkg/api/defaulting_test.go b/pkg/api/defaulting_test.go index 36a2ff1b3f5..e887eaa48d8 100644 --- a/pkg/api/defaulting_test.go +++ b/pkg/api/defaulting_test.go @@ -99,6 +99,8 @@ func TestDefaulting(t *testing.T) { {Group: "extensions", Version: "v1beta1", Kind: "DaemonSetList"}: {}, {Group: "apps", Version: "v1beta2", Kind: "DaemonSet"}: {}, {Group: "apps", Version: "v1beta2", Kind: "DaemonSetList"}: {}, + {Group: "apps", Version: "v1", Kind: "DaemonSet"}: {}, + {Group: "apps", Version: "v1", Kind: "DaemonSetList"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "Deployment"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "DeploymentList"}: {}, {Group: "apps", Version: "v1beta1", Kind: "Deployment"}: {}, diff --git a/pkg/apis/apps/install/install.go b/pkg/apis/apps/install/install.go index 790e280641e..2e31da6d9ea 100644 --- a/pkg/apis/apps/install/install.go +++ b/pkg/apis/apps/install/install.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/kubernetes/pkg/apis/apps/v1" "k8s.io/kubernetes/pkg/apis/apps/v1beta1" "k8s.io/kubernetes/pkg/apis/apps/v1beta2" ) @@ -37,12 +38,13 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r if err := announced.NewGroupMetaFactory( &announced.GroupMetaFactoryArgs{ GroupName: apps.GroupName, - VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version, v1beta2.SchemeGroupVersion.Version}, + VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version, v1beta2.SchemeGroupVersion.Version, v1.SchemeGroupVersion.Version}, AddInternalObjectsToScheme: apps.AddToScheme, }, announced.VersionToSchemeFunc{ v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, v1beta2.SchemeGroupVersion.Version: v1beta2.AddToScheme, + v1.SchemeGroupVersion.Version: v1.AddToScheme, }, ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { panic(err) diff --git a/pkg/apis/apps/v1/conversion.go b/pkg/apis/apps/v1/conversion.go new file mode 100644 index 00000000000..2e171964736 --- /dev/null +++ b/pkg/apis/apps/v1/conversion.go @@ -0,0 +1,158 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 ( + "strconv" + + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func addConversionFuncs(scheme *runtime.Scheme) error { + // Add non-generated conversion functions to handle the *int32 -> int32 + // conversion. A pointer is useful in the versioned type so we can default + // it, but a plain int32 is more convenient in the internal type. These + // functions are the same as the autogenerated ones in every other way. + err := scheme.AddConversionFuncs( + Convert_extensions_RollingUpdateDaemonSet_To_v1_RollingUpdateDaemonSet, + Convert_v1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet, + Convert_extensions_DaemonSet_To_v1_DaemonSet, + Convert_v1_DaemonSet_To_extensions_DaemonSet, + Convert_extensions_DaemonSetSpec_To_v1_DaemonSetSpec, + Convert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec, + Convert_extensions_DaemonSetUpdateStrategy_To_v1_DaemonSetUpdateStrategy, + Convert_v1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy, + ) + if err != nil { + return err + } + return nil +} + +func Convert_extensions_RollingUpdateDaemonSet_To_v1_RollingUpdateDaemonSet(in *extensions.RollingUpdateDaemonSet, out *appsv1.RollingUpdateDaemonSet, s conversion.Scope) error { + if out.MaxUnavailable == nil { + out.MaxUnavailable = &intstr.IntOrString{} + } + if err := s.Convert(&in.MaxUnavailable, out.MaxUnavailable, 0); err != nil { + return err + } + return nil +} + +func Convert_v1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet(in *appsv1.RollingUpdateDaemonSet, out *extensions.RollingUpdateDaemonSet, s conversion.Scope) error { + if err := s.Convert(in.MaxUnavailable, &out.MaxUnavailable, 0); err != nil { + return err + } + return nil +} + +func Convert_extensions_DaemonSet_To_v1_DaemonSet(in *extensions.DaemonSet, out *appsv1.DaemonSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if out.Annotations == nil { + out.Annotations = make(map[string]string) + } + out.Annotations[appsv1.DeprecatedTemplateGeneration] = strconv.FormatInt(in.Spec.TemplateGeneration, 10) + if err := Convert_extensions_DaemonSetSpec_To_v1_DaemonSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := s.Convert(&in.Status, &out.Status, 0); err != nil { + return err + } + return nil +} + +func Convert_extensions_DaemonSetSpec_To_v1_DaemonSetSpec(in *extensions.DaemonSetSpec, out *appsv1.DaemonSetSpec, s conversion.Scope) error { + out.Selector = in.Selector + if err := k8s_api_v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + if err := Convert_extensions_DaemonSetUpdateStrategy_To_v1_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { + return err + } + out.MinReadySeconds = int32(in.MinReadySeconds) + if in.RevisionHistoryLimit != nil { + out.RevisionHistoryLimit = new(int32) + *out.RevisionHistoryLimit = *in.RevisionHistoryLimit + } else { + out.RevisionHistoryLimit = nil + } + return nil +} + +func Convert_extensions_DaemonSetUpdateStrategy_To_v1_DaemonSetUpdateStrategy(in *extensions.DaemonSetUpdateStrategy, out *appsv1.DaemonSetUpdateStrategy, s conversion.Scope) error { + out.Type = appsv1.DaemonSetUpdateStrategyType(in.Type) + if in.RollingUpdate != nil { + out.RollingUpdate = &appsv1.RollingUpdateDaemonSet{} + if err := Convert_extensions_RollingUpdateDaemonSet_To_v1_RollingUpdateDaemonSet(in.RollingUpdate, out.RollingUpdate, s); err != nil { + return err + } + } + return nil +} + +func Convert_v1_DaemonSet_To_extensions_DaemonSet(in *appsv1.DaemonSet, out *extensions.DaemonSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if value, ok := in.Annotations[appsv1.DeprecatedTemplateGeneration]; ok { + if value64, err := strconv.ParseInt(value, 10, 64); err != nil { + return err + } else { + out.Spec.TemplateGeneration = value64 + delete(out.Annotations, appsv1.DeprecatedTemplateGeneration) + } + } + if err := s.Convert(&in.Status, &out.Status, 0); err != nil { + return err + } + return nil +} + +func Convert_v1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *appsv1.DaemonSetSpec, out *extensions.DaemonSetSpec, s conversion.Scope) error { + out.Selector = in.Selector + if err := k8s_api_v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + if err := Convert_v1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, s); err != nil { + return err + } + if in.RevisionHistoryLimit != nil { + out.RevisionHistoryLimit = new(int32) + *out.RevisionHistoryLimit = *in.RevisionHistoryLimit + } else { + out.RevisionHistoryLimit = nil + } + out.MinReadySeconds = in.MinReadySeconds + return nil +} + +func Convert_v1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy(in *appsv1.DaemonSetUpdateStrategy, out *extensions.DaemonSetUpdateStrategy, s conversion.Scope) error { + out.Type = extensions.DaemonSetUpdateStrategyType(in.Type) + if in.RollingUpdate != nil { + out.RollingUpdate = &extensions.RollingUpdateDaemonSet{} + if err := Convert_v1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet(in.RollingUpdate, out.RollingUpdate, s); err != nil { + return err + } + } + return nil +} diff --git a/pkg/apis/apps/v1/conversion_test.go b/pkg/apis/apps/v1/conversion_test.go new file mode 100644 index 00000000000..ae12f74425c --- /dev/null +++ b/pkg/apis/apps/v1/conversion_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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_test + +import ( + "testing" + + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + + apiequality "k8s.io/apimachinery/pkg/api/equality" +) + +func TestV1RollingUpdateDaemonSetConversion(t *testing.T) { + intorstr := intstr.FromInt(1) + testcases := map[string]struct { + rollingUpdateDs1 *extensions.RollingUpdateDaemonSet + rollingUpdateDs2 *appsv1.RollingUpdateDaemonSet + }{ + "RollingUpdateDaemonSet Conversion 2": { + rollingUpdateDs1: &extensions.RollingUpdateDaemonSet{MaxUnavailable: intorstr}, + rollingUpdateDs2: &appsv1.RollingUpdateDaemonSet{MaxUnavailable: &intorstr}, + }, + } + + for k, tc := range testcases { + // extensions -> v1 + internal1 := &appsv1.RollingUpdateDaemonSet{} + if err := api.Scheme.Convert(tc.rollingUpdateDs1, internal1, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "from extensions to v1", err) + } + if !apiequality.Semantic.DeepEqual(internal1, tc.rollingUpdateDs2) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "from extensions to v1", tc.rollingUpdateDs2, internal1) + } + + // v1 -> extensions + internal2 := &extensions.RollingUpdateDaemonSet{} + if err := api.Scheme.Convert(tc.rollingUpdateDs2, internal2, nil); err != nil { + t.Errorf("%q - %q: unexpected error: %v", k, "from v1 to extensions", err) + } + if !apiequality.Semantic.DeepEqual(internal2, tc.rollingUpdateDs1) { + t.Errorf("%q - %q: expected\n\t%#v, got \n\t%#v", k, "from v1 to extensions", tc.rollingUpdateDs1, internal2) + } + } +} diff --git a/pkg/apis/apps/v1/defaults.go b/pkg/apis/apps/v1/defaults.go new file mode 100644 index 00000000000..6803d038515 --- /dev/null +++ b/pkg/apis/apps/v1/defaults.go @@ -0,0 +1,49 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 ( + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_DaemonSet(obj *appsv1.DaemonSet) { + updateStrategy := &obj.Spec.UpdateStrategy + if updateStrategy.Type == "" { + updateStrategy.Type = appsv1.RollingUpdateDaemonSetStrategyType + } + if updateStrategy.Type == appsv1.RollingUpdateDaemonSetStrategyType { + if updateStrategy.RollingUpdate == nil { + rollingUpdate := appsv1.RollingUpdateDaemonSet{} + updateStrategy.RollingUpdate = &rollingUpdate + } + if updateStrategy.RollingUpdate.MaxUnavailable == nil { + // Set default MaxUnavailable as 1 by default. + maxUnavailable := intstr.FromInt(1) + updateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable + } + } + if obj.Spec.RevisionHistoryLimit == nil { + obj.Spec.RevisionHistoryLimit = new(int32) + *obj.Spec.RevisionHistoryLimit = 10 + } +} diff --git a/pkg/apis/apps/v1/defaults_test.go b/pkg/apis/apps/v1/defaults_test.go new file mode 100644 index 00000000000..9da68b4f8e8 --- /dev/null +++ b/pkg/apis/apps/v1/defaults_test.go @@ -0,0 +1,193 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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_test + +import ( + "reflect" + "testing" + + appsv1 "k8s.io/api/apps/v1" + "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/kubernetes/pkg/api" + _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/apps/install" + . "k8s.io/kubernetes/pkg/apis/apps/v1" +) + +func TestSetDefaultDaemonSetSpec(t *testing.T) { + defaultLabels := map[string]string{"foo": "bar"} + maxUnavailable := intstr.FromInt(1) + period := int64(v1.DefaultTerminationGracePeriodSeconds) + defaultTemplate := v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, + SecurityContext: &v1.PodSecurityContext{}, + TerminationGracePeriodSeconds: &period, + SchedulerName: api.DefaultSchedulerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: defaultLabels, + }, + } + templateNoLabel := v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + DNSPolicy: v1.DNSClusterFirst, + RestartPolicy: v1.RestartPolicyAlways, + SecurityContext: &v1.PodSecurityContext{}, + TerminationGracePeriodSeconds: &period, + SchedulerName: api.DefaultSchedulerName, + }, + } + tests := []struct { + original *appsv1.DaemonSet + expected *appsv1.DaemonSet + }{ + { // Labels change/defaulting test. + original: &appsv1.DaemonSet{ + Spec: appsv1.DaemonSetSpec{ + Template: defaultTemplate, + }, + }, + expected: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Labels: defaultLabels, + }, + Spec: appsv1.DaemonSetSpec{ + Template: defaultTemplate, + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateDaemonSet{ + MaxUnavailable: &maxUnavailable, + }, + }, + RevisionHistoryLimit: newInt32(10), + }, + }, + }, + { // Labels change/defaulting test. + original: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "bar": "foo", + }, + }, + Spec: appsv1.DaemonSetSpec{ + Template: defaultTemplate, + RevisionHistoryLimit: newInt32(1), + }, + }, + expected: &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "bar": "foo", + }, + }, + Spec: appsv1.DaemonSetSpec{ + Template: defaultTemplate, + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateDaemonSet{ + MaxUnavailable: &maxUnavailable, + }, + }, + RevisionHistoryLimit: newInt32(1), + }, + }, + }, + { // OnDeleteDaemonSetStrategyType update strategy. + original: &appsv1.DaemonSet{ + Spec: appsv1.DaemonSetSpec{ + Template: templateNoLabel, + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.OnDeleteDaemonSetStrategyType, + }, + }, + }, + expected: &appsv1.DaemonSet{ + Spec: appsv1.DaemonSetSpec{ + Template: templateNoLabel, + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.OnDeleteDaemonSetStrategyType, + }, + RevisionHistoryLimit: newInt32(10), + }, + }, + }, + { // Custom unique label key. + original: &appsv1.DaemonSet{ + Spec: appsv1.DaemonSetSpec{}, + }, + expected: &appsv1.DaemonSet{ + Spec: appsv1.DaemonSetSpec{ + Template: templateNoLabel, + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateDaemonSet{ + MaxUnavailable: &maxUnavailable, + }, + }, + RevisionHistoryLimit: newInt32(10), + }, + }, + }, + } + + for i, test := range tests { + original := test.original + expected := test.expected + obj2 := roundTrip(t, runtime.Object(original)) + got, ok := obj2.(*appsv1.DaemonSet) + if !ok { + t.Errorf("(%d) unexpected object: %v", i, got) + t.FailNow() + } + if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) { + t.Errorf("(%d) got different than expected\ngot:\n\t%+v\nexpected:\n\t%+v", i, got.Spec, expected.Spec) + } + } +} + +func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { + data, err := runtime.Encode(api.Codecs.LegacyCodec(SchemeGroupVersion), obj) + if err != nil { + t.Errorf("%v\n %#v", err, obj) + return nil + } + obj2, err := runtime.Decode(api.Codecs.UniversalDecoder(), 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, nil) + if err != nil { + t.Errorf("%v\nSource: %#v", err, obj2) + return nil + } + return obj3 +} + +func newInt32(val int32) *int32 { + p := new(int32) + *p = val + return p +} diff --git a/pkg/apis/apps/v1/doc.go b/pkg/apis/apps/v1/doc.go new file mode 100644 index 00000000000..771f2829623 --- /dev/null +++ b/pkg/apis/apps/v1/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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. +*/ + +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/apps +// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/extensions +// +k8s:conversion-gen-external-types=../../../../vendor/k8s.io/api/apps/v1 +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/apps/v1 + +package v1 // import "k8s.io/kubernetes/pkg/apis/apps/v1" diff --git a/pkg/apis/apps/v1/register.go b/pkg/apis/apps/v1/register.go new file mode 100644 index 00000000000..e5567276c11 --- /dev/null +++ b/pkg/apis/apps/v1/register.go @@ -0,0 +1,45 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 ( + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "apps" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &appsv1.SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addDefaultingFuncs, addConversionFuncs) +} diff --git a/pkg/registry/apps/rest/storage_apps.go b/pkg/registry/apps/rest/storage_apps.go index c667c3048e9..ac2e3cb3d7a 100644 --- a/pkg/registry/apps/rest/storage_apps.go +++ b/pkg/registry/apps/rest/storage_apps.go @@ -17,6 +17,7 @@ limitations under the License. package rest import ( + appsapiv1 "k8s.io/api/apps/v1" appsapiv1beta1 "k8s.io/api/apps/v1beta1" appsapiv1beta2 "k8s.io/api/apps/v1beta2" "k8s.io/apiserver/pkg/registry/generic" @@ -47,6 +48,10 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag apiGroupInfo.VersionedResourcesStorageMap[appsapiv1beta2.SchemeGroupVersion.Version] = p.v1beta2Storage(apiResourceConfigSource, restOptionsGetter) apiGroupInfo.GroupMeta.GroupVersion = appsapiv1beta2.SchemeGroupVersion } + if apiResourceConfigSource.AnyResourcesForVersionEnabled(appsapiv1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[appsapiv1.SchemeGroupVersion.Version] = p.v1Storage(apiResourceConfigSource, restOptionsGetter) + apiGroupInfo.GroupMeta.GroupVersion = appsapiv1.SchemeGroupVersion + } return apiGroupInfo, true } @@ -109,6 +114,18 @@ func (p RESTStorageProvider) v1beta2Storage(apiResourceConfigSource serverstorag return storage } +func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { + version := appsapiv1.SchemeGroupVersion + + storage := map[string]rest.Storage{} + if apiResourceConfigSource.ResourceEnabled(version.WithResource("daemonsets")) { + daemonSetStorage, daemonSetStatusStorage := daemonsetstore.NewREST(restOptionsGetter) + storage["daemonsets"] = daemonSetStorage + storage["daemonsets/status"] = daemonSetStatusStorage + } + return storage +} + func (p RESTStorageProvider) GroupName() string { return apps.GroupName } diff --git a/staging/src/k8s.io/api/apps/v1/doc.go b/staging/src/k8s.io/api/apps/v1/doc.go new file mode 100644 index 00000000000..62eb80cee34 --- /dev/null +++ b/staging/src/k8s.io/api/apps/v1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +// +k8s:deepcopy-gen=package,register +// +k8s:openapi-gen=true + +package v1 // import "k8s.io/api/apps/v1" diff --git a/staging/src/k8s.io/api/apps/v1/register.go b/staging/src/k8s.io/api/apps/v1/register.go new file mode 100644 index 00000000000..586b53ef0e9 --- /dev/null +++ b/staging/src/k8s.io/api/apps/v1/register.go @@ -0,0 +1,52 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "apps" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &DaemonSet{}, + &DaemonSetList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/api/apps/v1/types.go b/staging/src/k8s.io/api/apps/v1/types.go new file mode 100644 index 00000000000..8f5b867729f --- /dev/null +++ b/staging/src/k8s.io/api/apps/v1/types.go @@ -0,0 +1,207 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +const ( + ControllerRevisionHashLabelKey = "controller-revision-hash" + DeprecatedTemplateGeneration = "deprecated.daemonset.template.generation" +) + +// DaemonSetUpdateStrategy is a struct used to control the update strategy for a DaemonSet. +type DaemonSetUpdateStrategy struct { + // Type of daemon set update. Can be "RollingUpdate" or "OnDelete". Default is RollingUpdate. + // +optional + Type DaemonSetUpdateStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type"` + + // Rolling update config params. Present only if type = "RollingUpdate". + //--- + // TODO: Update this to follow our convention for oneOf, whatever we decide it + // to be. Same as Deployment `strategy.rollingUpdate`. + // See https://github.com/kubernetes/kubernetes/issues/35345 + // +optional + RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"` +} + +type DaemonSetUpdateStrategyType string + +const ( + // Replace the old daemons by new ones using rolling update i.e replace them on each node one after the other. + RollingUpdateDaemonSetStrategyType DaemonSetUpdateStrategyType = "RollingUpdate" + + // Replace the old daemons only when it's killed + OnDeleteDaemonSetStrategyType DaemonSetUpdateStrategyType = "OnDelete" +) + +// Spec to control the desired behavior of daemon set rolling update. +type RollingUpdateDaemonSet struct { + // The maximum number of DaemonSet pods that can be unavailable during the + // update. Value can be an absolute number (ex: 5) or a percentage of total + // number of DaemonSet pods at the start of the update (ex: 10%). Absolute + // number is calculated from percentage by rounding up. + // This cannot be 0. + // Default value is 1. + // Example: when this is set to 30%, at most 30% of the total number of nodes + // that should be running the daemon pod (i.e. status.desiredNumberScheduled) + // can have their pods stopped for an update at any given + // time. The update starts by stopping at most 30% of those DaemonSet pods + // and then brings up new DaemonSet pods in their place. Once the new pods + // are available, it then proceeds onto other DaemonSet pods, thus ensuring + // that at least 70% of original number of DaemonSet pods are available at + // all times during the update. + // +optional + MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,1,opt,name=maxUnavailable"` +} + +// DaemonSetSpec is the specification of a daemon set. +type DaemonSetSpec struct { + // A label query over pods that are managed by the daemon set. + // Must match in order to be controlled. + // If empty, defaulted to labels on Pod template. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + // +optional + Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,1,opt,name=selector"` + + // An object that describes the pod that will be created. + // The DaemonSet 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: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,2,opt,name=template"` + + // An update strategy to replace existing DaemonSet pods with new pods. + // +optional + UpdateStrategy DaemonSetUpdateStrategy `json:"updateStrategy,omitempty" protobuf:"bytes,3,opt,name=updateStrategy"` + + // The minimum number of seconds for which a newly created DaemonSet pod should + // be ready without any of its container crashing, for it to be considered + // available. Defaults to 0 (pod will be considered available as soon as it + // is ready). + // +optional + MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` + + // The number of old history to retain to allow rollback. + // This is a pointer to distinguish between explicit zero and not specified. + // Defaults to 10. + // +optional + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"` +} + +// DaemonSetStatus represents the current status of a daemon set. +type DaemonSetStatus struct { + // The number of nodes that are running at least 1 + // daemon pod and are supposed to run the daemon pod. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ + CurrentNumberScheduled int32 `json:"currentNumberScheduled" protobuf:"varint,1,opt,name=currentNumberScheduled"` + + // The number of nodes that are running the daemon pod, but are + // not supposed to run the daemon pod. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ + NumberMisscheduled int32 `json:"numberMisscheduled" protobuf:"varint,2,opt,name=numberMisscheduled"` + + // The total number of nodes that should be running the daemon + // pod (including nodes correctly running the daemon pod). + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ + DesiredNumberScheduled int32 `json:"desiredNumberScheduled" protobuf:"varint,3,opt,name=desiredNumberScheduled"` + + // The number of nodes that should be running the daemon pod and have one + // or more of the daemon pod running and ready. + NumberReady int32 `json:"numberReady" protobuf:"varint,4,opt,name=numberReady"` + + // The most recent generation observed by the daemon set controller. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,5,opt,name=observedGeneration"` + + // The total number of nodes that are running updated daemon pod + // +optional + UpdatedNumberScheduled int32 `json:"updatedNumberScheduled,omitempty" protobuf:"varint,6,opt,name=updatedNumberScheduled"` + + // The number of nodes that should be running the + // daemon pod and have one or more of the daemon pod running and + // available (ready for at least spec.minReadySeconds) + // +optional + NumberAvailable int32 `json:"numberAvailable,omitempty" protobuf:"varint,7,opt,name=numberAvailable"` + + // The number of nodes that should be running the + // daemon pod and have none of the daemon pod running and available + // (ready for at least spec.minReadySeconds) + // +optional + NumberUnavailable int32 `json:"numberUnavailable,omitempty" protobuf:"varint,8,opt,name=numberUnavailable"` + + // Count of hash collisions for the DaemonSet. The DaemonSet controller + // uses this field as a collision avoidance mechanism when it needs to + // create the name for the newest ControllerRevision. + // +optional + CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DaemonSet represents the configuration of a daemon set. +type DaemonSet struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // The desired behavior of this daemon set. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Spec DaemonSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + + // The current status of this daemon set. This data may be + // out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Status DaemonSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +const ( + // DEPRECATED: DefaultDaemonSetUniqueLabelKey is used instead. + // DaemonSetTemplateGenerationKey is the key of the labels that is added + // to daemon set pods to distinguish between old and new pod templates + // during DaemonSet template update. + DaemonSetTemplateGenerationKey string = "pod-template-generation" + + // DefaultDaemonSetUniqueLabelKey is the default label key that is added + // to existing DaemonSet pods to distinguish between old and new + // DaemonSet pods during DaemonSet template updates. + DefaultDaemonSetUniqueLabelKey = ControllerRevisionHashLabelKey +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DaemonSetList is a collection of daemon sets. +type DaemonSetList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // A list of daemon sets. + Items []DaemonSet `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/test/integration/etcd/etcd_storage_path_test.go b/test/integration/etcd/etcd_storage_path_test.go index 537cf5eb4b8..1c79ec59b9c 100644 --- a/test/integration/etcd/etcd_storage_path_test.go +++ b/test/integration/etcd/etcd_storage_path_test.go @@ -174,6 +174,14 @@ var etcdStorageData = map[schema.GroupVersionResource]struct { }, // -- + // k8s.io/kubernetes/pkg/apis/apps/v1 + gvr("apps", "v1", "daemonsets"): { + stub: `{"metadata": {"name": "ds6"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`, + expectedEtcdPath: "/registry/daemonsets/etcdstoragepathtestnamespace/ds6", + expectedGVK: gvkP("extensions", "v1beta1", "DaemonSet"), + }, + // -- + // k8s.io/kubernetes/pkg/apis/autoscaling/v1 gvr("autoscaling", "v1", "horizontalpodautoscalers"): { stub: `{"metadata": {"name": "hpa2"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,