Add angular create server group action

This patch adds create server group action for angular server groups
panel.

Change-Id: Ia4354448dcb42bc5e53e2415084f0569a75a68a3
Partial-Implements: blueprint ng-server-groups
This commit is contained in:
wei.ying 2017-11-04 19:07:32 +08:00
parent ce283b4a31
commit df857f00b5
15 changed files with 501 additions and 2 deletions

View File

@ -931,6 +931,13 @@ def server_group_list(request):
return novaclient(request).server_groups.list() return novaclient(request).server_groups.list()
@profiler.trace
def server_group_create(request, **kwargs):
microversion = get_microversion(request, "servergroup_soft_policies")
return novaclient(request, version=microversion).server_groups.create(
**kwargs)
@profiler.trace @profiler.trace
def service_list(request, binary=None): def service_list(request, binary=None):
return novaclient(request).services.list(binary=binary) return novaclient(request).services.list(binary=binary)

View File

@ -431,6 +431,22 @@ class ServerGroups(generic.View):
result = api.nova.server_group_list(request) result = api.nova.server_group_list(request)
return {'items': [u.to_dict() for u in result]} return {'items': [u.to_dict() for u in result]}
@rest_utils.ajax(data_required=True)
def post(self, request):
"""Create a server group.
Create a server group using parameters supplied in the POST
application/json object. The "name" (string) parameter is required
and the "policies" (array) parameter is required.
This method returns the new server group object on success.
"""
new_servergroup = api.nova.server_group_create(request, **request.DATA)
return rest_utils.CreatedResponse(
'/api/nova/servergroups/%s' % utils_http.urlquote(
new_servergroup.id), new_servergroup.to_dict()
)
@urls.register @urls.register
class ServerMetadata(generic.View): class ServerMetadata(generic.View):

View File

@ -53,6 +53,7 @@
getServer: getServer, getServer: getServer,
getServers: getServers, getServers: getServers,
getServerGroups: getServerGroups, getServerGroups: getServerGroups,
createServerGroup: createServerGroup,
deleteServer: deleteServer, deleteServer: deleteServer,
pauseServer: pauseServer, pauseServer: pauseServer,
unpauseServer: unpauseServer, unpauseServer: unpauseServer,
@ -332,6 +333,28 @@
}); });
} }
/**
* @name createServerGroup
* @description
* Create a new server group. This returns the new server group object on success.
*
* @param {Object} newServerGroup
* The server group to create.
*
* @param {string} newServerGroup.name
* The name of the new server group. Required.
*
* @param {array} newServerGroup.policies
* The policies of the new server group. Required.
* @returns {Object} The result of the API call
*/
function createServerGroup(newServerGroup) {
return apiService.post('/api/nova/servergroups/', newServerGroup)
.error(function () {
toastService.add('error', gettext('Unable to create the server group.'));
});
}
/* /*
* @name deleteServer * @name deleteServer
* @description * @description

View File

@ -302,6 +302,16 @@
"path": '/api/nova/servergroups/', "path": '/api/nova/servergroups/',
"error": 'Unable to retrieve server groups.' "error": 'Unable to retrieve server groups.'
}, },
{
"func": "createServerGroup",
"method": "post",
"path": "/api/nova/servergroups/",
"data": "new server group",
"error": "Unable to create the server group.",
"testInput": [
"new server group"
]
},
{ {
"func": "getExtensions", "func": "getExtensions",
"method": "get", "method": "get",

View File

@ -0,0 +1,56 @@
/*
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc overview
* @ngname horizon.app.core.server_groups.actions
*
* @description
* Provides all of the actions for server groups.
*/
angular
.module('horizon.app.core.server_groups.actions', [
'horizon.framework.conf',
'horizon.app.core.server_groups'
])
.run(registerServerGroupActions);
registerServerGroupActions.$inject = [
'horizon.app.core.server_groups.actions.create.service',
'horizon.app.core.server_groups.resourceType',
'horizon.framework.conf.resource-type-registry.service'
];
function registerServerGroupActions(
createService,
serverGroupResourceTypeCode,
registry
) {
var serverGroupResourceType = registry.getResourceType(serverGroupResourceTypeCode);
serverGroupResourceType.globalActions
.append({
id: 'createServerGroupAction',
service: createService,
template: {
type: 'create',
text: gettext('Create Server Group')
}
});
}
})();

View File

@ -0,0 +1,41 @@
/*
* 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.
*/
(function() {
'use strict';
describe('server groups actions module', function() {
var registry;
beforeEach(module('horizon.app.core.server_groups.actions'));
beforeEach(inject(function($injector) {
registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
}));
it('registers Create Server Group as a global action', function() {
var actions = registry.getResourceType('OS::Nova::ServerGroup').globalActions;
expect(actionHasId(actions, 'createServerGroupAction')).toBe(true);
});
function actionHasId(list, value) {
return list.filter(matchesId).length === 1;
function matchesId(action) {
return action.id === value;
}
}
});
})();

