Commit 9e99f9fa authored by Madhusudan.C.S's avatar Madhusudan.C.S

Register scale subresource for Deployments and ReplicaSets.

Define etcd registry methods for scale subresource in Deployments and ReplicaSets. Register them with the API server.
parent 4d599ea3
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
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
// }
...@@ -243,6 +243,12 @@ func (m *Master) InstallAPIs(c *Config) { ...@@ -243,6 +243,12 @@ func (m *Master) InstallAPIs(c *Config) {
ParameterCodec: api.ParameterCodec, ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs, 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) apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
extensionsGVForDiscovery := unversioned.GroupVersionForDiscovery{ extensionsGVForDiscovery := unversioned.GroupVersionForDiscovery{
...@@ -707,9 +713,11 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { ...@@ -707,9 +713,11 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
deploymentStorage := deploymentetcd.NewStorage(restOptions("deployments")) deploymentStorage := deploymentetcd.NewStorage(restOptions("deployments"))
storage["deployments"] = deploymentStorage.Deployment storage["deployments"] = deploymentStorage.Deployment
storage["deployments/status"] = deploymentStorage.Status 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 storage["deployments/rollback"] = deploymentStorage.Rollback
if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) {
storage["deployments/scale"] = deploymentStorage.Scale
}
} }
if isEnabled("jobs") { if isEnabled("jobs") {
m.constructJobResources(c, storage) m.constructJobResources(c, storage)
...@@ -727,6 +735,9 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { ...@@ -727,6 +735,9 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets")) replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets"))
storage["replicasets"] = replicaSetStorage.ReplicaSet storage["replicasets"] = replicaSetStorage.ReplicaSet
storage["replicasets/status"] = replicaSetStorage.Status storage["replicasets/status"] = replicaSetStorage.Status
if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) {
storage["replicasets/scale"] = replicaSetStorage.Scale
}
} }
return storage return storage
......
...@@ -23,6 +23,9 @@ import ( ...@@ -23,6 +23,9 @@ import (
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
etcderr "k8s.io/kubernetes/pkg/api/errors/etcd" etcderr "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/api/rest" "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/apis/extensions"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
...@@ -50,7 +53,7 @@ func NewStorage(opts generic.RESTOptions) DeploymentStorage { ...@@ -50,7 +53,7 @@ func NewStorage(opts generic.RESTOptions) DeploymentStorage {
return DeploymentStorage{ return DeploymentStorage{
Deployment: deploymentRest, Deployment: deploymentRest,
Status: deploymentStatusRest, Status: deploymentStatusRest,
Scale: &ScaleREST{registry: &deploymentRegistry}, Scale: &ScaleREST{registry: deploymentRegistry},
Rollback: deploymentRollbackRest, Rollback: deploymentRollbackRest,
} }
} }
...@@ -181,56 +184,80 @@ func (r *RollbackREST) setDeploymentRollback(ctx api.Context, deploymentID strin ...@@ -181,56 +184,80 @@ func (r *RollbackREST) setDeploymentRollback(ctx api.Context, deploymentID strin
} }
type ScaleREST struct { 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 {
// // New creates a new Scale object return &autoscaling.Scale{}
// 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)
// func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) { if err != nil {
// deployment, err := (*r.registry).GetDeployment(ctx, name) return nil, errors.NewNotFound(autoscaling.Resource("deployments/scale"), name)
// if err != nil { }
// return nil, errors.NewNotFound(extensions.Resource("deployments/scale"), name) scale, err := scaleFromDeployment(deployment)
// } if err != nil {
// scale, err := extensions.ScaleFromDeployment(deployment) return nil, errors.NewBadRequest(fmt.Sprintf("%v", err))
// if err != nil { }
// return nil, errors.NewBadRequest(fmt.Sprintf("%v", err)) return scale, nil
// } }
// return scale, nil
// } func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
if obj == nil {
// func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
// if obj == nil { }
// return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) scale, ok := obj.(*autoscaling.Scale)
// } if !ok {
// scale, ok := obj.(*extensions.Scale) return nil, false, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", obj))
// 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 {
// deployment, err := (*r.registry).GetDeployment(ctx, scale.Name) return nil, false, errors.NewNotFound(autoscaling.Resource("deployments/scale"), scale.Name)
// if err != nil { }
// return nil, false, errors.NewNotFound(extensions.Resource("deployments/scale"), scale.Name) deployment.Spec.Replicas = scale.Spec.Replicas
// } deployment.ResourceVersion = scale.ResourceVersion
// deployment.Spec.Replicas = scale.Spec.Replicas deployment, err = r.registry.UpdateDeployment(ctx, deployment)
// deployment, err = (*r.registry).UpdateDeployment(ctx, deployment) if err != nil {
// if err != nil { return nil, false, err
// return nil, false, errors.NewConflict(extensions.Resource("deployments/scale"), scale.Name, err) }
// } newScale, err := scaleFromDeployment(deployment)
// newScale, err := extensions.ScaleFromDeployment(deployment) if err != nil {
// if err != nil { return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err))
// return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err)) }
// } return newScale, false, nil
// 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
}
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
etcderrors "k8s.io/kubernetes/pkg/api/errors/etcd" etcderrors "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
...@@ -32,8 +33,11 @@ import ( ...@@ -32,8 +33,11 @@ import (
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage/etcd/etcdtest" "k8s.io/kubernetes/pkg/storage/etcd/etcdtest"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/util"
) )
const defaultReplicas = 100
func newStorage(t *testing.T) (*DeploymentStorage, *etcdtesting.EtcdTestServer) { func newStorage(t *testing.T) (*DeploymentStorage, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName)
restOptions := generic.RESTOptions{etcdStorage, generic.UndecoratedStorage, 1} restOptions := generic.RESTOptions{etcdStorage, generic.UndecoratedStorage, 1}
...@@ -180,73 +184,84 @@ func TestWatch(t *testing.T) { ...@@ -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)
// func validNewScale() *extensions.Scale { defer server.Terminate(t)
// return &extensions.Scale{
// ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, var deployment extensions.Deployment
// Spec: extensions.ScaleSpec{ ctx := api.WithNamespace(api.NewContext(), namespace)
// Replicas: validDeployment.Spec.Replicas, key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name)
// }, if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, &deployment, 0); err != nil {
// Status: extensions.ScaleStatus{ t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
// Replicas: validDeployment.Status.Replicas, }
// Selector: validDeployment.Spec.Template.Labels,
// }, selector, err := unversioned.LabelSelectorAsSelector(validDeployment.Spec.Selector)
// } if err != nil {
// } t.Errorf("invalid deployment selector %+v: %v", validDeployment.Spec.Selector, err)
}
// var validScale = *validNewScale() want := &autoscaling.Scale{
ObjectMeta: api.ObjectMeta{
// func TestScaleGet(t *testing.T) { Name: name,
// storage, server := newStorage(t) Namespace: namespace,
// defer server.Terminate(t) UID: deployment.UID,
ResourceVersion: deployment.ResourceVersion,
// ctx := api.WithNamespace(api.NewContext(), namespace) CreationTimestamp: deployment.CreationTimestamp,
// key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) },
// if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, nil, 0); err != nil { Spec: autoscaling.ScaleSpec{
// t.Fatalf("unexpected error: %v", err) Replicas: validDeployment.Spec.Replicas,
// } },
Status: autoscaling.ScaleStatus{
// expect := &validScale Replicas: validDeployment.Status.Replicas,
// obj, err := storage.Scale.Get(ctx, name) Selector: selector.String(),
// if err != nil { },
// t.Fatalf("unexpected error: %v", err) }
// } obj, err := storage.Scale.Get(ctx, name)
// scale := obj.(*extensions.Scale) if err != nil {
// if e, a := expect, scale; !api.Semantic.DeepDerivative(e, a) { t.Fatalf("error fetching scale for %s: %v", name, err)
// t.Errorf("unexpected scale: %s", util.ObjectDiff(e, a)) }
// } got := obj.(*autoscaling.Scale)
// } if !api.Semantic.DeepEqual(want, got) {
t.Errorf("unexpected scale: %s", util.ObjectDiff(want, got))
// func TestScaleUpdate(t *testing.T) { }
// storage, server := newStorage(t) }
// defer server.Terminate(t)
func TestScaleUpdate(t *testing.T) {
// ctx := api.WithNamespace(api.NewContext(), namespace) storage, server := newStorage(t)
// key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) defer server.Terminate(t)
// 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)
// replicas := 12 key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name)
// update := extensions.Scale{ if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, &deployment, 0); err != nil {
// ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
// Spec: extensions.ScaleSpec{ }
// Replicas: replicas, replicas := 12
// }, update := autoscaling.Scale{
// } ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace},
Spec: autoscaling.ScaleSpec{
// if _, _, err := storage.Scale.Update(ctx, &update); err != nil { Replicas: replicas,
// t.Fatalf("unexpected error: %v", err) },
// } }
// obj, err := storage.Deployment.Get(ctx, name)
// if err != nil { if _, _, err := storage.Scale.Update(ctx, &update); err != nil {
// t.Fatalf("unexpected error: %v", err) t.Fatalf("error updating scale %v: %v", update, err)
// } }
// deployment := obj.(*extensions.Deployment) obj, err := storage.Scale.Get(ctx, name)
// if deployment.Spec.Replicas != replicas { if err != nil {
// t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) 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)
}
update.ResourceVersion = deployment.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) { func TestStatusUpdate(t *testing.T) {
storage, server := newStorage(t) storage, server := newStorage(t)
...@@ -260,10 +275,10 @@ func TestStatusUpdate(t *testing.T) { ...@@ -260,10 +275,10 @@ func TestStatusUpdate(t *testing.T) {
update := extensions.Deployment{ update := extensions.Deployment{
ObjectMeta: validDeployment.ObjectMeta, ObjectMeta: validDeployment.ObjectMeta,
Spec: extensions.DeploymentSpec{ Spec: extensions.DeploymentSpec{
Replicas: 100, Replicas: defaultReplicas,
}, },
Status: extensions.DeploymentStatus{ Status: extensions.DeploymentStatus{
Replicas: 100, Replicas: defaultReplicas,
}, },
} }
...@@ -279,8 +294,8 @@ func TestStatusUpdate(t *testing.T) { ...@@ -279,8 +294,8 @@ func TestStatusUpdate(t *testing.T) {
if deployment.Spec.Replicas != 7 { if deployment.Spec.Replicas != 7 {
t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", deployment.Spec.Replicas) t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", deployment.Spec.Replicas)
} }
if deployment.Status.Replicas != 100 { if deployment.Status.Replicas != defaultReplicas {
t.Errorf("we expected .status.replicas to be updated to 100 but it was %v", deployment.Status.Replicas) t.Errorf("we expected .status.replicas to be updated to %d but it was %v", defaultReplicas, deployment.Status.Replicas)
} }
} }
......
...@@ -19,7 +19,14 @@ limitations under the License. ...@@ -19,7 +19,14 @@ limitations under the License.
package etcd package etcd
import ( import (
"fmt"
"k8s.io/kubernetes/pkg/api" "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/apis/extensions"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
...@@ -34,14 +41,17 @@ import ( ...@@ -34,14 +41,17 @@ import (
type ReplicaSetStorage struct { type ReplicaSetStorage struct {
ReplicaSet *REST ReplicaSet *REST
Status *StatusREST Status *StatusREST
Scale *ScaleREST
} }
func NewStorage(opts generic.RESTOptions) ReplicaSetStorage { func NewStorage(opts generic.RESTOptions) ReplicaSetStorage {
replicaSetRest, replicaSetStatusRest := NewREST(opts) replicaSetRest, replicaSetStatusRest := NewREST(opts)
replicaSetRegistry := replicaset.NewRegistry(replicaSetRest)
return ReplicaSetStorage{ return ReplicaSetStorage{
ReplicaSet: replicaSetRest, ReplicaSet: replicaSetRest,
Status: replicaSetStatusRest, Status: replicaSetStatusRest,
Scale: &ScaleREST{registry: replicaSetRegistry},
} }
} }
...@@ -110,3 +120,82 @@ func (r *StatusREST) New() runtime.Object { ...@@ -110,3 +120,82 @@ func (r *StatusREST) New() runtime.Object {
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj) 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
}
...@@ -20,16 +20,22 @@ import ( ...@@ -20,16 +20,22 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/registry/registrytest" "k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage/etcd/etcdtest"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/util"
) )
const defaultReplicas = 100
func newStorage(t *testing.T) (*ReplicaSetStorage, *etcdtesting.EtcdTestServer) { func newStorage(t *testing.T) (*ReplicaSetStorage, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, "extensions") etcdStorage, server := registrytest.NewEtcdStorage(t, "extensions")
restOptions := generic.RESTOptions{etcdStorage, generic.UndecoratedStorage, 1} restOptions := generic.RESTOptions{etcdStorage, generic.UndecoratedStorage, 1}
...@@ -236,3 +242,126 @@ func TestWatch(t *testing.T) { ...@@ -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)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment