Merge "Add ability to configure read access of container"

This commit is contained in:
Jenkins 2013-12-02 12:38:05 +00:00 committed by Gerrit Code Review
commit a575d5ea05
10 changed files with 240 additions and 15 deletions

View File

@ -843,8 +843,8 @@ class DataTableMetaclass(type):
# If the table is in a ResourceBrowser, the column number must meet
# these limits because of the width of the browser.
if opts.browser_table == "navigation" and len(columns) > 1:
raise ValueError("You can only assign one column to %s."
if opts.browser_table == "navigation" and len(columns) > 3:
raise ValueError("You can only assign three column to %s."
% class_name)
if opts.browser_table == "content" and len(columns) > 2:
raise ValueError("You can only assign two columns to %s."

View File

@ -19,6 +19,7 @@
# under the License.
import logging
import urllib
import swiftclient
@ -34,6 +35,9 @@ from openstack_dashboard.openstack.common import timeutils
LOG = logging.getLogger(__name__)
FOLDER_DELIMITER = "/"
# Swift ACL
GLOBAL_READ_ACL = ".r:*"
LIST_CONTENTS_ACL = ".rlistings"
class Container(base.APIDictWrapper):
@ -90,6 +94,19 @@ def _objectify(items, container_name):
return objects
def _metadata_to_header(metadata):
headers = {}
public = metadata.get('is_public')
if public is True:
public_container_acls = [GLOBAL_READ_ACL, LIST_CONTENTS_ACL]
headers['x-container-read'] = ",".join(public_container_acls)
elif public is False:
headers['x-container-read'] = ""
return headers
def swift_api(request):
endpoint = base.url_for(request, 'object-store')
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
@ -139,7 +156,15 @@ def swift_get_container(request, container_name, with_data=True):
data = None
headers = swift_api(request).head_container(container_name)
timestamp = None
is_public = False
public_url = None
try:
is_public = GLOBAL_READ_ACL in headers.get('x-container-read', '')
if is_public:
swift_endpoint = base.url_for(request,
'object-store',
endpoint_type='publicURL')
public_url = swift_endpoint + '/' + urllib.quote(container_name)
ts_float = float(headers.get('x-timestamp'))
timestamp = timeutils.iso8601_from_timestamp(ts_float)
except Exception:
@ -150,14 +175,23 @@ def swift_get_container(request, container_name, with_data=True):
'container_bytes_used': headers.get('x-container-bytes-used'),
'timestamp': timestamp,
'data': data,
'is_public': is_public,
'public_url': public_url,
}
return Container(container_info)
def swift_create_container(request, name):
def swift_create_container(request, name, metadata=({})):
if swift_container_exists(request, name):
raise exceptions.AlreadyExists(name, 'container')
swift_api(request).put_container(name)
headers = _metadata_to_header(metadata)
swift_api(request).put_container(name, headers=headers)
return Container({'name': name})
def swift_update_container(request, name, metadata=({})):
headers = _metadata_to_header(metadata)
swift_api(request).post_container(name, headers=headers)
return Container({'name': name})

View File

@ -37,18 +37,30 @@ no_slash_validator = validators.RegexValidator(r'^(?u)[^/]+$',
class CreateContainer(forms.SelfHandlingForm):
ACCESS_CHOICES = (
("private", _("Private")),
("public", _("Public")),
)
parent = forms.CharField(max_length=255,
required=False,
widget=forms.HiddenInput)
name = forms.CharField(max_length=255,
label=_("Container Name"),
validators=[no_slash_validator])
access = forms.ChoiceField(label=_("Container Access"),
required=True,
choices=ACCESS_CHOICES)
def handle(self, request, data):
try:
if not data['parent']:
is_public = data["access"] == "public"
metadata = ({'is_public': is_public})
# Create a container
api.swift.swift_create_container(request, data["name"])
api.swift.swift_create_container(request,
data["name"],
metadata=metadata)
messages.success(request, _("Container created successfully."))
else:
# Create a pseudo-folder

View File

@ -13,18 +13,28 @@
# 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 logging
from django.core.urlresolvers import reverse # noqa
from django.template.defaultfilters import filesizeformat # noqa
from django import shortcuts
from django import template
from django.template import defaultfilters as filters
from django.utils import http
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _ # noqa
from horizon import exceptions
from horizon import messages
from horizon import tables
from openstack_dashboard import api
from openstack_dashboard.api import swift
LOG = logging.getLogger(__name__)
LOADING_IMAGE = '<img src="/static/dashboard/img/loading.gif" />'
def wrap_delimiter(name):
if name and not name.endswith(swift.FOLDER_DELIMITER):
return name + swift.FOLDER_DELIMITER
@ -43,6 +53,58 @@ class ViewContainer(tables.LinkAction):
return reverse(self.url, args=args)
class MakePublicContainer(tables.Action):
name = "make_public"
verbose_name = _("Make Public")
classes = ("btn-edit", )
def allowed(self, request, container):
# Container metadata have not been loaded
if not hasattr(container, 'is_public'):
return False
return not container.is_public
def single(self, table, request, obj_id):
try:
api.swift.swift_update_container(request,
obj_id,
metadata=({'is_public': True}))
LOG.info('Updating container "%s" access to public.' % obj_id)
messages.success(request,
_('Successfully updated container access to '
'public.'))
except Exception:
exceptions.handle(request,
_('Unable to update container access.'))
return shortcuts.redirect('horizon:project:containers:index')
class MakePrivateContainer(tables.Action):
name = "make_private"
verbose_name = _("Make Private")
classes = ("btn-edit", )
def allowed(self, request, container):
# Container metadata have not been loaded
if not hasattr(container, 'is_public'):
return False
return container.is_public
def single(self, table, request, obj_id):
try:
api.swift.swift_update_container(request,
obj_id,
metadata=({'is_public': False}))
LOG.info('Updating container "%s" access to private.' % obj_id)
messages.success(request,
_('Successfully updated container access to '
'private.'))
except Exception:
exceptions.handle(request,
_('Unable to update container access.'))
return shortcuts.redirect('horizon:project:containers:index')
class DeleteContainer(tables.DeleteAction):
data_type_singular = _("Container")
data_type_plural = _("Containers")
@ -141,7 +203,7 @@ class UploadObject(tables.LinkAction):
def get_size_used(container):
return filesizeformat(container.bytes)
return filters.filesizeformat(container.bytes)
def get_container_link(container):
@ -149,16 +211,53 @@ def get_container_link(container):
args=(http.urlquote(wrap_delimiter(container.name)),))
class ContainerAjaxUpdateRow(tables.Row):
ajax = True
def get_data(self, request, container_name):
container = api.swift.swift_get_container(request, container_name)
return container
def get_metadata(container):
# If the metadata has not been loading, display a loading image
if not hasattr(container, 'is_public'):
return safestring.mark_safe(LOADING_IMAGE)
template_name = 'project/containers/_container_metadata.html'
context = {"container": container}
return template.loader.render_to_string(template_name, context)
def get_metadata_loaded(container):
# Determine if metadata has been loaded if the attribute is already set.
return hasattr(container, 'is_public') and container.is_public is not None
class ContainersTable(tables.DataTable):
METADATA_LOADED_CHOICES = (
(False, None),
(True, True),
)
name = tables.Column("name",
link=get_container_link,
verbose_name=_("Container Name"))
metadata = tables.Column(get_metadata,
verbose_name=_("Container Details"),
classes=('nowrap-col', ),)
metadata_loaded = tables.Column(get_metadata_loaded,
verbose_name=_("Metadata Loaded"),
status=True,
status_choices=METADATA_LOADED_CHOICES,
hidden=True)
class Meta:
name = "containers"
verbose_name = _("Containers")
row_class = ContainerAjaxUpdateRow
status_columns = ['metadata_loaded', ]
table_actions = (CreateContainer,)
row_actions = (ViewContainer, DeleteContainer,)
row_actions = (ViewContainer, MakePublicContainer,
MakePrivateContainer, DeleteContainer,)
browser_table = "navigation"
footer = False
@ -266,7 +365,7 @@ def sanitize_name(name):
def get_size(obj):
if obj.bytes:
return filesizeformat(obj.bytes)
return filters.filesizeformat(obj.bytes)
def get_link_subfolder(subfolder):

View File

@ -9,6 +9,14 @@
<dl>
<dt>{% trans "Container Name" %}</dt>
<dd>{{ container.name }}</dd>
<dt>{% trans "Container Access" %}</dt>
{% if container.public_url %}
<dd>{% trans "Public" %}</dd>
<dt>{% trans "Public URL" %}</dt>
<dd>{{ container.public_url }}</dd>
{% else %}
<dd>{% trans "Private" %}</dd>
{% endif %}
<dt>{% trans "Object Count" %}</dt>
<dd>{{ container.container_object_count }}</dd>
<dt>{% trans "Size" %}</dt>

View File

@ -0,0 +1,12 @@
{% load i18n %}
<ul>
<li>{% trans "Object Count: " %}{{ container.container_object_count }}</li>
<li>{% trans "Size: " %}: {{ container.container_bytes_used|filesizeformat }}</li>
<li>{% trans "Access: " %}
{% if container.public_url %}
<a href="{{ container.public_url }}">{% trans "Public" %}</a>
{% else %}
{% trans "Private" %}
{% endif %}
</li>
</ul>

View File

@ -16,6 +16,7 @@
<div class="right">
<h3>{% trans "Description" %}:</h3>
<p>{% trans "A container is a storage compartment for your data and provides a way for you to organize your data. You can think of a container as a folder in Windows &reg; or a directory in UNIX &reg;. The primary difference between a container and these other file system concepts is that containers cannot be nested. You can, however, create an unlimited number of containers within your account. Data must be stored in a container so you must have at least one container defined in your account prior to uploading data." %}</p>
<p>{% trans "Note: A Public Container will allow anyone with the Public URL to gain access to your objects in the container." %}</p>
</div>
{% endblock %}

View File

@ -98,10 +98,12 @@ class SwiftTests(test.TestCase):
for container in self.containers.list():
self.mox.ResetAll() # mandatory in a for loop
api.swift.swift_create_container(IsA(http.HttpRequest),
container.name)
container.name,
metadata=({'is_public': False}))
self.mox.ReplayAll()
formData = {'name': container.name,
'access': "private",
'method': forms.CreateContainer.__name__}
res = self.client.post(
reverse('horizon:project:containers:create'), formData)
@ -110,6 +112,36 @@ class SwiftTests(test.TestCase):
url = reverse('horizon:project:containers:index', args=args)
self.assertRedirectsNoFollow(res, url)
@test.create_stubs({api.swift: ('swift_update_container', )})
def test_update_container_to_public(self):
container = self.containers.get(name=u"container_one%\u6346")
api.swift.swift_update_container(IsA(http.HttpRequest),
container.name,
metadata=({'is_public': True}))
self.mox.ReplayAll()
action_string = u"containers__make_public__%s" % container.name
form_data = {"action": action_string}
req = self.factory.post(CONTAINER_INDEX_URL, form_data)
table = tables.ContainersTable(req, self.containers.list())
handled = table.maybe_handle()
self.assertEqual(handled['location'], CONTAINER_INDEX_URL)
@test.create_stubs({api.swift: ('swift_update_container', )})
def test_update_container_to_private(self):
container = self.containers.get(name=u"container_two\u6346")
api.swift.swift_update_container(IsA(http.HttpRequest),
container.name,
metadata=({'is_public': False}))
self.mox.ReplayAll()
action_string = u"containers__make_private__%s" % container.name
form_data = {"action": action_string}
req = self.factory.post(CONTAINER_INDEX_URL, form_data)
table = tables.ContainersTable(req, self.containers.list())
handled = table.maybe_handle()
self.assertEqual(handled['location'], CONTAINER_INDEX_URL)
@test.create_stubs({api.swift: ('swift_get_containers',
'swift_get_objects')})
def test_index_container_selected(self):

