Merge "Add angular create server group action"

This commit is contained in:
Zuul 2018-03-20 03:38:01 +00:00 committed by Gerrit Code Review
commit 6d3cb836a0
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()
@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
def service_list(request, binary=None):
return novaclient(request).services.list(binary=binary)

View File

@ -431,6 +431,22 @@ class ServerGroups(generic.View):
result = api.nova.server_group_list(request)
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
class ServerMetadata(generic.View):

View File

@ -53,6 +53,7 @@
getServer: getServer,
getServers: getServers,
getServerGroups: getServerGroups,
createServerGroup: createServerGroup,
deleteServer: deleteServer,
pauseServer: pauseServer,
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
* @description

View File

@ -302,6 +302,16 @@
"path": '/api/nova/servergroups/',
"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",
"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
.module('horizon.app.core.server_groups', [
'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')
.run(run)

View File

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

View File

@ -11,6 +11,8 @@
# 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.
import json
from json import loads as to_json
import uuid
@ -365,6 +367,29 @@ class NovaRestTestCase(test.TestCase):
response.json)
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
#

View File

@ -643,3 +643,17 @@ class ComputeApiTests(test.APIMockTestCase):
self.assertIsInstance(ret_val, list)
self.assertEqual(len(ret_val), len(server_groups))
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
extension 'ServerGroups' is available. It displays information about
server groups.
Supported actions: create.