Merge pull request #21966 from madhusudancs/scale-deployment-replicaset

Auto commit by PR queue bot
pull/6/head
k8s-merge-robot 2016-03-04 14:40:10 -08:00
commit a435537e27
9 changed files with 6791 additions and 5311 deletions

View File

@ -1654,6 +1654,168 @@
}
]
},
{
"path": "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/scale",
"description": "API at /apis/extensions/v1beta1",
"operations": [
{
"type": "v1.Scale",
"method": "GET",
"summary": "read scale of the specified Scale",
"nickname": "readNamespacedScaleScale",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "pretty",
"description": "If 'true', then the output is pretty printed.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "namespace",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Scale",
"required": true,
"allowMultiple": false
}
],
"responseMessages": [
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
}
],
"produces": [
"application/json",
"application/yaml"
],
"consumes": [
"*/*"
]
},
{
"type": "v1.Scale",
"method": "PUT",
"summary": "replace scale of the specified Scale",
"nickname": "replaceNamespacedScaleScale",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "pretty",
"description": "If 'true', then the output is pretty printed.",
"required": false,
"allowMultiple": false
},
{
"type": "v1.Scale",
"paramType": "body",
"name": "body",
"description": "",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "namespace",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Scale",
"required": true,
"allowMultiple": false
}
],
"responseMessages": [
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
}
],
"produces": [
"application/json",
"application/yaml"
],
"consumes": [
"*/*"
]
},
{
"type": "v1.Scale",
"method": "PATCH",
"summary": "partially update scale of the specified Scale",
"nickname": "patchNamespacedScaleScale",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "pretty",
"description": "If 'true', then the output is pretty printed.",
"required": false,
"allowMultiple": false
},
{
"type": "unversioned.Patch",
"paramType": "body",
"name": "body",
"description": "",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "namespace",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Scale",
"required": true,
"allowMultiple": false
}
],
"responseMessages": [
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
}
],
"produces": [
"application/json",
"application/yaml"
],
"consumes": [
"application/json-patch+json",
"application/merge-patch+json",
"application/strategic-merge-patch+json"
]
}
]
},
{
"path": "/apis/extensions/v1beta1/namespaces/{namespace}/deployments/{name}/status",
"description": "API at /apis/extensions/v1beta1",
@ -4954,6 +5116,168 @@
}
]
},
{
"path": "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/scale",
"description": "API at /apis/extensions/v1beta1",
"operations": [
{
"type": "v1.Scale",
"method": "GET",
"summary": "read scale of the specified Scale",
"nickname": "readNamespacedScaleScale",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "pretty",
"description": "If 'true', then the output is pretty printed.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "namespace",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Scale",
"required": true,
"allowMultiple": false
}
],
"responseMessages": [
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
}
],
"produces": [
"application/json",
"application/yaml"
],
"consumes": [
"*/*"
]
},
{
"type": "v1.Scale",
"method": "PUT",
"summary": "replace scale of the specified Scale",
"nickname": "replaceNamespacedScaleScale",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "pretty",
"description": "If 'true', then the output is pretty printed.",
"required": false,
"allowMultiple": false
},
{
"type": "v1.Scale",
"paramType": "body",
"name": "body",
"description": "",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "namespace",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Scale",
"required": true,
"allowMultiple": false
}
],
"responseMessages": [
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
}
],
"produces": [
"application/json",
"application/yaml"
],
"consumes": [
"*/*"
]
},
{
"type": "v1.Scale",
"method": "PATCH",
"summary": "partially update scale of the specified Scale",
"nickname": "patchNamespacedScaleScale",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "pretty",
"description": "If 'true', then the output is pretty printed.",
"required": false,
"allowMultiple": false
},
{
"type": "unversioned.Patch",
"paramType": "body",
"name": "body",
"description": "",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "namespace",
"description": "object name and auth scope, such as for teams and projects",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Scale",
"required": true,
"allowMultiple": false
}
],
"responseMessages": [
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
}
],
"produces": [
"application/json",
"application/yaml"
],
"consumes": [
"application/json-patch+json",
"application/merge-patch+json",
"application/strategic-merge-patch+json"
]
}
]
},
{
"path": "/apis/extensions/v1beta1/namespaces/{namespace}/replicasets/{name}/status",
"description": "API at /apis/extensions/v1beta1",
@ -6910,6 +7234,61 @@
}
}
},
"v1.Scale": {
"id": "v1.Scale",
"description": "Scale represents a scaling request for a resource.",
"properties": {
"kind": {
"type": "string",
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds"
},
"apiVersion": {
"type": "string",
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
},
"metadata": {
"$ref": "v1.ObjectMeta",
"description": "Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata."
},
"spec": {
"$ref": "v1.ScaleSpec",
"description": "defines the behavior of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status."
},
"status": {
"$ref": "v1.ScaleStatus",
"description": "current status of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. Read-only."
}
}
},
"v1.ScaleSpec": {
"id": "v1.ScaleSpec",
"description": "ScaleSpec describes the attributes of a scale subresource.",
"properties": {
"replicas": {
"type": "integer",
"format": "int32",
"description": "desired number of instances for the scaled object."
}
}
},
"v1.ScaleStatus": {
"id": "v1.ScaleStatus",
"description": "ScaleStatus represents the current status of a scale subresource.",
"required": [
"replicas"
],
"properties": {
"replicas": {
"type": "integer",
"format": "int32",
"description": "actual number of observed instances of the scaled object."
},
"selector": {
"type": "string",
"description": "label query over pods that should match the replicas count. This is same as the label selector but in the string format to avoid introspection by clients. The string will be in the same format as the query-param syntax. More info about label selectors: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"
}
}
},
"v1beta1.HorizontalPodAutoscalerList": {
"id": "v1beta1.HorizontalPodAutoscalerList",
"description": "list of horizontal pod autoscaler objects.",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +0,0 @@
/*
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 extensions
// TODO(madhusudancs): Fix this when Scale group issues are resolved (see issue #18528).
// import (
// "fmt"
// "k8s.io/kubernetes/pkg/api"
// "k8s.io/kubernetes/pkg/api/unversioned"
// )
// // ScaleFromDeployment returns a scale subresource for a deployment.
// func ScaleFromDeployment(deployment *Deployment) (*Scale, error) {
// selector, err := unversioned.LabelSelectorAsSelector(deployment.Spec.Selector)
// if err != nil {
// return nil, fmt.Errorf("invalid label selector: %v", err)
// }
// return &Scale{
// ObjectMeta: api.ObjectMeta{
// Name: deployment.Name,
// Namespace: deployment.Namespace,
// CreationTimestamp: deployment.CreationTimestamp,
// },
// Spec: ScaleSpec{
// Replicas: deployment.Spec.Replicas,
// },
// Status: ScaleStatus{
// Replicas: deployment.Status.Replicas,
// Selector: selector.String(),
// },
// }, nil
// }

View File

@ -248,6 +248,12 @@ func (m *Master) InstallAPIs(c *Config) {
ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs,
}
if autoscalingGroupVersion := (unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}); registered.IsEnabledVersion(autoscalingGroupVersion) {
apiGroupInfo.SubresourceGroupVersionKind = map[string]unversioned.GroupVersionKind{
"deployments/scale": autoscalingGroupVersion.WithKind("Scale"),
"replicasets/scale": autoscalingGroupVersion.WithKind("Scale"),
}
}
apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
extensionsGVForDiscovery := unversioned.GroupVersionForDiscovery{
@ -716,9 +722,11 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
deploymentStorage := deploymentetcd.NewStorage(restOptions("deployments"))
storage["deployments"] = deploymentStorage.Deployment
storage["deployments/status"] = deploymentStorage.Status
// TODO(madhusudancs): Install scale when Scale group issues are fixed (see issue #18528).
// storage["deployments/scale"] = deploymentStorage.Scale
storage["deployments/rollback"] = deploymentStorage.Rollback
if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) {
storage["deployments/scale"] = deploymentStorage.Scale
}
}
if isEnabled("jobs") {
m.constructJobResources(c, storage)
@ -736,6 +744,9 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets"))
storage["replicasets"] = replicaSetStorage.ReplicaSet
storage["replicasets/status"] = replicaSetStorage.Status
if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) {
storage["replicasets/scale"] = replicaSetStorage.Scale
}
}
return storage

View File

@ -23,6 +23,9 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
etcderr "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
asvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation"
"k8s.io/kubernetes/pkg/apis/extensions"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields"
@ -50,7 +53,7 @@ func NewStorage(opts generic.RESTOptions) DeploymentStorage {
return DeploymentStorage{
Deployment: deploymentRest,
Status: deploymentStatusRest,
Scale: &ScaleREST{registry: &deploymentRegistry},
Scale: &ScaleREST{registry: deploymentRegistry},
Rollback: deploymentRollbackRest,
}
}
@ -181,56 +184,80 @@ func (r *RollbackREST) setDeploymentRollback(ctx api.Context, deploymentID strin
}
type ScaleREST struct {
registry *deployment.Registry
registry deployment.Registry
}
// TODO(madhusudancs): Fix this when Scale group issues are resolved (see issue #18528).
// ScaleREST implements Patcher
var _ = rest.Patcher(&ScaleREST{})
// // ScaleREST implements Patcher
// var _ = rest.Patcher(&ScaleREST{})
// New creates a new Scale object
func (r *ScaleREST) New() runtime.Object {
return &autoscaling.Scale{}
}
// // New creates a new Scale object
// func (r *ScaleREST) New() runtime.Object {
// return &extensions.Scale{}
// }
func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
deployment, err := r.registry.GetDeployment(ctx, name)
if err != nil {
return nil, errors.NewNotFound(autoscaling.Resource("deployments/scale"), name)
}
scale, err := scaleFromDeployment(deployment)
if err != nil {
return nil, errors.NewBadRequest(fmt.Sprintf("%v", err))
}
return scale, nil
}
// func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
// deployment, err := (*r.registry).GetDeployment(ctx, name)
// if err != nil {
// return nil, errors.NewNotFound(extensions.Resource("deployments/scale"), name)
// }
// scale, err := extensions.ScaleFromDeployment(deployment)
// if err != nil {
// return nil, errors.NewBadRequest(fmt.Sprintf("%v", err))
// }
// return scale, nil
// }
func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
if obj == nil {
return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
}
scale, ok := obj.(*autoscaling.Scale)
if !ok {
return nil, false, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", obj))
}
// func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
// if obj == nil {
// return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
// }
// scale, ok := obj.(*extensions.Scale)
// if !ok {
// return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj))
// }
if errs := asvalidation.ValidateScale(scale); len(errs) > 0 {
return nil, false, errors.NewInvalid(autoscaling.Kind("Scale"), scale.Name, errs)
}
// if errs := extvalidation.ValidateScale(scale); len(errs) > 0 {
// return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs)
// }
deployment, err := r.registry.GetDeployment(ctx, scale.Name)
if err != nil {
return nil, false, errors.NewNotFound(autoscaling.Resource("deployments/scale"), scale.Name)
}
deployment.Spec.Replicas = scale.Spec.Replicas
deployment.ResourceVersion = scale.ResourceVersion
deployment, err = r.registry.UpdateDeployment(ctx, deployment)
if err != nil {
return nil, false, err
}
newScale, err := scaleFromDeployment(deployment)
if err != nil {
return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err))
}
return newScale, false, nil
}
// deployment, err := (*r.registry).GetDeployment(ctx, scale.Name)
// if err != nil {
// return nil, false, errors.NewNotFound(extensions.Resource("deployments/scale"), scale.Name)
// }
// deployment.Spec.Replicas = scale.Spec.Replicas
// deployment, err = (*r.registry).UpdateDeployment(ctx, deployment)
// if err != nil {
// return nil, false, errors.NewConflict(extensions.Resource("deployments/scale"), scale.Name, err)
// }
// newScale, err := extensions.ScaleFromDeployment(deployment)
// if err != nil {
// return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err))
// }
// return newScale, false, nil
// }
// scaleFromDeployment returns a scale subresource for a deployment.
func scaleFromDeployment(deployment *extensions.Deployment) (*autoscaling.Scale, error) {
selector, err := unversioned.LabelSelectorAsSelector(deployment.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("stored deployment object can't be represented in the form of a scale subresource because the label selector ('%v') can't be parsed: %v", deployment.Spec.Selector, err)
}
return &autoscaling.Scale{
// TODO: Create a variant of ObjectMeta type that only contains the fields below.
ObjectMeta: api.ObjectMeta{
Name: deployment.Name,
Namespace: deployment.Namespace,
UID: deployment.UID,
ResourceVersion: deployment.ResourceVersion,
CreationTimestamp: deployment.CreationTimestamp,
},
Spec: autoscaling.ScaleSpec{
Replicas: deployment.Spec.Replicas,
},
Status: autoscaling.ScaleStatus{
Replicas: deployment.Status.Replicas,
Selector: selector.String(),
},
}, nil
}

View File

@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
etcderrors "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
@ -32,8 +33,11 @@ import (
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage/etcd/etcdtest"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/util"
)
const defaultReplicas = 100
func newStorage(t *testing.T) (*DeploymentStorage, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName)
restOptions := generic.RESTOptions{etcdStorage, generic.UndecoratedStorage, 1}
@ -180,73 +184,84 @@ func TestWatch(t *testing.T) {
)
}
// TODO(madhusudancs): Fix this when Scale group issues are resolved (see issue #18528).
func TestScaleGet(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
// func validNewScale() *extensions.Scale {
// return &extensions.Scale{
// ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace},
// Spec: extensions.ScaleSpec{
// Replicas: validDeployment.Spec.Replicas,
// },
// Status: extensions.ScaleStatus{
// Replicas: validDeployment.Status.Replicas,
// Selector: validDeployment.Spec.Template.Labels,
// },
// }
// }
var deployment extensions.Deployment
ctx := api.WithNamespace(api.NewContext(), namespace)
key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name)
if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, &deployment, 0); err != nil {
t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
}
// var validScale = *validNewScale()
selector, err := unversioned.LabelSelectorAsSelector(validDeployment.Spec.Selector)
if err != nil {
t.Errorf("invalid deployment selector %+v: %v", validDeployment.Spec.Selector, err)
}
want := &autoscaling.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: namespace,
UID: deployment.UID,
ResourceVersion: deployment.ResourceVersion,
CreationTimestamp: deployment.CreationTimestamp,
},
Spec: autoscaling.ScaleSpec{
Replicas: validDeployment.Spec.Replicas,
},
Status: autoscaling.ScaleStatus{
Replicas: validDeployment.Status.Replicas,
Selector: selector.String(),
},
}
obj, err := storage.Scale.Get(ctx, name)
if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err)
}
got := obj.(*autoscaling.Scale)
if !api.Semantic.DeepEqual(want, got) {
t.Errorf("unexpected scale: %s", util.ObjectDiff(want, got))
}
}
// func TestScaleGet(t *testing.T) {
// storage, server := newStorage(t)
// defer server.Terminate(t)
func TestScaleUpdate(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
// ctx := api.WithNamespace(api.NewContext(), namespace)
// key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name)
// if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, nil, 0); err != nil {
// t.Fatalf("unexpected error: %v", err)
// }
var deployment extensions.Deployment
ctx := api.WithNamespace(api.NewContext(), namespace)
key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name)
if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, &deployment, 0); err != nil {
t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
}
replicas := 12
update := autoscaling.Scale{
ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace},
Spec: autoscaling.ScaleSpec{
Replicas: replicas,
},
}
// expect := &validScale
// obj, err := storage.Scale.Get(ctx, name)
// if err != nil {
// t.Fatalf("unexpected error: %v", err)
// }
// scale := obj.(*extensions.Scale)
// if e, a := expect, scale; !api.Semantic.DeepDerivative(e, a) {
// t.Errorf("unexpected scale: %s", util.ObjectDiff(e, a))
// }
// }
if _, _, err := storage.Scale.Update(ctx, &update); err != nil {
t.Fatalf("error updating scale %v: %v", update, err)
}
obj, err := storage.Scale.Get(ctx, name)
if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err)
}
scale := obj.(*autoscaling.Scale)
if scale.Spec.Replicas != replicas {
t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas)
}
// func TestScaleUpdate(t *testing.T) {
// storage, server := newStorage(t)
// defer server.Terminate(t)
update.ResourceVersion = deployment.ResourceVersion
update.Spec.Replicas = 15
// ctx := api.WithNamespace(api.NewContext(), namespace)
// key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name)
// if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, nil, 0); err != nil {
// t.Fatalf("unexpected error: %v", err)
// }
// replicas := 12
// update := extensions.Scale{
// ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace},
// Spec: extensions.ScaleSpec{
// Replicas: replicas,
// },
// }
// if _, _, err := storage.Scale.Update(ctx, &update); err != nil {
// t.Fatalf("unexpected error: %v", err)
// }
// obj, err := storage.Deployment.Get(ctx, name)
// if err != nil {
// t.Fatalf("unexpected error: %v", err)
// }
// deployment := obj.(*extensions.Deployment)
// if deployment.Spec.Replicas != replicas {
// t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas)
// }
// }
if _, _, err = storage.Scale.Update(ctx, &update); err != nil && !errors.IsConflict(err) {
t.Fatalf("unexpected error, expecting an update conflict but got %v", err)
}
}
func TestStatusUpdate(t *testing.T) {
storage, server := newStorage(t)
@ -260,10 +275,10 @@ func TestStatusUpdate(t *testing.T) {
update := extensions.Deployment{
ObjectMeta: validDeployment.ObjectMeta,
Spec: extensions.DeploymentSpec{
Replicas: 100,
Replicas: defaultReplicas,
},
Status: extensions.DeploymentStatus{
Replicas: 100,
Replicas: defaultReplicas,
},
}
@ -279,8 +294,8 @@ func TestStatusUpdate(t *testing.T) {
if deployment.Spec.Replicas != 7 {
t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", deployment.Spec.Replicas)
}
if deployment.Status.Replicas != 100 {
t.Errorf("we expected .status.replicas to be updated to 100 but it was %v", deployment.Status.Replicas)
if deployment.Status.Replicas != defaultReplicas {
t.Errorf("we expected .status.replicas to be updated to %d but it was %v", defaultReplicas, deployment.Status.Replicas)
}
}

View File

@ -19,7 +19,14 @@ limitations under the License.
package etcd
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
asvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
@ -34,14 +41,17 @@ import (
type ReplicaSetStorage struct {
ReplicaSet *REST
Status *StatusREST
Scale *ScaleREST
}
func NewStorage(opts generic.RESTOptions) ReplicaSetStorage {
replicaSetRest, replicaSetStatusRest := NewREST(opts)
replicaSetRegistry := replicaset.NewRegistry(replicaSetRest)
return ReplicaSetStorage{
ReplicaSet: replicaSetRest,
Status: replicaSetStatusRest,
Scale: &ScaleREST{registry: replicaSetRegistry},
}
}
@ -110,3 +120,82 @@ func (r *StatusREST) New() runtime.Object {
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
}
type ScaleREST struct {
registry replicaset.Registry
}
// ScaleREST implements Patcher
var _ = rest.Patcher(&ScaleREST{})
// New creates a new Scale object
func (r *ScaleREST) New() runtime.Object {
return &autoscaling.Scale{}
}
func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
rs, err := r.registry.GetReplicaSet(ctx, name)
if err != nil {
return nil, errors.NewNotFound(autoscaling.Resource("replicasets/scale"), name)
}
scale, err := scaleFromReplicaSet(rs)
if err != nil {
return nil, errors.NewBadRequest(fmt.Sprintf("%v", err))
}
return scale, err
}
func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
if obj == nil {
return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
}
scale, ok := obj.(*autoscaling.Scale)
if !ok {
return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj))
}
if errs := asvalidation.ValidateScale(scale); len(errs) > 0 {
return nil, false, errors.NewInvalid(autoscaling.Kind("Scale"), scale.Name, errs)
}
rs, err := r.registry.GetReplicaSet(ctx, scale.Name)
if err != nil {
return nil, false, errors.NewNotFound(autoscaling.Resource("replicasets/scale"), scale.Name)
}
rs.Spec.Replicas = scale.Spec.Replicas
rs.ResourceVersion = scale.ResourceVersion
rs, err = r.registry.UpdateReplicaSet(ctx, rs)
if err != nil {
return nil, false, err
}
newScale, err := scaleFromReplicaSet(rs)
if err != nil {
return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err))
}
return newScale, false, err
}
// scaleFromReplicaSet returns a scale subresource for a replica set.
func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*autoscaling.Scale, error) {
selector, err := unversioned.LabelSelectorAsSelector(rs.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("stored replica set object can't be represented in the form of a scale subresource because the label selector ('%v') can't be parsed: %v", rs.Spec.Selector, err)
}
return &autoscaling.Scale{
// TODO: Create a variant of ObjectMeta type that only contains the fields below.
ObjectMeta: api.ObjectMeta{
Name: rs.Name,
Namespace: rs.Namespace,
UID: rs.UID,
ResourceVersion: rs.ResourceVersion,
CreationTimestamp: rs.CreationTimestamp,
},
Spec: autoscaling.ScaleSpec{
Replicas: rs.Spec.Replicas,
},
Status: autoscaling.ScaleStatus{
Replicas: rs.Status.Replicas,
Selector: selector.String(),
},
}, nil
}

View File

@ -20,16 +20,22 @@ import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage/etcd/etcdtest"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/util"
)
const defaultReplicas = 100
func newStorage(t *testing.T) (*ReplicaSetStorage, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, "extensions")
restOptions := generic.RESTOptions{etcdStorage, generic.UndecoratedStorage, 1}
@ -236,3 +242,126 @@ func TestWatch(t *testing.T) {
},
)
}
func TestScaleGet(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
name := "foo"
var rs extensions.ReplicaSet
ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault)
key := etcdtest.AddPrefix("/replicasets/" + api.NamespaceDefault + "/" + name)
if err := storage.ReplicaSet.Storage.Set(ctx, key, &validReplicaSet, &rs, 0); err != nil {
t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
}
selector, err := unversioned.LabelSelectorAsSelector(validReplicaSet.Spec.Selector)
if err != nil {
t.Errorf("invalid replicaset selector %+v: %v", validReplicaSet.Spec.Selector, err)
}
want := &autoscaling.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
UID: rs.UID,
ResourceVersion: rs.ResourceVersion,
CreationTimestamp: rs.CreationTimestamp,
},
Spec: autoscaling.ScaleSpec{
Replicas: validReplicaSet.Spec.Replicas,
},
Status: autoscaling.ScaleStatus{
Replicas: validReplicaSet.Status.Replicas,
Selector: selector.String(),
},
}
obj, err := storage.Scale.Get(ctx, name)
got := obj.(*autoscaling.Scale)
if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err)
}
if !api.Semantic.DeepEqual(got, want) {
t.Errorf("unexpected scale: %s", util.ObjectDiff(got, want))
}
}
func TestScaleUpdate(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
name := "foo"
var rs extensions.ReplicaSet
ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault)
key := etcdtest.AddPrefix("/replicasets/" + api.NamespaceDefault + "/" + name)
if err := storage.ReplicaSet.Storage.Set(ctx, key, &validReplicaSet, &rs, 0); err != nil {
t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
}
replicas := 12
update := autoscaling.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
},
Spec: autoscaling.ScaleSpec{
Replicas: replicas,
},
}
if _, _, err := storage.Scale.Update(ctx, &update); err != nil {
t.Fatalf("error updating scale %v: %v", update, err)
}
obj, err := storage.Scale.Get(ctx, name)
if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err)
}
scale := obj.(*autoscaling.Scale)
if scale.Spec.Replicas != replicas {
t.Errorf("wrong replicas count expected: %d got: %d", replicas, scale.Spec.Replicas)
}
update.ResourceVersion = rs.ResourceVersion
update.Spec.Replicas = 15
if _, _, err = storage.Scale.Update(ctx, &update); err != nil && !errors.IsConflict(err) {
t.Fatalf("unexpected error, expecting an update conflict but got %v", err)
}
}
func TestStatusUpdate(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault)
key := etcdtest.AddPrefix("/replicasets/" + api.NamespaceDefault + "/foo")
if err := storage.ReplicaSet.Storage.Set(ctx, key, &validReplicaSet, nil, 0); err != nil {
t.Fatalf("unexpected error: %v", err)
}
update := extensions.ReplicaSet{
ObjectMeta: validReplicaSet.ObjectMeta,
Spec: extensions.ReplicaSetSpec{
Replicas: defaultReplicas,
},
Status: extensions.ReplicaSetStatus{
Replicas: defaultReplicas,
},
}
if _, _, err := storage.Status.Update(ctx, &update); err != nil {
t.Fatalf("unexpected error: %v", err)
}
obj, err := storage.ReplicaSet.Get(ctx, "foo")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
rs := obj.(*extensions.ReplicaSet)
if rs.Spec.Replicas != 7 {
t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", rs.Spec.Replicas)
}
if rs.Status.Replicas != defaultReplicas {
t.Errorf("we expected .status.replicas to be updated to %d but it was %v", defaultReplicas, rs.Status.Replicas)
}
}