diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 11c7725a70b..77d153787bd 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -51,6 +51,9 @@ import ( "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/pflag" + + // TODO: Enable the code belowe when horizontal pod autoscaler controller is implemented. + // "k8s.io/kubernetes/pkg/controller/autoscaler" ) // CMServer is the main context object for the controller manager. @@ -264,5 +267,13 @@ func (s *CMServer) Run(_ []string) error { serviceaccount.DefaultServiceAccountsControllerOptions(), ).Run() + // TODO: Enable the code belowe when horizontal pod autoscaler controller is implemented. + // expClient, err := client.NewExperimental(kubeconfig) + // if err != nil { + // glog.Fatalf("Invalid API configuration: %v", err) + // } + // horizontalPodAutoscalerController := autoscalercontroller.New(expClient) + // horizontalPodAutoscalerController.Run(s.NodeSyncPeriod) + select {} } diff --git a/pkg/client/unversioned/client_test.go b/pkg/client/unversioned/client_test.go index 5671518704f..d4100cc9296 100644 --- a/pkg/client/unversioned/client_test.go +++ b/pkg/client/unversioned/client_test.go @@ -54,6 +54,7 @@ type Response struct { type testClient struct { *Client + *ExperimentalClient Request testRequest Response Response Error bool @@ -86,6 +87,16 @@ func (c *testClient) Setup() *testClient { Version: version, }) } + if c.ExperimentalClient == nil { + version := c.Version + if len(version) == 0 { + version = testapi.Version() + } + c.ExperimentalClient = NewExperimentalOrDie(&Config{ + Host: c.server.URL, + Version: version, + }) + } c.QueryValidator = map[string]func(string, string) bool{} return c } diff --git a/pkg/client/unversioned/experimental.go b/pkg/client/unversioned/experimental.go index ed360a698c9..396aa7ce2c1 100644 --- a/pkg/client/unversioned/experimental.go +++ b/pkg/client/unversioned/experimental.go @@ -32,6 +32,7 @@ import ( // incompatible ways at any time. type ExperimentalInterface interface { VersionInterface + HorizontalPodAutoscalersNamespacer } // ExperimentalClient is used to interact with experimental Kubernetes features. @@ -70,6 +71,10 @@ func (c *ExperimentalClient) ServerAPIVersions() (*api.APIVersions, error) { return &v, nil } +func (c *ExperimentalClient) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { + return newHorizontalPodAutoscalers(c, namespace) +} + // NewExperimental creates a new ExperimentalClient for the given config. This client // provides access to experimental Kubernetes features. // Experimental features are not supported and may be changed or removed in diff --git a/pkg/client/unversioned/horizontalpodautoscaler.go b/pkg/client/unversioned/horizontalpodautoscaler.go new file mode 100644 index 00000000000..8c6a743c411 --- /dev/null +++ b/pkg/client/unversioned/horizontalpodautoscaler.go @@ -0,0 +1,107 @@ +/* +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 unversioned + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/watch" +) + +// HorizontalPodAutoscalersNamespacer has methods to work with HorizontalPodAutoscaler resources in a namespace +type HorizontalPodAutoscalersNamespacer interface { + HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface +} + +// HorizontalPodAutoscalerInterface has methods to work with HorizontalPodAutoscaler resources. +type HorizontalPodAutoscalerInterface interface { + List(label labels.Selector, field fields.Selector) (*expapi.HorizontalPodAutoscalerList, error) + Get(name string) (*expapi.HorizontalPodAutoscaler, error) + Delete(name string, options *api.DeleteOptions) error + Create(horizontalPodAutoscaler *expapi.HorizontalPodAutoscaler) (*expapi.HorizontalPodAutoscaler, error) + Update(horizontalPodAutoscaler *expapi.HorizontalPodAutoscaler) (*expapi.HorizontalPodAutoscaler, error) + Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) +} + +// horizontalPodAutoscalers implements HorizontalPodAutoscalersNamespacer interface +type horizontalPodAutoscalers struct { + client *ExperimentalClient + ns string +} + +// newHorizontalPodAutoscalers returns a horizontalPodAutoscalers +func newHorizontalPodAutoscalers(c *ExperimentalClient, namespace string) *horizontalPodAutoscalers { + return &horizontalPodAutoscalers{ + client: c, + ns: namespace, + } +} + +// List takes label and field selectors, and returns the list of horizontalPodAutoscalers that match those selectors. +func (c *horizontalPodAutoscalers) List(label labels.Selector, field fields.Selector) (result *expapi.HorizontalPodAutoscalerList, err error) { + result = &expapi.HorizontalPodAutoscalerList{} + err = c.client.Get().Namespace(c.ns).Resource("horizontalPodAutoscalers").LabelsSelectorParam(label).FieldsSelectorParam(field).Do().Into(result) + return +} + +// Get takes the name of the horizontalPodAutoscaler, and returns the corresponding HorizontalPodAutoscaler object, and an error if it occurs +func (c *horizontalPodAutoscalers) Get(name string) (result *expapi.HorizontalPodAutoscaler, err error) { + result = &expapi.HorizontalPodAutoscaler{} + err = c.client.Get().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(name).Do().Into(result) + return +} + +// Delete takes the name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs. +func (c *horizontalPodAutoscalers) Delete(name string, options *api.DeleteOptions) error { + // TODO: to make this reusable in other client libraries + if options == nil { + return c.client.Delete().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(name).Do().Error() + } + body, err := api.Scheme.EncodeToVersion(options, c.client.APIVersion()) + if err != nil { + return err + } + return c.client.Delete().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(name).Body(body).Do().Error() +} + +// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if it occurs. +func (c *horizontalPodAutoscalers) Create(horizontalPodAutoscaler *expapi.HorizontalPodAutoscaler) (result *expapi.HorizontalPodAutoscaler, err error) { + result = &expapi.HorizontalPodAutoscaler{} + err = c.client.Post().Namespace(c.ns).Resource("horizontalPodAutoscalers").Body(horizontalPodAutoscaler).Do().Into(result) + return +} + +// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if it occurs. +func (c *horizontalPodAutoscalers) Update(horizontalPodAutoscaler *expapi.HorizontalPodAutoscaler) (result *expapi.HorizontalPodAutoscaler, err error) { + result = &expapi.HorizontalPodAutoscaler{} + err = c.client.Put().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(horizontalPodAutoscaler.Name).Body(horizontalPodAutoscaler).Do().Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. +func (c *horizontalPodAutoscalers) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("horizontalPodAutoscalers"). + Param("resourceVersion", resourceVersion). + LabelsSelectorParam(label). + FieldsSelectorParam(field). + Watch() +} diff --git a/pkg/client/unversioned/horizontalpodautoscaler_test.go b/pkg/client/unversioned/horizontalpodautoscaler_test.go new file mode 100644 index 00000000000..23ff02d8eed --- /dev/null +++ b/pkg/client/unversioned/horizontalpodautoscaler_test.go @@ -0,0 +1,143 @@ +/* +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 unversioned + +import ( + "net/url" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/expapi" + "k8s.io/kubernetes/pkg/expapi/testapi" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" +) + +func getHorizontalPodAutoscalersResoureName() string { + return "horizontalpodautoscalers" +} + +func TestHorizontalPodAutoscalerCreate(t *testing.T) { + ns := api.NamespaceDefault + horizontalPodAutoscaler := expapi.HorizontalPodAutoscaler{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: ns, + }, + } + c := &testClient{ + Request: testRequest{ + Method: "POST", + Path: testapi.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""), + Query: buildQueryValues(nil), + Body: &horizontalPodAutoscaler, + }, + Response: Response{StatusCode: 200, Body: &horizontalPodAutoscaler}, + } + + response, err := c.Setup().HorizontalPodAutoscalers(ns).Create(&horizontalPodAutoscaler) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + c.Validate(t, response, err) +} + +func TestHorizontalPodAutoscalerGet(t *testing.T) { + ns := api.NamespaceDefault + horizontalPodAutoscaler := &expapi.HorizontalPodAutoscaler{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: ns, + }, + } + c := &testClient{ + Request: testRequest{ + Method: "GET", + Path: testapi.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"), + Query: buildQueryValues(nil), + Body: nil, + }, + Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler}, + } + + response, err := c.Setup().HorizontalPodAutoscalers(ns).Get("abc") + c.Validate(t, response, err) +} + +func TestHorizontalPodAutoscalerList(t *testing.T) { + ns := api.NamespaceDefault + horizontalPodAutoscalerList := &expapi.HorizontalPodAutoscalerList{ + Items: []expapi.HorizontalPodAutoscaler{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + }, + }, + }, + } + c := &testClient{ + Request: testRequest{ + Method: "GET", + Path: testapi.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""), + Query: buildQueryValues(nil), + Body: nil, + }, + Response: Response{StatusCode: 200, Body: horizontalPodAutoscalerList}, + } + response, err := c.Setup().HorizontalPodAutoscalers(ns).List(labels.Everything(), fields.Everything()) + c.Validate(t, response, err) +} + +func TestHorizontalPodAutoscalerUpdate(t *testing.T) { + ns := api.NamespaceDefault + horizontalPodAutoscaler := &expapi.HorizontalPodAutoscaler{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: ns, + ResourceVersion: "1", + }, + } + c := &testClient{ + Request: testRequest{Method: "PUT", Path: testapi.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"), Query: buildQueryValues(nil)}, + Response: Response{StatusCode: 200, Body: horizontalPodAutoscaler}, + } + response, err := c.Setup().HorizontalPodAutoscalers(ns).Update(horizontalPodAutoscaler) + c.Validate(t, response, err) +} + +func TestHorizontalPodAutoscalerDelete(t *testing.T) { + ns := api.NamespaceDefault + c := &testClient{ + Request: testRequest{Method: "DELETE", Path: testapi.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "foo"), Query: buildQueryValues(nil)}, + Response: Response{StatusCode: 200}, + } + err := c.Setup().HorizontalPodAutoscalers(ns).Delete("foo", nil) + c.Validate(t, nil, err) +} + +func TestHorizontalPodAutoscalerWatch(t *testing.T) { + c := &testClient{ + Request: testRequest{ + Method: "GET", + Path: testapi.ResourcePathWithPrefix("watch", getHorizontalPodAutoscalersResoureName(), "", ""), + Query: url.Values{"resourceVersion": []string{}}}, + Response: Response{StatusCode: 200}, + } + _, err := c.Setup().HorizontalPodAutoscalers(api.NamespaceAll).Watch(labels.Everything(), fields.Everything(), "") + c.Validate(t, nil, err) +} diff --git a/pkg/controller/autoscaler/horizontalpodautoscaler_controller.go b/pkg/controller/autoscaler/horizontalpodautoscaler_controller.go new file mode 100644 index 00000000000..d49d98a7719 --- /dev/null +++ b/pkg/controller/autoscaler/horizontalpodautoscaler_controller.go @@ -0,0 +1,58 @@ +/* +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 autoscalercontroller + +import ( + "fmt" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/util" +) + +type HorizontalPodAutoscalerController struct { + kubeClient unversioned.ExperimentalInterface +} + +func New(kubeClient unversioned.ExperimentalInterface) *HorizontalPodAutoscalerController { + return &HorizontalPodAutoscalerController{ + kubeClient: kubeClient, + } +} + +func (a *HorizontalPodAutoscalerController) Run(syncPeriod time.Duration) { + go util.Forever(func() { + if err := a.reconcileAutoscalers(); err != nil { + glog.Errorf("Couldn't reconcile horizontal pod autoscalers: %v", err) + } + }, syncPeriod) +} + +func (a *HorizontalPodAutoscalerController) reconcileAutoscalers() error { + ns := api.NamespaceAll + list, err := a.kubeClient.HorizontalPodAutoscalers(ns).List(labels.Everything(), fields.Everything()) + if err != nil { + return fmt.Errorf("error listing nodes: %v", err) + } + // TODO: implement! + glog.Info("autoscalers: %v", list) + return nil +} diff --git a/pkg/expapi/testapi/testapi.go b/pkg/expapi/testapi/testapi.go new file mode 100644 index 00000000000..5a344b8c95b --- /dev/null +++ b/pkg/expapi/testapi/testapi.go @@ -0,0 +1,52 @@ +/* +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 testapi + +import ( + "strings" + + "k8s.io/kubernetes/pkg/expapi/latest" +) + +// Returns the appropriate path for the given prefix (watch, proxy, redirect, etc), resource, namespace and name. +// For example, this is of the form: +// /experimental/v1/watch/namespaces/foo/pods/pod0 for v1. +func ResourcePathWithPrefix(prefix, resource, namespace, name string) string { + path := "/experimental/" + latest.Version + if prefix != "" { + path = path + "/" + prefix + } + if namespace != "" { + path = path + "/namespaces/" + namespace + } + // Resource names are lower case. + resource = strings.ToLower(resource) + if resource != "" { + path = path + "/" + resource + } + if name != "" { + path = path + "/" + name + } + return path +} + +// Returns the appropriate path for the given resource, namespace and name. +// For example, this is of the form: +// /experimental/v1/namespaces/foo/pods/pod0 for v1. +func ResourcePath(resource, namespace, name string) string { + return ResourcePathWithPrefix("", resource, namespace, name) +}