Merge pull request #12217 from mwielgus/exp_scale

Experimental Scale subresource
pull/6/head
Marek Grabowski 2015-08-10 16:32:59 +02:00
commit 87250d075f
8 changed files with 376 additions and 3 deletions

View File

@ -16,4 +16,19 @@ limitations under the License.
package expapi
func init() {}
import (
"k8s.io/kubernetes/pkg/api"
)
func init() {
// Register the API.
addKnownTypes()
}
// Adds the list of known types to api.Scheme.
func addKnownTypes() {
api.Scheme.AddKnownTypes("", &Scale{}, &ReplicationControllerDummy{})
}
func (*Scale) IsAnAPIObject() {}
func (*ReplicationControllerDummy) IsAnAPIObject() {}

View File

@ -27,3 +27,37 @@ support is experimental.
*/
package expapi
import "k8s.io/kubernetes/pkg/api"
// ScaleSpec describes the attributes a Scale subresource
type ScaleSpec struct {
// Replicas is the number of desired replicas.
Replicas int `json:"replicas,omitempty" description:"number of replicas desired; http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller"`
}
// ScaleStatus represents the current status of a Scale subresource.
type ScaleStatus struct {
// Replicas is the number of actual replicas.
Replicas int `json:"replicas" description:"most recently oberved number of replicas; see http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller"`
// Selector is a label query over pods that should match the replicas count.
Selector map[string]string `json:"selector,omitempty" description:"label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template; see http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"`
}
// Scale subresource, applicable to ReplicationControllers and (in future) Deployment.
type Scale struct {
api.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata"`
// Spec defines the behavior of the scaler.
Spec ScaleSpec `json:"spec,omitempty" description:"specification of the desired behavior of the scaler; http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status"`
// Status represents the current status of the scaler.
Status ScaleStatus `json:"status,omitempty" description:"most recently observed status of the service; populated by the system, read-only; http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status"`
}
// Dummy definition
type ReplicationControllerDummy struct {
api.TypeMeta `json:",inline"`
}

View File

@ -16,4 +16,5 @@ limitations under the License.
package v1
func addDefaultingFuncs() {}
func addDefaultingFuncs() {
}

View File

@ -27,4 +27,13 @@ func init() {
addDeepCopyFuncs()
addConversionFuncs()
addDefaultingFuncs()
addKnownTypes()
}
// Adds the list of known types to api.Scheme.
func addKnownTypes() {
api.Scheme.AddKnownTypes("v1", &Scale{}, &ReplicationControllerDummy{})
}
func (*Scale) IsAnAPIObject() {}
func (*ReplicationControllerDummy) IsAnAPIObject() {}

View File

@ -15,3 +15,37 @@ limitations under the License.
*/
package v1
import "k8s.io/kubernetes/pkg/api/v1"
// ScaleSpec describes the attributes a Scale subresource
type ScaleSpec struct {
// Replicas is the number of desired replicas.
Replicas int `json:"replicas,omitempty" description:"number of replicas desired; see http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller"`
}
// ScaleStatus represents the current status of a Scale subresource.
type ScaleStatus struct {
// Replicas is the number of actual replicas.
Replicas int `json:"replicas" description:"most recently oberved number of replicas; see http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller"`
// Selector is a label query over pods that should match the replicas count.
Selector map[string]string `json:"selector,omitempty" description:"label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template; see http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"`
}
// Scale subresource, applicable to ReplicationControllers and (in future) Deployment.
type Scale struct {
v1.TypeMeta `json:",inline"`
v1.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata"`
// Spec defines the behavior of the scaler.
Spec ScaleSpec `json:"spec,omitempty" description:"specification of the desired behavior of the scaler; http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status"`
// Status represents the current status of the scaler.
Status ScaleStatus `json:"status,omitempty" description:"most recently observed status of the service; populated by the system, read-only; http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status"`
}
// Dummy definition
type ReplicationControllerDummy struct {
v1.TypeMeta `json:",inline"`
}

View File

