Merge "[Micro version] Support description for instance"
This commit is contained in:
commit
2e1b80a1f5
@ -28,7 +28,8 @@ LOG = logging.getLogger(__name__)
|
||||
# microversion_support.html
|
||||
MICROVERSION_FEATURES = {
|
||||
"nova": {
|
||||
"locked_attribute": ["2.9", "2.42"]
|
||||
"locked_attribute": ["2.9", "2.42"],
|
||||
"instance_description": ["2.19", "2.42"],
|
||||
},
|
||||
"cinder": {
|
||||
"consistency_groups": ["2.0", "3.10"],
|
||||
|
@ -106,7 +106,7 @@ class Server(base.APIResourceWrapper):
|
||||
|
||||
Preserves the request info so image name can later be retrieved.
|
||||
"""
|
||||
_attrs = ['addresses', 'attrs', 'id', 'image', 'links',
|
||||
_attrs = ['addresses', 'attrs', 'id', 'image', 'links', 'description',
|
||||
'metadata', 'name', 'private_ip', 'public_ip', 'status', 'uuid',
|
||||
'image_name', 'VirtualInterfaces', 'flavor', 'key_name', 'fault',
|
||||
'tenant_id', 'user_id', 'created', 'locked',
|
||||
@ -479,8 +479,11 @@ def server_create(request, name, image, flavor, key_name, user_data,
|
||||
block_device_mapping_v2=None, nics=None,
|
||||
availability_zone=None, instance_count=1, admin_pass=None,
|
||||
disk_config=None, config_drive=None, meta=None,
|
||||
scheduler_hints=None):
|
||||
return Server(novaclient(request).servers.create(
|
||||
scheduler_hints=None, description=None):
|
||||
kwargs = {}
|
||||
if description is not None:
|
||||
kwargs['description'] = description
|
||||
return Server(get_novaclient_with_instance_desc(request).servers.create(
|
||||
name.strip(), image, flavor, userdata=user_data,
|
||||
security_groups=security_groups,
|
||||
key_name=key_name, block_device_mapping=block_device_mapping,
|
||||
@ -488,7 +491,7 @@ def server_create(request, name, image, flavor, key_name, user_data,
|
||||
nics=nics, availability_zone=availability_zone,
|
||||
min_count=instance_count, admin_pass=admin_pass,
|
||||
disk_config=disk_config, config_drive=config_drive,
|
||||
meta=meta, scheduler_hints=scheduler_hints), request)
|
||||
meta=meta, scheduler_hints=scheduler_hints, **kwargs), request)
|
||||
|
||||
|
||||
@profiler.trace
|
||||
@ -501,9 +504,14 @@ def get_novaclient_with_locked_status(request):
|
||||
return novaclient(request, version=microversion)
|
||||
|
||||
|
||||
def get_novaclient_with_instance_desc(request):
|
||||
microversion = get_microversion(request, "instance_description")
|
||||
return novaclient(request, version=microversion)
|
||||
|
||||
|
||||
@profiler.trace
|
||||
def server_get(request, instance_id):
|
||||
return Server(get_novaclient_with_locked_status(request).servers.get(
|
||||
return Server(get_novaclient_with_instance_desc(request).servers.get(
|
||||
instance_id), request)
|
||||
|
||||
|
||||
@ -591,8 +599,9 @@ def server_rebuild(request, instance_id, image_id, password=None,
|
||||
|
||||
|
||||
@profiler.trace
|
||||
def server_update(request, instance_id, name):
|
||||
return novaclient(request).servers.update(instance_id, name=name.strip())
|
||||
def server_update(request, instance_id, name, description=None):
|
||||
return get_novaclient_with_instance_desc(request).servers.update(
|
||||
instance_id, name=name.strip(), description=description)
|
||||
|
||||
|
||||
@profiler.trace
|
||||
|
@ -43,6 +43,17 @@ class Snapshots(generic.View):
|
||||
return result
|
||||
|
||||
|
||||
@urls.register
|
||||
class Features(generic.View):
|
||||
"""API for check if a specified feature is supported."""
|
||||
url_regex = r'nova/features/(?P<name>[^/]+)/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, name):
|
||||
"""Check if a specified feature is supported."""
|
||||
return api.nova.is_feature_available(request, name)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Keypairs(generic.View):
|
||||
"""API for nova keypairs."""
|
||||
@ -300,7 +311,7 @@ class Servers(generic.View):
|
||||
_optional_create = [
|
||||
'block_device_mapping', 'block_device_mapping_v2', 'nics', 'meta',
|
||||
'availability_zone', 'instance_count', 'admin_pass', 'disk_config',
|
||||
'config_drive', 'scheduler_hints'
|
||||
'config_drive', 'scheduler_hints', 'description'
|
||||
]
|
||||
|
||||
@rest_utils.ajax()
|
||||
|
@ -4,6 +4,10 @@
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd data-display="{{ instance.name|default:instance.id }}">{{ instance.name }}</dd>
|
||||
{% if instance.description != None %}
|
||||
<dt>{% trans "Description" %}</dt>
|
||||
<dd>{{ instance.description }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ instance.id }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
|
@ -32,15 +32,19 @@
|
||||
LaunchInstanceDetailsController.$inject = [
|
||||
'$scope',
|
||||
'horizon.framework.widgets.charts.donutChartSettings',
|
||||
'horizon.framework.widgets.charts.quotaChartDefaults'
|
||||
'horizon.framework.widgets.charts.quotaChartDefaults',
|
||||
'horizon.app.core.openstack-service-api.nova'
|
||||
];
|
||||
|
||||
function LaunchInstanceDetailsController($scope,
|
||||
donutChartSettings,
|
||||
quotaChartDefaults
|
||||
quotaChartDefaults,
|
||||
novaAPI
|
||||
) {
|
||||
|
||||
var ctrl = this;
|
||||
novaAPI.isFeatureSupported(
|
||||
'instance_description').then(isDescriptionSupported);
|
||||
|
||||
// Error text for invalid fields
|
||||
ctrl.instanceNameError = gettext('A name is required for your instance.');
|
||||
@ -102,6 +106,10 @@
|
||||
|
||||
////////////////////
|
||||
|
||||
function isDescriptionSupported(data) {
|
||||
ctrl.isDescriptionSupported = data.data;
|
||||
}
|
||||
|
||||
function getMaxInstances() {
|
||||
return $scope.model.novaLimits.maxTotalInstances;
|
||||
}
|
||||
|
@ -23,15 +23,24 @@
|
||||
beforeEach(module('horizon.dashboard.project'));
|
||||
|
||||
describe('LaunchInstanceDetailsController', function() {
|
||||
var scope, ctrl, deferred;
|
||||
var $q, scope, ctrl, deferred;
|
||||
var novaAPI = {
|
||||
isFeatureSupported: function() {
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve({ data: true });
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('horizon.framework.widgets.charts.donutChartSettings', noop);
|
||||
$provide.value('horizon.framework.widgets.charts.quotaChartDefaults', noop);
|
||||
$provide.value('horizon.app.core.openstack-service-api.nova', novaAPI);
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($controller, $rootScope, $q) {
|
||||
scope = $rootScope.$new();
|
||||
beforeEach(inject(function($injector, $controller, _$q_, _$rootScope_) {
|
||||
scope = _$rootScope_.$new();
|
||||
$q = _$q_;
|
||||
deferred = $q.defer();
|
||||
scope.initPromise = deferred.promise;
|
||||
|
||||
@ -47,11 +56,21 @@
|
||||
}
|
||||
};
|
||||
|
||||
novaAPI = $injector.get('horizon.app.core.openstack-service-api.nova');
|
||||
ctrl = $controller('LaunchInstanceDetailsController', { $scope: scope });
|
||||
|
||||
scope.$apply();
|
||||
}));
|
||||
|
||||
it('should have isDescriptionSupported defined', function() {
|
||||
spyOn(novaAPI, 'isFeatureSupported').and.callFake(function () {
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve({ data: true });
|
||||
return deferred.promise;
|
||||
});
|
||||
expect(ctrl.isDescriptionSupported).toBe(true);
|
||||
});
|
||||
|
||||
it('should define error messages for invalid fields', function() {
|
||||
expect(ctrl.instanceNameError).toBeDefined();
|
||||
expect(ctrl.instanceCountError).toBeDefined();
|
||||
|
@ -19,6 +19,12 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.isDescriptionSupported" class="form-group">
|
||||
<label class="control-label" translate for="description">Description</label>
|
||||
<input id="description" name="description" type="text" class="form-control"
|
||||
ng-model="model.newInstanceSpec.description">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" translate for="availability-zone">Availability Zone</label>
|
||||
<div class="horizon-loading-bar" ng-if="!model.loaded.availabilityZones">
|
||||
|
@ -181,6 +181,7 @@
|
||||
availability_zone: null,
|
||||
admin_pass: null,
|
||||
config_drive: false,
|
||||
description: null,
|
||||
// REQUIRED Server Key. Null allowed.
|
||||
user_data: '',
|
||||
disk_config: 'AUTO',
|
||||
|
@ -822,7 +822,7 @@
|
||||
// This is here to ensure that as people add/change items, they
|
||||
// don't forget to implement tests for them.
|
||||
it('has the right number of properties', function() {
|
||||
expect(Object.keys(model.newInstanceSpec).length).toBe(21);
|
||||
expect(Object.keys(model.newInstanceSpec).length).toBe(22);
|
||||
});
|
||||
|
||||
it('sets availability zone to null', function() {
|
||||
@ -833,6 +833,10 @@
|
||||
expect(model.newInstanceSpec.admin_pass).toBeNull();
|
||||
});
|
||||
|
||||
it('sets description to null', function() {
|
||||
expect(model.newInstanceSpec.description).toBeNull();
|
||||
});
|
||||
|
||||
it('sets config drive to false', function() {
|
||||
expect(model.newInstanceSpec.config_drive).toBe(false);
|
||||
});
|
||||
|
@ -42,6 +42,7 @@
|
||||
getConsoleInfo: getConsoleInfo,
|
||||
getServerVolumes: getServerVolumes,
|
||||
getServerSecurityGroups: getServerSecurityGroups,
|
||||
isFeatureSupported: isFeatureSupported,
|
||||
getKeypairs: getKeypairs,
|
||||
createKeypair: createKeypair,
|
||||
getKeypair: getKeypair,
|
||||
@ -84,6 +85,21 @@
|
||||
|
||||
///////////
|
||||
|
||||
// Feature
|
||||
|
||||
/**
|
||||
* @name isFeatureSupported
|
||||
* @description
|
||||
* Check if the feature is supported.
|
||||
* @returns {Object} The result of the API call
|
||||
*/
|
||||
function isFeatureSupported(feature) {
|
||||
return apiService.get('/api/nova/features/' + feature)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to check the Nova service feature.'));
|
||||
});
|
||||
}
|
||||
|
||||
// Nova Services
|
||||
|
||||
/**
|
||||
|
@ -40,6 +40,15 @@
|
||||
});
|
||||
|
||||
var tests = [
|
||||
{
|
||||
"func": "isFeatureSupported",
|
||||
"method": "get",
|
||||
"path": "/api/nova/features/fake",
|
||||
"error": "Unable to check the Nova service feature.",
|
||||
"testInput": [
|
||||
"fake"
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "getServices",
|
||||
"method": "get",
|
||||
|
@ -969,3 +969,12 @@ class NovaRestTestCase(test.TestCase):
|
||||
self.assertEqual(response.content.decode('utf-8'),
|
||||
'"Service Nova is disabled."')
|
||||
nc.tenant_quota_update.assert_not_called()
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_version_get(self, nc):
|
||||
request = self.mock_rest_request()
|
||||
nc.is_feature_available.return_value = True
|
||||
|
||||
response = nova.Features().get(request, 'fake')
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.content.decode('utf-8'), 'true')
|
||||
|
Loading…
Reference in New Issue
Block a user