View File

@ -67,24 +67,45 @@ class SwiftApiTests(test.APITestCase):
self.assertIsNone(cont.data)
def test_swift_create_duplicate_container(self):
metadata = {'is_public': False}
container = self.containers.first()
headers = api.swift._metadata_to_header(metadata=(metadata))
swift_api = self.stub_swiftclient(expected_calls=2)
# Check for existence, then create
exc = self.exceptions.swift
swift_api.head_container(container.name).AndRaise(exc)
swift_api.put_container(container.name).AndReturn(container)
swift_api.put_container(container.name, headers=headers) \
.AndReturn(container)
self.mox.ReplayAll()
# Verification handled by mox, no assertions needed.
api.swift.swift_create_container(self.request, container.name)
api.swift.swift_create_container(self.request,
container.name,
metadata=(metadata))
def test_swift_create_container(self):
metadata = {'is_public': True}
container = self.containers.first()
swift_api = self.stub_swiftclient()
swift_api.head_container(container.name).AndReturn(container)
self.mox.ReplayAll()
# Verification handled by mox, no assertions needed.
with self.assertRaises(exceptions.AlreadyExists):
api.swift.swift_create_container(self.request, container.name)
api.swift.swift_create_container(self.request,
container.name,
metadata=(metadata))
def test_swift_update_container(self):
metadata = {'is_public': True}
container = self.containers.first()
swift_api = self.stub_swiftclient()
headers = api.swift._metadata_to_header(metadata=(metadata))
swift_api.post_container(container.name, headers=headers)\
.AndReturn(container)
self.mox.ReplayAll()
# Verification handled by mox, no assertions needed.
api.swift.swift_update_container(self.request,
container.name,
metadata=(metadata))
def test_swift_get_objects(self):
container = self.containers.first()

View File

@ -26,12 +26,18 @@ def data(TEST):
container_dict_1 = {"name": u"container_one%\u6346",
"container_object_count": 2,
"container_bytes_used": 256,
"timestamp": timeutils.isotime()}
"timestamp": timeutils.isotime(),
"is_public": False,
"public_url": ""}
container_1 = swift.Container(container_dict_1)
container_dict_2 = {"name": u"container_two\u6346",
"container_object_count": 4,
"container_bytes_used": 1024,
"timestamp": timeutils.isotime()}
"timestamp": timeutils.isotime(),
"is_public": True,
"public_url":
"http://public.swift.example.com:8080/" +
"v1/project_id/container_two\u6346"}
container_2 = swift.Container(container_dict_2)
TEST.containers.add(container_1, container_2)