提交 a320d528 编写于 作者: P Piotr Bryk

Merge pull request #329 from floreks/delete-service

Added option to delete services related to replication controller while deleting RC
......@@ -297,13 +297,20 @@ func (apiHandler *ApiHandler) handleUpdateReplicasCount(
}
// Handles delete Replication Controller API call.
// TODO(floreks): there has to be some kind of transaction here
func (apiHandler *ApiHandler) handleDeleteReplicationController(
request *restful.Request, response *restful.Response) {
namespace := request.PathParameter("namespace")
replicationController := request.PathParameter("replicationController")
deleteServices, err := strconv.ParseBool(request.QueryParameter("deleteServices"))
if err != nil {
handleInternalError(response, err)
return
}
if err := DeleteReplicationControllerWithPods(apiHandler.client, namespace, replicationController); err != nil {
if err := DeleteReplicationController(apiHandler.client, namespace,
replicationController, deleteServices); err != nil {
handleInternalError(response, err)
return
}
......
......@@ -17,6 +17,7 @@ package main
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
......@@ -103,3 +104,47 @@ func getReplicationControllerPodInfo(replicationController *api.ReplicationContr
return result
}
// Transforms simple selector map to labels.Selector object that can be used when querying for
// object.
func toLabelSelector(selector map[string]string) (labels.Selector, error) {
labelSelector, err := extensions.LabelSelectorAsSelector(&extensions.LabelSelector{MatchLabels: selector})
if err != nil {
return nil, err
}
return labelSelector, nil
}
// Based on given selector returns list of services that are candidates for deletion.
// Services are matched by replication controllers' label selector. They are deleted if given
// label selector is targeting only 1 replication controller.
func getServicesForDeletion(client client.Interface, labelSelector labels.Selector,
namespace string) ([]api.Service, error) {
replicationControllers, err := client.ReplicationControllers(namespace).List(unversioned.ListOptions{
LabelSelector: unversioned.LabelSelector{labelSelector},
FieldSelector: unversioned.FieldSelector{fields.Everything()},
})
if err != nil {
return nil, err
}
// if label selector is targeting only 1 replication controller
// then we can delete services targeted by this label selector,
// otherwise we can not delete any services so just return empty list
if len(replicationControllers.Items) != 1 {
return []api.Service{}, nil
}
services, err := client.Services(namespace).List(unversioned.ListOptions{
LabelSelector: unversioned.LabelSelector{labelSelector},
FieldSelector: unversioned.FieldSelector{fields.Everything()},
})
if err != nil {
return nil, err
}
return services.Items, nil
}
......@@ -186,12 +186,20 @@ func GetReplicationControllerDetail(client client.Interface, heapsterClient Heap
return replicationControllerDetail, nil
}
// TODO(floreks): This should be transactional to make sure that RC will not be deleted without
// TODO(floreks): Should related services be deleted also?
// Deletes replication controller with given name in given namespace and related pods
func DeleteReplicationControllerWithPods(client client.Interface, namespace, name string) error {
// TODO(floreks): This should be transactional to make sure that RC will not be deleted without pods
// Deletes replication controller with given name in given namespace and related pods.
// Also deletes services related to replication controller if deleteServices is true.
func DeleteReplicationController(client client.Interface, namespace, name string,
deleteServices bool) error {
log.Printf("Deleting %s replication controller from %s namespace", name, namespace)
if deleteServices {
if err := DeleteReplicationControllerServices(client, namespace, name); err != nil {
return err
}
}
pods, err := getRawReplicationControllerPods(client, namespace, name)
if err != nil {
return err
......@@ -212,6 +220,38 @@ func DeleteReplicationControllerWithPods(client client.Interface, namespace, nam
return nil
}
// Deletes services related to replication controller with given name in given namespace.
func DeleteReplicationControllerServices(client client.Interface, namespace, name string) error {
log.Printf("Deleting services related to %s replication controller from %s namespace", name,
namespace)
replicationController, err := client.ReplicationControllers(namespace).Get(name)
if err != nil {
return err
}
labelSelector, err := toLabelSelector(replicationController.Spec.Selector)
if err != nil {
return err
}
services, err := getServicesForDeletion(client, labelSelector, namespace)
if err != nil {
return err
}
for _, service := range services {
if err := client.Services(namespace).Delete(service.Name); err != nil {
return err
}
}
log.Printf("Successfully deleted services related to %s replication controller from %s namespace",
name, namespace)
return nil
}
// Updates number of replicas in Replication Controller based on Replication Controller Spec
func UpdateReplicasCount(client client.Interface, namespace, name string,
replicationControllerSpec *ReplicationControllerSpec) error {
......
......@@ -155,6 +155,13 @@ backendApi.ReplicationControllerDetail;
*/
backendApi.ReplicationControllerSpec;
/**
* @typedef {{
* deleteServices: boolean
* }}
*/
backendApi.DeleteReplicationControllerSpec;
/**
* @typedef {{
* name: string,
......
......@@ -21,6 +21,14 @@ limitations under the License.
Delete replication controller {{::ctrl.replicationController}} in namespace {{::ctrl.namespace}}.<br>
Pods managed by the replication controller will be also deleted.
</div>
<md-checkbox ng-model="ctrl.deleteServices" class="kd-deletedialog-services-checkbox">
Delete related services
<md-icon class="material-icons kd-deletedialog-info-icon">
info
<md-tooltip>Services with label selector matching only this replication controller
will be deleted</md-tooltip>
</md-icon>
</md-checkbox>
<md-dialog-actions>
<md-button class="md-primary" ng-click="ctrl.cancel()">Cancel</md-button>
<md-button class="md-primary" ng-click="ctrl.remove()">Delete</md-button>
......
......@@ -12,52 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
@import '../variables';
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
)
func TestGetReplicaSetPodInfo(t *testing.T) {
cases := []struct {
controller *api.ReplicationController
pods []api.Pod
expected ReplicationControllerPodInfo
}{
{
&api.ReplicationController{
Status: api.ReplicationControllerStatus{
Replicas: 5,
},
Spec: api.ReplicationControllerSpec{
Replicas: 4,
},
},
[]api.Pod{
{
Status: api.PodStatus{
Phase: api.PodRunning,
},
},
},
ReplicationControllerPodInfo{
Current: 5,
Desired: 4,
Running: 1,
Pending: 0,
Failed: 0,
},
},
}
md-checkbox {
&.kd-deletedialog-services-checkbox {
margin: $baseline-grid 0 0 $baseline-grid;
}
}
for _, c := range cases {
actual := getReplicationControllerPodInfo(c.controller, c.pods)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("getReplicaSetPodInfo(%#v, %#v) == \n%#v\nexpected \n%#v\n",
c.controller, c.pods, actual, c.expected)
}
}
.kd-deletedialog-info-icon {
font-size: $subhead-font-size-base;
height: $subhead-font-size-base;
margin-left: $baseline-grid / 2;
}
......@@ -35,6 +35,9 @@ export default class DeleteReplicationControllerDialogController {
/** @export {string} */
this.namespace = namespace;
/** @export {boolean} */
this.deleteServices = false;
/** @private {!md.$dialog} */
this.mdDialog_ = $mdDialog;
......@@ -50,7 +53,15 @@ export default class DeleteReplicationControllerDialogController {
remove() {
let resource = getReplicationControllerDetailsResource(
new StateParams(this.namespace, this.replicationController), this.resource_);
resource.remove(() => { this.mdDialog_.hide(); }, () => { this.mdDialog_.cancel(); });
/** @type {!backendApi.DeleteReplicationControllerSpec} */
let deleteReplicationControllerSpec = {
deleteServices: this.deleteServices,
};
resource.remove(
deleteReplicationControllerSpec, () => { this.mdDialog_.hide(); },
() => { this.mdDialog_.cancel(); });
}
/**
......
// Copyright 2015 Google Inc. 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 main
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util/sets"
)
func TestGetReplicationControllerPodInfo(t *testing.T) {
cases := []struct {
controller *api.ReplicationController
pods []api.Pod
expected ReplicationControllerPodInfo
}{
{
&api.ReplicationController{
Status: api.ReplicationControllerStatus{
Replicas: 5,
},
Spec: api.ReplicationControllerSpec{
Replicas: 4,
},
},
[]api.Pod{
{
Status: api.PodStatus{
Phase: api.PodRunning,
},
},
},
ReplicationControllerPodInfo{
Current: 5,
Desired: 4,
Running: 1,
Pending: 0,
Failed: 0,
},
},
}
for _, c := range cases {
actual := getReplicationControllerPodInfo(c.controller, c.pods)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("getReplicaSetPodInfo(%#v, %#v) == \n%#v\nexpected \n%#v\n",
c.controller, c.pods, actual, c.expected)
}
}
}
func TestToLabelSelector(t *testing.T) {
requirement, _ := labels.NewRequirement("app", labels.InOperator, sets.NewString("test"))
cases := []struct {
selector map[string]string
expected labels.LabelSelector
}{
{
map[string]string{},
labels.LabelSelector{},
},
{
map[string]string{"app": "test"},
labels.LabelSelector{*requirement},
},
}
for _, c := range cases {
actual, _ := toLabelSelector(c.selector)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("toLabelSelector(%#v) == \n%#v\nexpected \n%#v\n",
c.selector, actual, c.expected)
}
}
}
func TestGetServicesForDeletion(t *testing.T) {
requirement, _ := labels.NewRequirement("app", labels.InOperator, sets.NewString("test"))
cases := []struct {
labelSelector labels.Selector
replicationControllerList *api.ReplicationControllerList
expected *api.ServiceList
expectedActions []string
}{
{
labels.LabelSelector{*requirement},
&api.ReplicationControllerList{
Items: []api.ReplicationController{
{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
},
},
&api.ServiceList{
Items: []api.Service{
{Spec: api.ServiceSpec{Selector: map[string]string{"app": "test"}}},
},
},
[]string{"list", "list"},
},
{
labels.LabelSelector{*requirement},
&api.ReplicationControllerList{
Items: []api.ReplicationController{
{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
},
},
&api.ServiceList{
Items: []api.Service{
{Spec: api.ServiceSpec{Selector: map[string]string{"app": "test"}}},
},
},
[]string{"list"},
},
{
labels.LabelSelector{*requirement},
&api.ReplicationControllerList{},
&api.ServiceList{
Items: []api.Service{
{Spec: api.ServiceSpec{Selector: map[string]string{"app": "test"}}},
},
},
[]string{"list"},
},
}
for _, c := range cases {
fakeClient := testclient.NewSimpleFake(c.replicationControllerList, c.expected)
getServicesForDeletion(fakeClient, c.labelSelector, "mock")
actions := fakeClient.Actions()
if len(actions) != len(c.expectedActions) {
t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions,
len(c.expectedActions), len(actions))
continue
}
for i, verb := range c.expectedActions {
if actions[i].GetVerb() != verb {
t.Errorf("Unexpected action: %+v, expected %s",
actions[i], verb)
}
}
}
}
......@@ -22,6 +22,83 @@ import (
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
)
func TestDeleteReplicationControllerServices(t *testing.T) {
cases := []struct {
namespace, name string
replicationController *api.ReplicationController
replicationControllerList *api.ReplicationControllerList
serviceList *api.ServiceList
expectedActions []string
}{
{
"test-namespace", "test-name",
&api.ReplicationController{
Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
&api.ReplicationControllerList{
Items: []api.ReplicationController{
{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
},
},
&api.ServiceList{
Items: []api.Service{
{Spec: api.ServiceSpec{Selector: map[string]string{"app": "test"}}},
},
},
[]string{"get", "list", "list", "delete"},
},
{
"test-namespace", "test-name",
&api.ReplicationController{
Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
&api.ReplicationControllerList{
Items: []api.ReplicationController{
{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
},
},
&api.ServiceList{
Items: []api.Service{
{Spec: api.ServiceSpec{Selector: map[string]string{"app": "test"}}},
},
},
[]string{"get", "list"},
},
{
"test-namespace", "test-name",
&api.ReplicationController{
Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
&api.ReplicationControllerList{
Items: []api.ReplicationController{
{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"app": "test"}}},
},
},
&api.ServiceList{},
[]string{"get", "list", "list"},
},
}
for _, c := range cases {
fakeClient := testclient.NewSimpleFake(c.replicationController,
c.replicationControllerList, c.serviceList)
DeleteReplicationControllerServices(fakeClient, c.namespace, c.name)
actions := fakeClient.Actions()
if len(actions) != len(c.expectedActions) {
t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions,
len(c.expectedActions), len(actions))
continue
}
for i, verb := range c.expectedActions {
if actions[i].GetVerb() != verb {
t.Errorf("Unexpected action: %+v, expected %s",
actions[i], verb)
}
}
}
}
func TestUpdateReplicasCount(t *testing.T) {
cases := []struct {
namespace, replicationControllerName string
......
......@@ -51,12 +51,28 @@ describe('Delete replication controller dialog controller', () => {
expect(mdDialog.cancel).toHaveBeenCalled();
});
it('should delete', () => {
it('should delete without services', () => {
// given
spyOn(mdDialog, 'hide');
// when
httpBackend.whenDELETE('api/replicationcontrollers/foo-namespace/foo-name').respond(200, {});
httpBackend.whenDELETE('api/replicationcontrollers/foo-namespace/foo-name?deleteServices=false')
.respond(200, {});
ctrl.remove();
httpBackend.flush();
// then
expect(mdDialog.hide).toHaveBeenCalled();
});
it('should delete with services', () => {
// given
spyOn(mdDialog, 'hide');
ctrl.deleteServices = true;
// when
httpBackend.whenDELETE('api/replicationcontrollers/foo-namespace/foo-name?deleteServices=true')
.respond(200, {});
ctrl.remove();
httpBackend.flush();
......@@ -69,7 +85,8 @@ describe('Delete replication controller dialog controller', () => {
spyOn(mdDialog, 'cancel');
// when
httpBackend.whenDELETE('api/replicationcontrollers/foo-namespace/foo-name').respond(503, {});
httpBackend.whenDELETE('api/replicationcontrollers/foo-namespace/foo-name?deleteServices=false')
.respond(503, {});
ctrl.remove();
httpBackend.flush();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册