View File

@ -0,0 +1,88 @@
/*
* 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.
*/
(function() {
'use strict';
angular
.module('horizon.app.core.server_groups')
.factory('horizon.app.core.server_groups.actions.create.service', createService);
createService.$inject = [
'horizon.app.core.openstack-service-api.nova',
'horizon.app.core.openstack-service-api.policy',
'horizon.app.core.server_groups.actions.workflow.service',
'horizon.app.core.server_groups.resourceType',
'horizon.framework.widgets.form.ModalFormService',
'horizon.framework.widgets.toast.service',
'horizon.framework.util.actions.action-result.service',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngDoc factory
* @name horizon.app.core.server_groups.actions.create.service
* @Description A service to handle the Create Server Group modal.
*/
function createService(
novaAPI,
policy,
workflow,
resourceType,
modalFormService,
toast,
actionResultService,
gettext
) {
var service = {
allowed: allowed,
perform: perform,
submit: submit
};
return service;
//////////////
function allowed() {
return policy.ifAllowed(
{rules: [['compute', 'os_compute_api:os-server-groups:create']]});
}
function perform() {
var config = workflow.init();
config.title = gettext("Create Server Group");
return modalFormService.open(config).then(submit);
}
function submit(context) {
var data = {name: context.model.name};
// Nova limits only one policy associated with a server group.
data.policies = [context.model.policy];
return novaAPI.createServerGroup(data).then(onSuccess);
}
function onSuccess(response) {
var servergroup = response.data;
toast.add('success', interpolate(
gettext('Server Group %s was successfully created.'), [servergroup.name]));
return actionResultService.getActionResult()
.created(resourceType, servergroup.id)
.result;
}
}
})();

View File

@ -0,0 +1,79 @@
/*
* 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.
*/
(function() {
'use strict';
describe('horizon.app.core.server_groups.actions.create.service', function() {
var $q, $scope, novaAPI, service, modalFormService, policyAPI, resType, toast;
///////////////////////
beforeEach(module('horizon.framework'));
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.app.core.server_groups'));
beforeEach(inject(function($injector, _$rootScope_, _$q_) {
$scope = _$rootScope_.$new();
$q = _$q_;
service = $injector.get('horizon.app.core.server_groups.actions.create.service');
toast = $injector.get('horizon.framework.widgets.toast.service');
modalFormService = $injector.get('horizon.framework.widgets.form.ModalFormService');
novaAPI = $injector.get('horizon.app.core.openstack-service-api.nova');
policyAPI = $injector.get('horizon.app.core.openstack-service-api.policy');
resType = $injector.get('horizon.app.core.server_groups.resourceType');
}));
it('should check the policy if the user is allowed to create server group', function() {
spyOn(policyAPI, 'ifAllowed').and.callThrough();
var allowed = service.allowed();
expect(allowed).toBeTruthy();
expect(policyAPI.ifAllowed).toHaveBeenCalledWith(
{ rules: [['compute', 'os_compute_api:os-server-groups:create']] });
});
it('should open the modal', function() {
spyOn(modalFormService, 'open').and.returnValue($q.defer().promise);
spyOn(novaAPI, 'isFeatureSupported').and.returnValue($q.defer().promise);
service.perform();
$scope.$apply();
expect(modalFormService.open).toHaveBeenCalled();
});
it('should submit create server group request to nova', function() {
var deferred = $q.defer();
spyOn(novaAPI, 'createServerGroup').and.returnValue(deferred.promise);
spyOn(toast, 'add').and.callFake(angular.noop);
var handler = jasmine.createSpyObj('handler', ['success']);
deferred.resolve({data: {name: 'name1', id: '1'}});
service.submit({model: {name: 'karma', policy: 'affinity'}}).then(handler.success);
$scope.$apply();
expect(novaAPI.createServerGroup).toHaveBeenCalledWith(
{name: 'karma', policies: ['affinity']});
expect(toast.add).toHaveBeenCalledWith(
'success', 'Server Group name1 was successfully created.');
expect(handler.success).toHaveBeenCalled();
var result = handler.success.calls.first().args[0];
expect(result.created).toEqual([{type: resType, id: '1'}]);
});
});
})();

View File

@ -0,0 +1,86 @@
/*
* 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.
*/
(function() {
'use strict';
/**
* @ngdoc factory
* @name horizon.app.core.server_groups.actions.workflow.service
* @ngController
*
* @description
* Workflow for creating server group
*/
angular
.module('horizon.app.core.server_groups.actions')
.factory('horizon.app.core.server_groups.actions.workflow.service', ServerGroupWorkflow);
ServerGroupWorkflow.$inject = [
'horizon.app.core.server_groups.service'
];
function ServerGroupWorkflow(serverGroupsService) {
var workflow = {
init: init
};
function init() {
var schema = {
type: 'object',
properties: {
name: {
title: gettext('Name'),
type: 'string'
},
policy: {
title: gettext('Policy'),
type: 'string'
}
},
required: ['name', 'policy']
};
var form = [
"name",
{
key: "policy",
type: "select",
titleMap: []
}
];
var model = {};
var config = {
schema: schema,
form: form,
model: model
};
serverGroupsService.getServerGroupPolicies().then(modifyPolicies);
function modifyPolicies(policies) {
var policyField = config.form[1];
angular.forEach(policies, function(k, v) {
this.push({name: k, value: v});
}, policyField.titleMap);
}
return config;
}
return workflow;
}
})();

View File

@ -0,0 +1,51 @@
/*
* 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.
*/
(function() {
'use strict';
describe('horizon.app.core.server_groups.actions.workflow.service', function() {
var $q, $scope, workflow, service;
beforeEach(module('horizon.framework'));
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.app.core.server_groups'));
beforeEach(inject(function($injector, _$rootScope_, _$q_) {
$scope = _$rootScope_.$new();
$q = _$q_;
workflow = $injector.get('horizon.app.core.server_groups.actions.workflow.service');
service = $injector.get('horizon.app.core.server_groups.service');
}));
function testInitWorkflow() {
var deferred = $q.defer();
spyOn(service, 'getServerGroupPolicies').and.returnValue(deferred.promise);
deferred.resolve({'a1': 'n1'});
var config = workflow.init();
$scope.$apply();
expect(config.schema).toBeDefined();
expect(config.form).toBeDefined();
expect(config.model).toBeDefined();
return config;
}
it('should be create workflow config for creation', function() {
testInitWorkflow();
});
});
})();

View File

@ -26,7 +26,8 @@
angular angular
.module('horizon.app.core.server_groups', [ .module('horizon.app.core.server_groups', [
'horizon.framework.conf', 'horizon.framework.conf',
'horizon.app.core' 'horizon.app.core',
'horizon.app.core.server_groups.actions'
]) ])
.constant('horizon.app.core.server_groups.resourceType', 'OS::Nova::ServerGroup') .constant('horizon.app.core.server_groups.resourceType', 'OS::Nova::ServerGroup')
.run(run) .run(run)

View File

@ -34,7 +34,8 @@
*/ */
function serverGroupsService(nova) { function serverGroupsService(nova) {
return { return {
getServerGroupsPromise: getServerGroupsPromise getServerGroupsPromise: getServerGroupsPromise,
getServerGroupPolicies: getServerGroupPolicies
}; };
/* /*

View File

@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json
from json import loads as to_json from json import loads as to_json
from django.conf import settings from django.conf import settings
@ -363,6 +365,29 @@ class NovaRestTestCase(test.TestCase):
response.json) response.json)
nc.server_group_list.assert_called_once_with(request) nc.server_group_list.assert_called_once_with(request)
@test.create_mocks({api.nova: ['server_group_create']})
def test_server_group_create(self):
req_data = json.dumps({
'name': 'server_group', 'policies': ['affinity']})
self.mock_server_group_create.return_value = mock.Mock(**{
'id': '123',
'to_dict.return_value': {'id': '123',
'name': 'server_group',
'policies': ['affinity']}
})
server_group_data = {'name': 'server_group',
'policies': ['affinity']}
request = self.mock_rest_request(body=req_data)
response = nova.ServerGroups().post(request)
self.assertStatusCode(response, 201)
self.assertEqual('/api/nova/servergroups/123', response['location'])
self.mock_server_group_create.assert_called_once_with(
request, **server_group_data)
# #
# Server Metadata # Server Metadata
# #

View File

@ -643,3 +643,17 @@ class ComputeApiTests(test.APIMockTestCase):
self.assertIsInstance(ret_val, list) self.assertIsInstance(ret_val, list)
self.assertEqual(len(ret_val), len(server_groups)) self.assertEqual(len(ret_val), len(server_groups))
novaclient.server_groups.list.assert_called_once_with() novaclient.server_groups.list.assert_called_once_with()
def test_server_group_create(self):
servergroup = self.server_groups.first()
kwargs = {'name': servergroup.name, 'policies': servergroup.policies}
novaclient = self.stub_novaclient()
self._mock_current_version(novaclient, '2.45')
novaclient.server_groups.create.return_value = servergroup
ret_val = api.nova.server_group_create(self.request, **kwargs)
self.assertEqual(servergroup.name, ret_val.name)
self.assertEqual(servergroup.policies, ret_val.policies)
novaclient.versions.get_current.assert_called_once_with()
novaclient.server_groups.create.assert_called_once_with(**kwargs)

View File

@ -6,3 +6,4 @@ features:
Project->Compute panel group. The panel turns on if Nova API Project->Compute panel group. The panel turns on if Nova API
extension 'ServerGroups' is available. It displays information about extension 'ServerGroups' is available. It displays information about
server groups. server groups.
Supported actions: create.