Merge "Add support for multiple swift storage policies"
This commit is contained in:
commit
b62c49acf5
@ -2339,6 +2339,19 @@ from Swift. Do not make it very large (higher than several dozens of Megabytes,
|
|||||||
exact number depends on your connection speed), otherwise you may encounter
|
exact number depends on your connection speed), otherwise you may encounter
|
||||||
socket timeout. The default value is 524288 bytes (or 512 Kilobytes).
|
socket timeout. The default value is 524288 bytes (or 512 Kilobytes).
|
||||||
|
|
||||||
|
|
||||||
|
SWIFT_STORAGE_POLICY_DISPLAY_NAMES
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 18.3.0(Ussuri)
|
||||||
|
|
||||||
|
Default: ``{}``
|
||||||
|
|
||||||
|
A dictionary mapping from the swift storage policy name to an alternate,
|
||||||
|
user friendly display name which will be rendered on the dashboard. If
|
||||||
|
no display is specified for a storage policy, the storage
|
||||||
|
policy name will be used verbatim.
|
||||||
|
|
||||||
Django Settings
|
Django Settings
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
@ -40,6 +40,27 @@ class Info(generic.View):
|
|||||||
return {'info': capabilities}
|
return {'info': capabilities}
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class Policies(generic.View):
|
||||||
|
"""API for information about available container storage policies"""
|
||||||
|
url_regex = r'swift/policies/$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request):
|
||||||
|
"""List available container storage policies"""
|
||||||
|
|
||||||
|
capabilities = api.swift.swift_get_capabilities(request)
|
||||||
|
policies = capabilities['swift']['policies']
|
||||||
|
|
||||||
|
for policy in policies:
|
||||||
|
display_name = \
|
||||||
|
api.swift.get_storage_policy_display_name(policy['name'])
|
||||||
|
if display_name:
|
||||||
|
policy["display_name"] = display_name
|
||||||
|
|
||||||
|
return {'policies': policies}
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
@urls.register
|
||||||
class Containers(generic.View):
|
class Containers(generic.View):
|
||||||
"""API for swift container listing for an account"""
|
"""API for swift container listing for an account"""
|
||||||
@ -83,6 +104,9 @@ class Container(generic.View):
|
|||||||
if 'is_public' in request.DATA:
|
if 'is_public' in request.DATA:
|
||||||
metadata['is_public'] = request.DATA['is_public']
|
metadata['is_public'] = request.DATA['is_public']
|
||||||
|
|
||||||
|
if 'storage_policy' in request.DATA:
|
||||||
|
metadata['storage_policy'] = request.DATA['storage_policy']
|
||||||
|
|
||||||
# This will raise an exception if the container already exists
|
# This will raise an exception if the container already exists
|
||||||
try:
|
try:
|
||||||
api.swift.swift_create_container(request, container,
|
api.swift.swift_create_container(request, container,
|
||||||
|
@ -104,6 +104,13 @@ def _objectify(items, container_name):
|
|||||||
return objects
|
return objects
|
||||||
|
|
||||||
|
|
||||||
|
def get_storage_policy_display_name(name):
|
||||||
|
"""Gets the user friendly display name for a storage policy"""
|
||||||
|
|
||||||
|
display_names = settings.SWIFT_STORAGE_POLICY_DISPLAY_NAMES
|
||||||
|
return display_names.get(name)
|
||||||
|
|
||||||
|
|
||||||
def _metadata_to_header(metadata):
|
def _metadata_to_header(metadata):
|
||||||
headers = {}
|
headers = {}
|
||||||
public = metadata.get('is_public')
|
public = metadata.get('is_public')
|
||||||
@ -114,6 +121,10 @@ def _metadata_to_header(metadata):
|
|||||||
elif public is False:
|
elif public is False:
|
||||||
headers['x-container-read'] = ""
|
headers['x-container-read'] = ""
|
||||||
|
|
||||||
|
storage_policy = metadata.get("storage_policy")
|
||||||
|
if storage_policy:
|
||||||
|
headers["x-storage-policy"] = storage_policy
|
||||||
|
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
@ -175,6 +186,10 @@ def swift_get_container(request, container_name, with_data=True):
|
|||||||
timestamp = None
|
timestamp = None
|
||||||
is_public = False
|
is_public = False
|
||||||
public_url = None
|
public_url = None
|
||||||
|
storage_policy = headers.get("x-storage-policy")
|
||||||
|
storage_policy_display_name = \
|
||||||
|
get_storage_policy_display_name(storage_policy)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
is_public = GLOBAL_READ_ACL in headers.get('x-container-read', '')
|
is_public = GLOBAL_READ_ACL in headers.get('x-container-read', '')
|
||||||
if is_public:
|
if is_public:
|
||||||
@ -194,8 +209,16 @@ def swift_get_container(request, container_name, with_data=True):
|
|||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'data': data,
|
'data': data,
|
||||||
'is_public': is_public,
|
'is_public': is_public,
|
||||||
|
'storage_policy': {
|
||||||
|
"name": storage_policy,
|
||||||
|
},
|
||||||
'public_url': public_url,
|
'public_url': public_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if storage_policy_display_name:
|
||||||
|
container_info['storage_policy']['display_name'] = \
|
||||||
|
get_storage_policy_display_name(storage_policy)
|
||||||
|
|
||||||
return Container(container_info)
|
return Container(container_info)
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,6 +59,9 @@
|
|||||||
$location,
|
$location,
|
||||||
$q) {
|
$q) {
|
||||||
var ctrl = this;
|
var ctrl = this;
|
||||||
|
ctrl.defaultPolicy = '';
|
||||||
|
ctrl.policies = []; // on ctrl scope to be included in tests
|
||||||
|
ctrl.policyOptions = [];
|
||||||
ctrl.model = containersModel;
|
ctrl.model = containersModel;
|
||||||
ctrl.model.initialize();
|
ctrl.model.initialize();
|
||||||
ctrl.baseRoute = baseRoute;
|
ctrl.baseRoute = baseRoute;
|
||||||
@ -85,6 +88,7 @@
|
|||||||
ctrl.createContainer = createContainer;
|
ctrl.createContainer = createContainer;
|
||||||
ctrl.createContainerAction = createContainerAction;
|
ctrl.createContainerAction = createContainerAction;
|
||||||
ctrl.selectContainer = selectContainer;
|
ctrl.selectContainer = selectContainer;
|
||||||
|
ctrl.setDefaultPolicyAndOptions = setDefaultPolicyAndOptions;
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
function checkContainerNameConflict(containerName) {
|
function checkContainerNameConflict(containerName) {
|
||||||
@ -169,6 +173,34 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPolicyOptions() {
|
||||||
|
// get the details for available storage policies
|
||||||
|
swiftAPI.getPolicyDetails().then(setDefaultPolicyAndOptions);
|
||||||
|
return ctrl.policyOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDefaultPolicyAndOptions(data) {
|
||||||
|
ctrl.policies = data.data.policies;
|
||||||
|
ctrl.defaultPolicy = ctrl.policies[0].name; // set the first option as default policy
|
||||||
|
angular.forEach(ctrl.policies, function(policy) {
|
||||||
|
// set the correct default policy as per the API data
|
||||||
|
if (policy.default) {
|
||||||
|
ctrl.defaultPolicy = policy.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayName = policy.name;
|
||||||
|
|
||||||
|
if (policy.display_name) {
|
||||||
|
displayName = policy.display_name + ' (' + policy.name + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl.policyOptions.push({
|
||||||
|
value: policy.name,
|
||||||
|
name: displayName
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var createContainerSchema = {
|
var createContainerSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@ -178,6 +210,11 @@
|
|||||||
pattern: '^[^/]+$',
|
pattern: '^[^/]+$',
|
||||||
description: gettext('Container name must not contain "/".')
|
description: gettext('Container name must not contain "/".')
|
||||||
},
|
},
|
||||||
|
policy: {
|
||||||
|
title: gettext('Storage Policy'),
|
||||||
|
type: 'string',
|
||||||
|
default: ctrl.defaultPolicy
|
||||||
|
},
|
||||||
public: {
|
public: {
|
||||||
title: gettext('Container Access'),
|
title: gettext('Container Access'),
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
@ -186,7 +223,7 @@
|
|||||||
'gain access to your objects in the container.')
|
'gain access to your objects in the container.')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['name']
|
required: ['name', 'policy']
|
||||||
};
|
};
|
||||||
|
|
||||||
var createContainerForm = [
|
var createContainerForm = [
|
||||||
@ -207,6 +244,11 @@
|
|||||||
exists: checkContainerNameConflict
|
exists: checkContainerNameConflict
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'policy',
|
||||||
|
type: 'select',
|
||||||
|
titleMap: getPolicyOptions()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'public',
|
key: 'public',
|
||||||
type: 'radiobuttons',
|
type: 'radiobuttons',
|
||||||
@ -232,13 +274,14 @@
|
|||||||
size: 'md',
|
size: 'md',
|
||||||
helpUrl: basePath + 'create-container.help.html'
|
helpUrl: basePath + 'create-container.help.html'
|
||||||
};
|
};
|
||||||
|
config.schema.properties.policy.default = ctrl.defaultPolicy;
|
||||||
return modalFormService.open(config).then(function then() {
|
return modalFormService.open(config).then(function then() {
|
||||||
return ctrl.createContainerAction(model);
|
return ctrl.createContainerAction(model);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createContainerAction(model) {
|
function createContainerAction(model) {
|
||||||
return swiftAPI.createContainer(model.name, model.public).then(
|
return swiftAPI.createContainer(model.name, model.public, model.policy).then(
|
||||||
function success() {
|
function success() {
|
||||||
toastService.add('success', interpolate(
|
toastService.add('success', interpolate(
|
||||||
gettext('Container %(name)s created.'), model, true
|
gettext('Container %(name)s created.'), model, true
|
||||||
|
@ -44,16 +44,17 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var $q, scope, $location, $rootScope, controller,
|
var $q, scope, $location, $httpBackend, $rootScope, controller,
|
||||||
modalFormService, simpleModal, swiftAPI, toast;
|
modalFormService, simpleModal, swiftAPI, toast;
|
||||||
|
|
||||||
beforeEach(module('horizon.dashboard.project.containers', function($provide) {
|
beforeEach(module('horizon.dashboard.project.containers', function($provide) {
|
||||||
$provide.value('horizon.dashboard.project.containers.containers-model', fakeModel);
|
$provide.value('horizon.dashboard.project.containers.containers-model', fakeModel);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(inject(function ($injector, _$q_, _$rootScope_) {
|
beforeEach(inject(function ($injector, _$httpBackend_, _$q_, _$rootScope_) {
|
||||||
controller = $injector.get('$controller');
|
controller = $injector.get('$controller');
|
||||||
$q = _$q_;
|
$q = _$q_;
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
$location = $injector.get('$location');
|
$location = $injector.get('$location');
|
||||||
$rootScope = _$rootScope_;
|
$rootScope = _$rootScope_;
|
||||||
scope = $rootScope.$new();
|
scope = $rootScope.$new();
|
||||||
@ -105,6 +106,7 @@
|
|||||||
ctrl.toggleAccess(container);
|
ctrl.toggleAccess(container);
|
||||||
|
|
||||||
expect(swiftAPI.setContainerAccess).toHaveBeenCalledWith('spam', true);
|
expect(swiftAPI.setContainerAccess).toHaveBeenCalledWith('spam', true);
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
$rootScope.$apply();
|
$rootScope.$apply();
|
||||||
@ -121,6 +123,7 @@
|
|||||||
ctrl.toggleAccess(container);
|
ctrl.toggleAccess(container);
|
||||||
|
|
||||||
expect(swiftAPI.setContainerAccess).toHaveBeenCalledWith('spam', false);
|
expect(swiftAPI.setContainerAccess).toHaveBeenCalledWith('spam', false);
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
$rootScope.$apply();
|
$rootScope.$apply();
|
||||||
@ -146,6 +149,7 @@
|
|||||||
expect(spec.body).toBeDefined();
|
expect(spec.body).toBeDefined();
|
||||||
expect(spec.submit).toBeDefined();
|
expect(spec.submit).toBeDefined();
|
||||||
expect(spec.cancel).toBeDefined();
|
expect(spec.cancel).toBeDefined();
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
// when the modal is resolved, make sure delete is called
|
// when the modal is resolved, make sure delete is called
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
@ -158,10 +162,12 @@
|
|||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
spyOn(swiftAPI, 'deleteContainer').and.returnValue(deferred.promise);
|
spyOn(swiftAPI, 'deleteContainer').and.returnValue(deferred.promise);
|
||||||
spyOn($location, 'path');
|
spyOn($location, 'path');
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
var ctrl = createController();
|
var ctrl = createController();
|
||||||
ctrl.model.container = {name: 'one'};
|
ctrl.model.container = {name: 'one'};
|
||||||
createController().deleteContainerAction(fakeModel.containers[1]);
|
createController().deleteContainerAction(fakeModel.containers[1]);
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
$rootScope.$apply();
|
$rootScope.$apply();
|
||||||
@ -183,6 +189,8 @@
|
|||||||
ctrl.model.container = {name: 'two'};
|
ctrl.model.container = {name: 'two'};
|
||||||
ctrl.deleteContainerAction(fakeModel.containers[1]);
|
ctrl.deleteContainerAction(fakeModel.containers[1]);
|
||||||
|
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
$rootScope.$apply();
|
$rootScope.$apply();
|
||||||
expect($location.path).toHaveBeenCalledWith('base ham');
|
expect($location.path).toHaveBeenCalledWith('base ham');
|
||||||
@ -203,12 +211,49 @@
|
|||||||
expect(config.schema).toBeDefined();
|
expect(config.schema).toBeDefined();
|
||||||
expect(config.form).toBeDefined();
|
expect(config.form).toBeDefined();
|
||||||
|
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
// when the modal is resolved, make sure create is called
|
// when the modal is resolved, make sure create is called
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
$rootScope.$apply();
|
$rootScope.$apply();
|
||||||
expect(ctrl.createContainerAction).toHaveBeenCalledWith({public: false});
|
expect(ctrl.createContainerAction).toHaveBeenCalledWith({public: false});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should preselect default policy in create container dialog', function test() {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
spyOn(modalFormService, 'open').and.returnValue(deferred.promise);
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond();
|
||||||
|
|
||||||
|
var ctrl = createController();
|
||||||
|
var policyOptions = {
|
||||||
|
data: {
|
||||||
|
policies: [
|
||||||
|
{
|
||||||
|
name: 'nz--o1--mr-r3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
display_name: 'Single Region nz-por-1',
|
||||||
|
default: true,
|
||||||
|
name: 'nz-por-1--o1--sr-r3'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ctrl.setDefaultPolicyAndOptions(policyOptions);
|
||||||
|
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond();
|
||||||
|
|
||||||
|
ctrl.createContainer();
|
||||||
|
expect(modalFormService.open).toHaveBeenCalled();
|
||||||
|
var config = modalFormService.open.calls.mostRecent().args[0];
|
||||||
|
expect(config.schema.properties.policy.default).toBe(
|
||||||
|
'nz-por-1--o1--sr-r3'
|
||||||
|
);
|
||||||
|
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
it('should check for container existence - with presence', function test() {
|
it('should check for container existence - with presence', function test() {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
spyOn(swiftAPI, 'getContainer').and.returnValue(deferred.promise);
|
spyOn(swiftAPI, 'getContainer').and.returnValue(deferred.promise);
|
||||||
@ -221,6 +266,7 @@
|
|||||||
d.then(function result() { resolved = true; }, function () { rejected = true; });
|
d.then(function result() { resolved = true; }, function () { rejected = true; });
|
||||||
|
|
||||||
expect(swiftAPI.getContainer).toHaveBeenCalledWith('spam', true);
|
expect(swiftAPI.getContainer).toHaveBeenCalledWith('spam', true);
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
// we found something
|
// we found something
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
@ -240,6 +286,7 @@
|
|||||||
d.then(function result() { resolved = true; }, function () { rejected = true; });
|
d.then(function result() { resolved = true; }, function () { rejected = true; });
|
||||||
|
|
||||||
expect(swiftAPI.getContainer).toHaveBeenCalledWith('spam', true);
|
expect(swiftAPI.getContainer).toHaveBeenCalledWith('spam', true);
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
// we did not find something
|
// we did not find something
|
||||||
deferred.reject();
|
deferred.reject();
|
||||||
@ -261,6 +308,7 @@
|
|||||||
spyOn(swiftAPI, 'createContainer').and.returnValue(deferred.promise);
|
spyOn(swiftAPI, 'createContainer').and.returnValue(deferred.promise);
|
||||||
|
|
||||||
createController().createContainerAction({name: 'spam', public: true});
|
createController().createContainerAction({name: 'spam', public: true});
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
$rootScope.$apply();
|
$rootScope.$apply();
|
||||||
@ -271,6 +319,7 @@
|
|||||||
|
|
||||||
it('should call getContainers when filters change', function test() {
|
it('should call getContainers when filters change', function test() {
|
||||||
spyOn(fakeModel, 'getContainers').and.callThrough();
|
spyOn(fakeModel, 'getContainers').and.callThrough();
|
||||||
|
$httpBackend.expectGET('/dashboard/api/swift/policies/').respond({});
|
||||||
var ctrl = createController();
|
var ctrl = createController();
|
||||||
ctrl.filterEventTrigeredBySearchBar = true;
|
ctrl.filterEventTrigeredBySearchBar = true;
|
||||||
scope.cc = ctrl;
|
scope.cc = ctrl;
|
||||||
|
@ -55,6 +55,15 @@
|
|||||||
<span class="hz-object-label col-lg-7 col-md-12" translate>Date Created</span>
|
<span class="hz-object-label col-lg-7 col-md-12" translate>Date Created</span>
|
||||||
<span class="hz-object-val col-lg-5 col-md-12">{$ container.timestamp | date $}</span>
|
<span class="hz-object-val col-lg-5 col-md-12">{$ container.timestamp | date $}</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li ng-if="container.storage_policy.display_name" class="hz-object-policy row">
|
||||||
|
<span class="hz-object-label col-lg-7 col-md-12" translate>Storage Policy</span>
|
||||||
|
<span class="hz-object-val col-lg-5 col-md-12">{$ container.storage_policy.display_name $}</span>
|
||||||
|
<span class="hz-object-val col-lg-offset-7 col-lg-5 col-md-12">({$ container.storage_policy.name $})</span>
|
||||||
|
</li>
|
||||||
|
<li ng-if="!container.storage_policy.display_name" class="hz-object-policy row">
|
||||||
|
<span class="hz-object-label col-lg-7 col-md-12" translate>Storage Policy</span>
|
||||||
|
<span class="hz-object-val col-lg-5 col-md-12">{$ container.storage_policy.name $}</span>
|
||||||
|
</li>
|
||||||
<li class="hz-object-link row">
|
<li class="hz-object-link row">
|
||||||
<div class="themable-checkbox col-lg-7 col-md-12">
|
<div class="themable-checkbox col-lg-7 col-md-12">
|
||||||
<input type="checkbox" id="id_access" ng-model="container.is_public"
|
<input type="checkbox" id="id_access" ng-model="container.is_public"
|
||||||
|
@ -322,6 +322,10 @@ SHOW_OPENSTACK_CLOUDS_YAML = True
|
|||||||
# The size of chunk in bytes for downloading objects from Swift
|
# The size of chunk in bytes for downloading objects from Swift
|
||||||
SWIFT_FILE_TRANSFER_CHUNK_SIZE = 512 * 1024
|
SWIFT_FILE_TRANSFER_CHUNK_SIZE = 512 * 1024
|
||||||
|
|
||||||
|
# Mapping from actual storage policy name to user friendly
|
||||||
|
# name to be rendered.
|
||||||
|
SWIFT_STORAGE_POLICY_DISPLAY_NAMES = {}
|
||||||
|
|
||||||
# NOTE: The default value of USER_MENU_LINKS will be set after loading
|
# NOTE: The default value of USER_MENU_LINKS will be set after loading
|
||||||
# local_settings if it is not configured.
|
# local_settings if it is not configured.
|
||||||
USER_MENU_LINKS = None
|
USER_MENU_LINKS = None
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
getObjectDetails:getObjectDetails,
|
getObjectDetails:getObjectDetails,
|
||||||
getObjects: getObjects,
|
getObjects: getObjects,
|
||||||
getObjectURL: getObjectURL,
|
getObjectURL: getObjectURL,
|
||||||
|
getPolicyDetails: getPolicyDetails,
|
||||||
setContainerAccess: setContainerAccess,
|
setContainerAccess: setContainerAccess,
|
||||||
uploadObject: uploadObject
|
uploadObject: uploadObject
|
||||||
};
|
};
|
||||||
@ -89,6 +90,23 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name getPolicyDetails
|
||||||
|
* @description
|
||||||
|
* Fetch all the storage policy details with display names and storage values.
|
||||||
|
*
|
||||||
|
* @returns {Object} The result of the object passed to the Swift /policies call.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function getPolicyDetails() {
|
||||||
|
return apiService.get('/api/swift/policies/').error(function() {
|
||||||
|
toastService.add(
|
||||||
|
'error',
|
||||||
|
gettext('Unable to fetch the policy details.')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name getContainers
|
* @name getContainers
|
||||||
* @description
|
* @description
|
||||||
@ -135,8 +153,8 @@
|
|||||||
* @returns {Object} The result of the creation call
|
* @returns {Object} The result of the creation call
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function createContainer(container, isPublic) {
|
function createContainer(container, isPublic, policy) {
|
||||||
var data = {is_public: false};
|
var data = {is_public: false, storage_policy: policy};
|
||||||
|
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
data.is_public = true;
|
data.is_public = true;
|
||||||
|
@ -61,28 +61,34 @@
|
|||||||
testInput: [ 'spam' ]
|
testInput: [ 'spam' ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
func: 'createContainer',
|
func: 'getPolicyDetails',
|
||||||
method: 'post',
|
method: 'get',
|
||||||
path: '/api/swift/containers/new-spam/metadata/',
|
path: '/api/swift/policies/',
|
||||||
data: {is_public: false},
|
error: 'Unable to fetch the policy details.'
|
||||||
error: 'Unable to create the container.',
|
|
||||||
testInput: [ 'new-spam' ]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
func: 'createContainer',
|
func: 'createContainer',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
path: '/api/swift/containers/new-spam/metadata/',
|
path: '/api/swift/containers/new-spam/metadata/',
|
||||||
data: {is_public: false},
|
data: {is_public: false, storage_policy: 'nz--o1--mr-r3'},
|
||||||
error: 'Unable to create the container.',
|
error: 'Unable to create the container.',
|
||||||
testInput: [ 'new-spam', false ]
|
testInput: [ 'new-spam', false, 'nz--o1--mr-r3' ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
func: 'createContainer',
|
func: 'createContainer',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
path: '/api/swift/containers/new-spam/metadata/',
|
path: '/api/swift/containers/new-spam/metadata/',
|
||||||
data: {is_public: true},
|
data: {is_public: false, storage_policy: 'nz--o1--mr-r3'},
|
||||||
error: 'Unable to create the container.',
|
error: 'Unable to create the container.',
|
||||||
testInput: [ 'new-spam', true ]
|
testInput: [ 'new-spam', false, 'nz--o1--mr-r3' ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
func: 'createContainer',
|
||||||
|
method: 'post',
|
||||||
|
path: '/api/swift/containers/new-spam/metadata/',
|
||||||
|
data: {is_public: true, storage_policy: 'nz--o1--mr-r3'},
|
||||||
|
error: 'Unable to create the container.',
|
||||||
|
testInput: [ 'new-spam', true, 'nz--o1--mr-r3' ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
func: 'deleteContainer',
|
func: 'deleteContainer',
|
||||||
|
@ -34,6 +34,43 @@ class SwiftRestTestCase(test.TestCase):
|
|||||||
response.json)
|
response.json)
|
||||||
self.mock_swift_get_capabilities.assert_called_once_with(request)
|
self.mock_swift_get_capabilities.assert_called_once_with(request)
|
||||||
|
|
||||||
|
@test.create_mocks({api.swift: ['swift_get_capabilities',
|
||||||
|
'get_storage_policy_display_name']})
|
||||||
|
def test_policies_get(self):
|
||||||
|
request = self.mock_rest_request()
|
||||||
|
self.mock_get_storage_policy_display_name.side_effect = [
|
||||||
|
"Multi Region", None]
|
||||||
|
self.mock_swift_get_capabilities.return_value = {
|
||||||
|
'swift': {
|
||||||
|
'policies': [
|
||||||
|
{
|
||||||
|
"aliases": "nz--o1--mr-r3",
|
||||||
|
"default": True,
|
||||||
|
"name": "nz--o1--mr-r3"
|
||||||
|
}, {
|
||||||
|
"aliases": "another-policy",
|
||||||
|
"name": "some-other-policy"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = swift.Policies().get(request)
|
||||||
|
self.assertStatusCode(response, 200)
|
||||||
|
self.assertEqual({
|
||||||
|
'policies': [
|
||||||
|
{
|
||||||
|
"aliases": "nz--o1--mr-r3",
|
||||||
|
"default": True,
|
||||||
|
"name": "nz--o1--mr-r3",
|
||||||
|
"display_name": "Multi Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliases": "another-policy",
|
||||||
|
"name": "some-other-policy"
|
||||||
|
}
|
||||||
|
]}, response.json)
|
||||||
|
self.mock_swift_get_capabilities.assert_called_once_with(request)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Containers
|
# Containers
|
||||||
#
|
#
|
||||||
|
@ -95,6 +95,34 @@ class SwiftApiTests(test.APIMockTestCase):
|
|||||||
|
|
||||||
swift_api.head_container.assert_called_once_with(container.name)
|
swift_api.head_container.assert_called_once_with(container.name)
|
||||||
|
|
||||||
|
def test_swift_create_container_with_storage_policy(self, mock_swiftclient):
|
||||||
|
metadata = {'is_public': True, 'storage_policy': 'nz-o1-mr-r3'}
|
||||||
|
container = self.containers.first()
|
||||||
|
swift_api = mock_swiftclient.return_value
|
||||||
|
swift_api.head_container.return_value = container
|
||||||
|
|
||||||
|
headers = api.swift._metadata_to_header(metadata=(metadata))
|
||||||
|
self.assertEqual(headers["x-storage-policy"], 'nz-o1-mr-r3')
|
||||||
|
|
||||||
|
with self.assertRaises(exceptions.AlreadyExists):
|
||||||
|
api.swift.swift_create_container(self.request,
|
||||||
|
container.name,
|
||||||
|
metadata=(metadata))
|
||||||
|
|
||||||
|
swift_api.head_container.assert_called_once_with(container.name)
|
||||||
|
|
||||||
|
def test_metadata_to_headers(self, mock_swiftclient):
|
||||||
|
metadata = {'is_public': True, 'storage_policy': 'nz-o1-mr-r3'}
|
||||||
|
headers = api.swift._metadata_to_header(metadata=(metadata))
|
||||||
|
self.assertEqual(headers["x-storage-policy"], 'nz-o1-mr-r3')
|
||||||
|
self.assertEqual(headers["x-container-read"], '.r:*,.rlistings')
|
||||||
|
|
||||||
|
def test_metadata_to_headers_without_metadata(self, mock_swiftclient):
|
||||||
|
metadata = {}
|
||||||
|
headers = api.swift._metadata_to_header(metadata=(metadata))
|
||||||
|
self.assertNotIn("x-storage-policy", headers)
|
||||||
|
self.assertNotIn("x-container-read", headers)
|
||||||
|
|
||||||
def test_swift_update_container(self, mock_swiftclient):
|
def test_swift_update_container(self, mock_swiftclient):
|
||||||
metadata = {'is_public': True}
|
metadata = {'is_public': True}
|
||||||
container = self.containers.first()
|
container = self.containers.first()
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds options to gui to allow user to select which storage policy container
|
||||||
|
will use and displays the container's storage policy in the container
|
||||||
|
information.
|
Loading…
Reference in New Issue
Block a user