@ -55,6 +55,7 @@ import (
endpointsetcd "k8s.io/kubernetes/pkg/registry/endpoint/etcd"
"k8s.io/kubernetes/pkg/registry/etcd"
"k8s.io/kubernetes/pkg/registry/event"
expcontrolleretcd "k8s.io/kubernetes/pkg/registry/experimental/controller/etcd"
"k8s.io/kubernetes/pkg/registry/limitrange"
"k8s.io/kubernetes/pkg/registry/minion"
nodeetcd "k8s.io/kubernetes/pkg/registry/minion/etcd"
@ -777,7 +778,13 @@ func (m *Master) api_v1() *apiserver.APIGroupVersion {
// expapi returns the resources and codec for the experimental api
func (m *Master) expapi(c *Config) *apiserver.APIGroupVersion {
storage := map[string]rest.Storage{}
controllerStorage := expcontrolleretcd.NewStorage(c.DatabaseStorage)
storage := map[string]rest.Storage{
strings.ToLower("replicationControllers"): controllerStorage.ReplicationController,
strings.ToLower("replicationControllers/scaler"): controllerStorage.Scale,
}
return &apiserver.APIGroupVersion{
Root: m.expAPIPrefix,

View File

@ -0,0 +1,120 @@
/*
Copyright 2014 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 etcd
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
"k8s.io/kubernetes/pkg/registry/controller"
"k8s.io/kubernetes/pkg/registry/controller/etcd"
"k8s.io/kubernetes/pkg/expapi"
)
// Container includes dummy storage for RC pods and experimental storage for Scale.
type ContainerStorage struct {
ReplicationController *RcREST
Scale *ScaleREST
}
func NewStorage(s storage.Interface) ContainerStorage {
rcRegistry := controller.NewRegistry(etcd.NewREST(s))
return ContainerStorage{
ReplicationController: &RcREST{},
Scale: &ScaleREST{registry: &rcRegistry},
}
}
type ScaleREST struct {
registry *controller.Registry
}
// LogREST implements GetterWithOptions
var _ = rest.Patcher(&ScaleREST{})
// New creates a new Scale object
func (r *ScaleREST) New() runtime.Object {
return &expapi.Scale{}
}
func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
rc, err := (*r.registry).GetController(ctx, name)
if err != nil {
return nil, errors.NewNotFound("scaler", name)
}
return &expapi.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: rc.Namespace,
CreationTimestamp: rc.CreationTimestamp,
},
Spec: expapi.ScaleSpec{
Replicas: rc.Spec.Replicas,
},
Status: expapi.ScaleStatus{
Replicas: rc.Status.Replicas,
Selector: rc.Spec.Selector,
},
}, 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"))
}
scaler, ok := obj.(*expapi.Scale)
if !ok {
return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj))
}
rc, err := (*r.registry).GetController(ctx, scaler.Name)
if err != nil {
return nil, false, errors.NewNotFound("scaler", scaler.Name)
}
rc.Spec.Replicas = scaler.Spec.Replicas
rc, err = (*r.registry).UpdateController(ctx, rc)
if err != nil {
return nil, false, errors.NewConflict("scaler", scaler.Name, err)
}
return &expapi.Scale{
ObjectMeta: api.ObjectMeta{
Name: rc.Name,
Namespace: rc.Namespace,
CreationTimestamp: rc.CreationTimestamp,
},
Spec: expapi.ScaleSpec{
Replicas: rc.Spec.Replicas,
},
Status: expapi.ScaleStatus{
Replicas: rc.Status.Replicas,
Selector: rc.Spec.Selector,
},
}, false, nil
}
// Dummy implementation
type RcREST struct{}
func (r *RcREST) New() runtime.Object {
return &expapi.ReplicationControllerDummy{}
}

View File

@ -0,0 +1,153 @@
/*
Copyright 2014 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 etcd
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/expapi"
"github.com/coreos/go-etcd/etcd"
)
func newEtcdStorage(t *testing.T) (*tools.FakeEtcdClient, storage.Interface) {
fakeEtcdClient := tools.NewFakeEtcdClient(t)
fakeEtcdClient.TestIndex = true
etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, latest.Codec, etcdtest.PathPrefix())
return fakeEtcdClient, etcdStorage
}
func newStorage(t *testing.T) (*RcREST, *ScaleREST, *tools.FakeEtcdClient, storage.Interface) {
fakeEtcdClient, etcdStorage := newEtcdStorage(t)
storage := NewStorage(etcdStorage)
return storage.ReplicationController, storage.Scale, fakeEtcdClient, etcdStorage
}
var validPodTemplate = api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
},
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
}
var validReplicas = 8
var validControllerSpec = api.ReplicationControllerSpec{
Replicas: validReplicas,
Selector: validPodTemplate.Template.Labels,
Template: &validPodTemplate.Template,
}
var validController = api.ReplicationController{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "1"},
Spec: validControllerSpec,
}
var validScale = expapi.Scale{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"},
Spec: expapi.ScaleSpec{
Replicas: validReplicas,
},
Status: expapi.ScaleStatus{
Replicas: 0,
Selector: validPodTemplate.Template.Labels,
},
}
func TestGet(t *testing.T) {
expect := &validScale
fakeEtcdClient, etcdStorage := newEtcdStorage(t)
key := etcdtest.AddPrefix("/controllers/test/foo")
fakeEtcdClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(latest.Codec, &validController),
ModifiedIndex: 1,
},
},
}
storage := NewStorage(etcdStorage).Scale
obj, err := storage.Get(api.WithNamespace(api.NewContext(), "test"), "foo")
scaler := obj.(*expapi.Scale)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if e, a := expect, scaler; !api.Semantic.DeepEqual(e, a) {
t.Errorf("Unexpected scaler: %s", util.ObjectDiff(e, a))
}
}
func TestUpdate(t *testing.T) {
fakeEtcdClient, etcdStorage := newEtcdStorage(t)
storage := NewStorage(etcdStorage).Scale
key := etcdtest.AddPrefix("/controllers/test/foo")
fakeEtcdClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(latest.Codec, &validController),
ModifiedIndex: 1,
},
},
}
replicas := 12
update := expapi.Scale{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"},
Spec: expapi.ScaleSpec{
Replicas: replicas,
},
}
_, _, err := storage.Update(api.WithNamespace(api.NewContext(), "test"), &update)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
response, err := fakeEtcdClient.Get(key, false, false)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var controller api.ReplicationController
latest.Codec.DecodeInto([]byte(response.Node.Value), &controller)
if controller.Spec.Replicas != replicas {
t.Errorf("wrong replicas count expected: %d got: %d", replicas, controller.Spec.Replicas)
}
}