Add create action to bay panel

Change-Id: I65100b9f2e52b35e727c077a7ed1c75900cd2b8b
Partially Implements: blueprint bay-create
Co-Authored-By: OTSUKA, Yuanying<yuanying@fraction.jp>
This commit is contained in:
shu-mutou 2015-10-16 00:33:49 +01:00
parent 4527b6c7c5
commit 4738b11e07
20 changed files with 569 additions and 16 deletions

View File

@ -85,7 +85,7 @@ def bay_create(request, **kwargs):
else:
raise exceptions.InvalidAttribute(
"Key must be in %s" % ",".join(BAY_CREATE_ATTRS))
return magnumclient(request).bays.create(args)
return magnumclient(request).bays.create(**args)
def bay_update(request, id, patch):

View File

@ -100,8 +100,8 @@ class Bays(generic.View):
"""
new_bay = magnum.bay_create(request, **request.DATA)
return rest_utils.CreatedResponse(
'/api/containers/bay/%s' % new_bay['uuid'],
new_bay)
'/api/containers/bay/%s' % new_bay.uuid,
new_bay.to_dict())
@urls.register

View File

@ -20,6 +20,7 @@
/**
* @ngdoc overview
* @name horizon.dashboard.containers.bay
* @ngModule
*
* @description
* Provides all the services and widgets require to display the bay

View File

@ -0,0 +1,72 @@
/**
* Copyright 2015 Cisco Systems, Inc.
*
* 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.dashboard.containers.bay')
.factory('bayModel', bayModel);
bayModel.$inject = [
'horizon.app.core.openstack-service-api.magnum'
];
function bayModel(magnum) {
var model = {
newBaySpec: {},
// API methods
init: init,
createBay: createBay
};
function initNewBaySpec() {
model.newBaySpec = {
name: null,
baymodel_id: null,
master_count: null,
node_count: null,
discover_url: null,
bay_create_timeout: null
};
}
function init() {
// Reset the new Bay spec
initNewBaySpec();
}
function createBay() {
var finalSpec = angular.copy(model.newBaySpec);
cleanNullProperties(finalSpec);
return magnum.createBay(finalSpec);
}
function cleanNullProperties(finalSpec) {
// Initially clean fields that don't have any value.
for (var key in finalSpec) {
if (finalSpec.hasOwnProperty(key) && finalSpec[key] === null) {
delete finalSpec[key];
}
}
}
return model;
}
})();

View File

@ -0,0 +1,63 @@
/**
* Copyright 2015 Cisco Systems, Inc.
*
* 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.dashboard.containers.bay')
.factory('horizon.dashboard.containers.bay.workflow', bayWorkflow);
bayWorkflow.$inject = [
'horizon.dashboard.containers.basePath',
'horizon.app.core.workflow.factory'
];
function bayWorkflow(basePath, dashboardWorkflow) {
return dashboardWorkflow({
title: gettext('Create Bay'),
steps: [
{
title: gettext('Info'),
templateUrl: basePath + 'bay/create/info/info.html',
helpUrl: basePath + 'bay/create/info/info.help.html',
formName: 'bayInfoForm'
},
{
title: gettext('Size'),
templateUrl: basePath + 'bay/create/size/size.html',
helpUrl: basePath + 'bay/create/size/size.help.html',
formName: 'baySizeForm'
},
{
title: gettext('Misc'),
templateUrl: basePath + 'bay/create/misc/misc.html',
helpUrl: basePath + 'bay/create/misc/misc.help.html',
formName: 'bayMiscForm'
}
],
btnText: {
finish: gettext('Create')
},
btnIcon: {
finish: 'fa fa-cloud-download'
}
});
}
})();

View File

@ -0,0 +1,91 @@
/**
* Copyright 2015 NEC Corporation
*
* 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 controller
* @name createBayInfoController
* @ngController
*
* @description
* Controller for the containers bay info step in create workflow
*/
angular
.module('horizon.dashboard.containers.bay')
.controller('createBayInfoController', createBayInfoController);
createBayInfoController.$inject = [
'$q',
'$scope',
'horizon.dashboard.containers.basePath',
'horizon.app.core.openstack-service-api.magnum'
];
function createBayInfoController($q, $scope, basePath, magnum) {
var ctrl = this;
ctrl.baymodels = [{id:"", name: "Choose a Bay Model"}];
$scope.model.newBaySpec.baymodel_id = "";
$scope.baymodeldetail = {
name: "",
id: "",
coe: "",
image_id: "",
public: "",
registry_enabled: "",
tls_disabled: "",
apiserver_port: ""
};
$scope.changeBayModel = function(){
// show Bay Model Detail
if(!$scope.model.newBaySpec.baymodel_id){
$("#baymodel_detail").hide();
$("#baymodel_detail_none").show();
} else {
angular.forEach(ctrl.baymodels, function(model, idx){
if($scope.model.newBaySpec.baymodel_id === model.id){
$("#baymodel_detail").show();
$("#baymodel_detail_none").hide();
$scope.baymodeldetail.name = model.name;
$scope.baymodeldetail.id = model.id;
$scope.baymodeldetail.coe = model.coe;
$scope.baymodeldetail.image_id = model.image_id;
$scope.baymodeldetail.public = model.public;
$scope.baymodeldetail.registry_enabled = model.registry_enabled;
$scope.baymodeldetail.tls_disabled = model.tls_disabled;
$scope.baymodeldetail.apiserver_port = model.apiserver_port;
}
});
}
};
init();
$("#baymodel_detail").hide();
$("#baymodel_detail_none").show();
function init() {
magnum.getBayModels({paginate: false}).success(onGetBayModels);
}
function onGetBayModels(response) {
Array.prototype.push.apply(ctrl.baymodels, response.items);
}
}
})();

View File

@ -0,0 +1,4 @@
<div>
<h3 translate>Description:</h3>
<p translate>Specify bay name and choose bay model.</p>
</div>

View File

@ -0,0 +1,51 @@
<div ng-controller="createBayInfoController as ctrl">
<h1 translate>Bay Details</h1>
<div class="content">
<div translate class="subtitle">Please provide the name of the Bay.</div>
<div class="form-group">
<div class="form-field bay-name">
<label class="on-top"><translate>Bay Name</translate></label>
<input name="bay-name" type="text" class="form-control input-sm"
ng-model="model.newBaySpec.name"
placeholder="{$ 'Name of the bay to create.'|translate $}">
</div>
<div class="form-field required bay-model">
<label class="on-top"><translate>Bay Model</translate></label>
<select class="form-control" name="bay-model"
ng-model="model.newBaySpec.baymodel_id"
ng-required="true"
ng-options="baymodel.id as baymodel.name for baymodel in ctrl.baymodels"
ng-change="changeBayModel()">
</select>
</div>
</div>
<h2 translate class="section-title">Bay Model Detail</h2>
<div translate class="subtitle" id="baymodel_detail_none">
<translate>Choose a Bay Model</translate>
</div>
<div translate class="subtitle" id="baymodel_detail">
<dl class=dl-horizontal>
<dt><translate>Name</translate></dt>
<dd>{$ baymodeldetail.name $}</dd>
<dt><translate>ID</translate></dt>
<dd>{$ baymodeldetail.id $}</dd>
<dt><translate>COE</translate></dt>
<dd>{$ baymodeldetail.coe $}</dd>
<dt><translate>Image ID</translate></dt>
<dd>{$ baymodeldetail.image_id $}</dd>
<dt><translate>Public</translate></dt>
<dd>{$ baymodeldetail.public $}</dd>
<dt><translate>Registry Enabled</translate></dt>
<dd>{$ baymodeldetail.registry_enabled $}</dd>
<dt><translate>TLS Disabled</translate></dt>
<dd>{$ baymodeldetail.tls_disabled $}</dd>
<dt><translate>API Server Port</translate></dt>
<dd>{$ baymodeldetail.apiserver_port $}</dd>
</dl>
</div>
</div>
</div>

View File

@ -0,0 +1,43 @@
/**
* Copyright 2015 NEC Corporation
*
* 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 controller
* @name createBayMiscController
* @ngController
*
* @description
* Controller for the containers bay misc step in create workflow
*/
angular
.module('horizon.dashboard.containers.bay')
.controller('createBayMiscController', createBayMiscController);
createBayMiscController.$inject = [
'$scope',
'horizon.dashboard.containers.basePath',
'horizon.app.core.openstack-service-api.magnum'
];
function createBayMiscController($scope, basePath, magnum) {
var ctrl = this;
}
})();

View File

@ -0,0 +1,4 @@
<div>
<h3 translate>Description:</h3>
<p translate>Specify conditions for bay creation.</p>
</div>

View File

@ -0,0 +1,23 @@
<div ng-controller="createBayMiscController as ctrl">
<h1 translate>Miscellaneous</h1>
<div class="content">
<div translate class="subtitle">Please provide the miscellaneous for creation of the Bay.</div>
<div class="form-group">
<div class="form-field bay-discovery-url">
<label class="on-top"><translate>Discovery URL</translate></label>
<input name="bay-discovery-url" type="text" class="form-control input-sm"
ng-model="model.newBaySpec.discovery_url"
placeholder="{$ 'Specifies custom discovery url for node discovery.'|translate $}">
</div>
<div class="form-field bay-timeout">
<label class="on-top"><translate>Timeout</translate></label>
<input name="bay-timeout" type="number" ng-pattern="/^[0-9]+$/" class="form-control input-sm"
ng-model="model.newBaySpec.bay_create_timeout"
placeholder="{$ 'The timeout for bay creation in minutes. Set to 0 for no timeout. The default is no timeout.'|translate $}">
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,71 @@
/**
* Copyright 2015 Cisco Systems, Inc.
*
* 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
* @name containersBayModalController
* @ngController
*
* @description
* Controller for the containers bay create modal
*/
angular
.module('horizon.dashboard.containers.bay')
.controller('containersBayModalController', containersBayModalController);
containersBayModalController.$inject = [
'$modal',
'$window',
'horizon.dashboard.containers.basePath',
'horizon.app.core.openstack-service-api.magnum'
];
function containersBayModalController($modal, $window, basePath, magnum) {
var ctrl = this;
ctrl.openBayCreateWizard = openBayCreateWizard;
function openBayCreateWizard(launchContext) {
var options = {
controller: 'ModalContainerController',
backdrop: 'static',
template: '<wizard ng-controller="createBayWizardController"></wizard>"',
windowClass: 'modal-dialog-wizard',
resolve: {
launchContext: function() {
return launchContext;
}
}
};
var launchInstanceModal = $modal.open(options);
var handleModalClose = function (redirectPropertyName) {
return function () {
if (launchContext && launchContext[redirectPropertyName]) {
$window.location.href = launchContext[redirectPropertyName];
}
};
};
launchInstanceModal.result.then(
handleModalClose('successUrl'),
handleModalClose('dismissUrl')
);
}
}
})();

View File

@ -0,0 +1,43 @@
/**
* Copyright 2015 NEC Corporation
*
* 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 controller
* @name createBaySizeController
* @ngController
*
* @description
* Controller for the containers bay size step in create workflow
*/
angular
.module('horizon.dashboard.containers.bay')
.controller('createBaySizeController', createBaySizeController);
createBaySizeController.$inject = [
'$scope',
'horizon.dashboard.containers.basePath',
'horizon.app.core.openstack-service-api.magnum'
];
function createBaySizeController($scope, basePath, magnum) {
var ctrl = this;
}
})();

View File

@ -0,0 +1,4 @@
<div>
<h3 translate>Description:</h3>
<p translate>Specify the number of master nodes and bay nodes for the bay.</p>
</div>

View File

@ -0,0 +1,25 @@
<div ng-controller="createBaySizeController as ctrl">
<h1 translate>Bay Size</h1>
<div class="content">
<div translate class="subtitle">Please provide the count of the master and node in this Bay.</div>
<div class="form-group">
<div class="form-field bay-master-count">
<label class="on-top"><translate>Master Count</translate></label>
<input name="bay-master-count" type="number" ng-pattern="/^[0-9]+$/"
class="form-control input-sm"
ng-model="model.newBaySpec.master_count"
placeholder="{$ 'The bay node count.'|translate $}">
</div>
<div class="form-field bay-node-count">
<label class="on-top"><translate>Node Count</translate></label>
<input name="bay-node-count" type="number" ng-pattern="/^[0-9]+$/"
class="form-control input-sm"
ng-model="model.newBaySpec.node_count"
placeholder="{$ 'The number of master nodes for the bay.'|translate $}">
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,45 @@
/**
* Copyright 2015 Cisco Systems, Inc.
*
* 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
* @name createBayWizardController
* @ngController
*
* @description
* Controller for the containers bay create modal
*/
angular
.module('horizon.dashboard.containers.bay')
.controller('createBayWizardController', createBayWizardController);
createBayWizardController.$inject = [
'$scope',
'bayModel',
'horizon.dashboard.containers.bay.workflow'
];
function createBayWizardController($scope, model, workflow) {
$scope.workflow = workflow;
$scope.model = model;
$scope.model.init();
$scope.submit = $scope.model.createBay;
}
})();

View File

@ -14,7 +14,14 @@
<tr>
<th colspan="100" class="search-header">
<hz-search-bar group-classes="input-group-sm" icon-classes="fa-search">
<action-list class="btn-addon">
<a href="javascipt:void(0);"
class="btn btn-default btn-sm btn-launch ng-scope"
ng-controller="containersBayModalController as modal"
ng-click="modal.openBayCreateWizard({successUrl: '/containers/'})">
<span class="fa fa-plus"> <translate>Create Bay</translate></span>
</a>
</action-list>
<action-list class="btn-addon">
<action
action-classes="'btn btn-default btn-sm btn-danger'"
@ -24,7 +31,6 @@
<translate>Delete Bays</translate>
</action>
</action-list>
</hz-search-bar>
</th>
</tr>
@ -103,14 +109,12 @@
-->
<action-list dropdown>
<action
button-type="split-button"
action-classes="'btn btn-default btn-danger btn-sm'"
callback="table.singleDelete" item="b">
<action button-type="split-button"
action-classes="'btn btn-default btn-sm btn-danger'"
callback="table.singleDelete" item="b">
<translate>Delete</translate>
</action>
</action>
</menu>
</action-list>
</td>
</tr>

View File

@ -27,6 +27,7 @@
function MagnumAPI(apiService, toastService) {
var service = {
createBay: createBay,
getBays: getBays,
deleteBay: deleteBay,
deleteBays: deleteBays,
@ -41,6 +42,13 @@
// Bays //
//////////
function createBay(params) {
return apiService.post('/api/containers/bays/', params)
.error(function() {
toastService.add('error', gettext('Unable to create Bay.'));
});
}
function getBays() {
return apiService.get('/api/containers/bays/')
.error(function() {

View File

@ -77,17 +77,18 @@ class MagnumRestTestCase(test.TestCase):
@mock.patch.object(magnum, 'magnum')
def test_bay_create(self, client):
test_bay = TEST.bays.first()
test_body = json.dumps(test_bay)
test_bays = mock_resource(TEST.bays.list())
test_bay = test_bays[0]
test_body = json.dumps(test_bay.to_dict())
request = self.mock_rest_request(body=test_body)
client.bay_create.return_value = test_bay
response = magnum.Bays().post(request)
self.assertStatusCode(response, 201)
self.assertEqual(response['location'],
'/api/containers/bay/%s' % test_bay['uuid'])
'/api/containers/bay/%s' % test_bay.uuid)
client.bay_create.assert_called_once_with(request,
**test_bay)
**test_bay.to_dict())
@mock.patch.object(magnum, 'magnum')
def test_bay_delete(self, client):