Support for VERY basic Swift container and object management.

This commit is contained in:
Tres Henry 2011-06-27 16:43:08 -07:00
parent 23e928c1e2
commit dfdcc259f4
23 changed files with 492 additions and 35 deletions

View File

@ -2,17 +2,16 @@
from django.conf import settings
import logging
import cloudfiles
import glance.client
import httplib
import json
import logging
import openstack.compute
import openstackx.admin
import openstackx.extras
import openstackx.auth
from urlparse import urlparse
import json
LOG = logging.getLogger('django_openstack.api')
@ -76,7 +75,15 @@ def extras_api(request):
def auth_api():
LOG.debug('auth_api connection created using url "%s"' %
settings.OPENSTACK_KEYSTONE_URL)
return openstackx.auth.Auth(management_url=settings.OPENSTACK_KEYSTONE_URL)
return openstackx.auth.Auth(
management_url=settings.OPENSTACK_KEYSTONE_URL)
def swift_api():
return cloudfiles.get_connection(
settings.SWIFT_ACCOUNT + ":" + settings.SWIFT_USER,
settings.SWIFT_PASS,
authurl=settings.SWIFT_AUTHURL)
def console_create(request, instance_id, kind=None):
@ -270,3 +277,36 @@ def user_update_password(request, user_id, password):
def user_update_tenant(request, user_id, tenant_id):
return account_api(request).users.update_tenant(user_id, tenant_id)
def swift_get_containers():
return [{"name":c.name} for c in swift_api().get_all_containers()]
def swift_create_container(name):
return swift_api().create_container(name)
def swift_delete_container(name):
return swift_api().delete_container(name)
def swift_get_objects(container_name):
container = swift_api().get_container(container_name)
return [{"name":obj.name} for obj in container.get_objects()]
def swift_upload_object(container_name, object_name, object_data):
container = swift_api().get_container(container_name)
obj = container.create_object(object_name)
return obj.write(object_data)
def swift_delete_object(container_name, object_name):
container = swift_api().get_container(container_name)
return container.delete_object(object_name)
def swift_get_object_data(container_name, object_name):
container = swift_api().get_container(container_name)
return container.get_object(object_name).stream()

View File

@ -1,4 +1,4 @@
from django.conf import settings
from django_openstack import api
@ -6,3 +6,7 @@ def tenants(request):
if not request.user or not request.user.is_authenticated():
return {}
return {'tenants': api.token_list_tenants(request, request.user.token)}
def swift(request):
return {'swift_configured': hasattr(settings, "SWIFT_AUTHURL")}

View File

@ -1,11 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from django.conf.urls.defaults import *
from django.conf import settings
INSTANCES = r'^(?P<tenant_id>[^/]+)/instances/(?P<instance_id>[^/]+)/%s$'
IMAGES = r'^(?P<tenant_id>[^/]+)/images/(?P<image_id>[^/]+)/%s$'
KEYPAIRS = r'^(?P<tenant_id>[^/]+)/keypairs/%s$'
CONTAINERS = r'^(?P<tenant_id>[^/]+)/containers/%s$'
OBJECTS = r'^(?P<tenant_id>[^/]+)/containers/(?P<container_name>[^/]+)/%s$'
urlpatterns = patterns('django_openstack.dash.views.instances',
url(r'^(?P<tenant_id>[^/]+)/$', 'usage', name='dash_usage'),
@ -23,3 +24,16 @@ urlpatterns += patterns('django_openstack.dash.views.keypairs',
url(r'^(?P<tenant_id>[^/]+)/keypairs/$', 'index', name='dash_keypairs'),
url(KEYPAIRS % 'create', 'create', name='dash_keypairs_create'),
)
# Swift containers and objects.
urlpatterns += patterns('django_openstack.dash.views.containers',
url(CONTAINERS % '', 'index', name='dash_containers'),
url(CONTAINERS % 'create', 'create', name='dash_containers_create'),
)
urlpatterns += patterns('django_openstack.dash.views.objects',
url(OBJECTS % '', 'index', name='dash_objects'),
url(OBJECTS % 'upload', 'upload', name='dash_objects_upload'),
url(OBJECTS % '(?P<object_name>[^/]+)/download',
'download', name='dash_objects_download'),
)

View File

@ -0,0 +1,73 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
"""
Views for managing Swift containers.
"""
import logging
from django import template
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django import shortcuts
from django.shortcuts import render_to_response
from django_openstack import api
from django_openstack import forms
from cloudfiles.errors import ContainerNotEmpty
LOG = logging.getLogger('django_openstack.dash')
class DeleteContainer(forms.SelfHandlingForm):
container_name = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
try:
api.swift_delete_container(data['container_name'])
except ContainerNotEmpty, e:
messages.error(request,
'Unable to delete non-empty container: %s' % \
data['container_name'])
LOG.error('Unable to delete container "%s". Exception: "%s"' %
(data['container_name'], str(e)))
else:
messages.info(request,
'Successfully deleted container: %s' % \
data['container_name'])
return shortcuts.redirect(request.build_absolute_uri())
class CreateContainer(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label="Container Name")
def handle(self, request, data):
api.swift_create_container(data['name'])
messages.success(request, "Container was successfully created.")
return shortcuts.redirect(request.build_absolute_uri())
@login_required
def index(request, tenant_id):
delete_form, handled = DeleteContainer.maybe_handle(request)
if handled:
return handled
containers = api.swift_get_containers()
return render_to_response('dash_containers.html', {
'containers': containers,
'delete_form': delete_form,
}, context_instance=template.RequestContext(request))
@login_required
def create(request, tenant_id):
form, handled = CreateContainer.maybe_handle(request)
if handled:
return handled
return render_to_response('dash_containers_create.html', {
'create_form': form,
}, context_instance=template.RequestContext(request))

View File

@ -85,7 +85,7 @@ def index(request, tenant_id):
except api_exceptions.ApiException, e:
keypairs = []
LOG.error("ApiException in keypair index", exc_info=True)
messages.error(request, 'Error featching keypairs: %s' % e.message)
messages.error(request, 'Error fetching keypairs: %s' % e.message)
return render_to_response('dash_keypairs.html', {
'keypairs': keypairs,

View File

@ -0,0 +1,85 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
"""
Views for managing Swift containers.
"""
import logging
from django import http
from django import template
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django import shortcuts
from django.shortcuts import render_to_response
from django_openstack import api
from django_openstack import forms
LOG = logging.getLogger('django_openstack.dash')
class DeleteObject(forms.SelfHandlingForm):
object_name = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
api.swift_delete_object(
request.POST['container_name'],
data['object_name'])
messages.info(request,
'Successfully deleted object: %s' % \
data['object_name'])
return shortcuts.redirect(request.build_absolute_uri())
class UploadObject(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label="Object Name")
object_file = forms.FileField(label="File")
def handle(self, request, data):
api.swift_upload_object(
request.POST['container_name'],
data['name'],
request.FILES['object_file'].read())
messages.success(request, "Object was successfully uploaded.")
return shortcuts.redirect(request.build_absolute_uri())
@login_required
def index(request, tenant_id, container_name):
delete_form, handled = DeleteObject.maybe_handle(request)
if handled:
return handled
objects = api.swift_get_objects(container_name)
return render_to_response('dash_objects.html', {
'container_name': container_name,
'objects': objects,
'delete_form': delete_form,
}, context_instance=template.RequestContext(request))
@login_required
def upload(request, tenant_id, container_name):
form, handled = UploadObject.maybe_handle(request)
if handled:
return handled
return render_to_response('dash_objects_upload.html', {
'container_name': container_name,
'upload_form': form,
}, context_instance=template.RequestContext(request))
@login_required
def download(request, tenant_id, container_name, object_name):
object_data = api.swift_get_object_data(
container_name, object_name)
response = http.HttpResponse()
response['Content-Disposition'] = 'attachment; filename=%s' % \
object_name
for data in object_data:
response.write(data)
return response

View File

@ -138,7 +138,10 @@ class SelfHandlingForm(Form):
return cls(*args, **kwargs), None
try:
form = cls(request.POST, *args, **kwargs)
if request.FILES:
form = cls(request.POST, request.FILES, *args, **kwargs)
else:
form = cls(request.POST, *args, **kwargs)
if not form.is_valid():
return form, None

View File

@ -44,6 +44,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.media',
'django.contrib.messages.context_processors.messages',
'django_openstack.context_processors.tenants',
'django_openstack.context_processors.swift',
)
TEMPLATE_LOADERS = (

View File

@ -0,0 +1,10 @@
<form id="container_form" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input type="submit" value="Create Container" class="large-rounded" />
</form>

View File

@ -0,0 +1,17 @@
<table id="containers" style="width: 100%">
<tr>
<th>Name</th>
<th style="width: 100px;">Actions</th>
</tr>
{% for container in containers %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ container.name }}</td>
<td id="actions">
<ul>
<li><a href="{% url dash_objects request.user.tenant container.name %}">List Objects</a></li>
<li>{% include "_delete_container.html" with form=delete_form %}</li>
</ul>
</td>
</tr>
{% endfor %}
</table>

View File

@ -6,4 +6,10 @@
<li><a {% if current_sidebar == "images" %} class="active" {% endif %} href="{% url dash_images request.user.tenant %}">Images</a></li>
<li><a {% if current_sidebar == "keypairs" %} class="active" {% endif %} href="{% url dash_keypairs request.user.tenant %}">Keypairs</a></li>
</ul>
{% if swift_configured %}
<h3>Manage Object Store</h3>
<ul class='sub_nav'>
<li><a {% if current_sidebar == "containers" %} class="active" {% endif %} href="{% url dash_containers request.user.tenant %}">Containers</a></li>
</ul>
{% endif %}
</div>

View File

@ -0,0 +1,8 @@
<form id="form_delete_{{container.name}}" class="form-delete" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="container_name" type="hidden" value="{{container.name}}" />
<input id="delete_{{container.name}}" class="delete" title="Container: {{container.name}}" type="submit" value="Delete" />
</form>

View File

@ -0,0 +1,9 @@
<form id="form_delete_{{ object.name }}" class="form-delete" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input type="hidden" name="container_name" value="{{ container_name }}" />
<input name="object_name" type="hidden" value="{{ object.name }}" />
<input id="delete_{{ object.name }}" class="delete" title="Object: {{ object.name }}" type="submit" value="Delete" />
</form>

View File

@ -0,0 +1,11 @@
<form id="object_form" enctype="multipart/form-data" method="post">
{% csrf_token %}
<input type="hidden" name="container_name" value="{{ container_name }}" />
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input type="submit" value="Upload Object" class="large-rounded" />
</form>

View File

@ -0,0 +1,17 @@
<table id="objects" style="width: 100%">
<tr>
<th>Name</th>
<th style="width: 100px;">Actions</th>
</tr>
{% for object in objects %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ object.name }}</td>
<td id="actions">
<ul>
<li>{% include "_delete_object.html" with form=delete_form %}</li>
<li><a href="{% url dash_objects_download request.user.tenant container_name object.name %}">Download</a>
</ul>
</td>
</tr>
{% endfor %}
</table>

View File

@ -0,0 +1,37 @@
{% extends 'dash_base.html' %}
{# list of user's containers #}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Containers</h2>
<p class='desc'><span>&mdash;</span> Manage Containers.</p>
</div>
{% include "_messages.html" %}
<div class='main_content'>
{% if containers %}
<div class='table_title wide'>
<h3>Containers</h3>
<div class='search'>
<form action='' method='post'>
<fieldset>
<label for='table_search'>Search</label>
<input id='table_search' name='search' type='text' value='' />
</fieldset>
</form>
</div>
</div>
{% include '_container_list.html' %}
{% endif %}
<a href="{% url dash_containers_create request.user.tenant %}">Create New Container &gt;&gt;</a>
</div>
{% endblock %}

View File

@ -0,0 +1,33 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Containers</h2>
<p class='desc'><span>&mdash;</span> Create a container.</p>
</div>
{% include "_messages.html" %}
<div class="main_content">
<div class="dash_block wide form">
<div class='title_block'>
<h3>Create Container</h3>
</div>
<div class="left">
{% include '_container_form.html' with form=create_form %}
<h3><a href="{% url dash_containers request.user.tenant %}">&lt;&lt; Return to containers list</a></h3>
</div>
<div class="right">
<h3>Description:</h3>
<p>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® or a directory in UNIX®. 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>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,38 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Objects</h2>
<p class='desc'><span>&mdash;</span> Manage Objects.</p>
</div>
{% include "_messages.html" %}
<div class="container-label">Container:</div>
<div class="container-name">{{ container_name }}</div>
<div style="clear: both;"></div>
<div class='main_content'>
{% if objects %}
<div class='table_title wide'>
<h3>Objects</h3>
<div class='search'>
<form action='' method='post'>
<fieldset>
<label for='table_search'>Search</label>
<input id='table_search' name='search' type='text' value='' />
</fieldset>
</form>
</div>
</div>
{% include '_object_list.html' %}
{% endif %}
<a href="{% url dash_objects_upload request.user.tenant container_name %}">Upload New Object &gt;&gt;</a>
</div>
{% endblock %}

View File

@ -0,0 +1,36 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Objects</h2>
<p class='desc'><span>&mdash;</span> Upload an object.</p>
</div>
{% include "_messages.html" %}
<div class="container-label">Container:</div>
<div class="container-name">{{ container_name }}</div>
<div style="clear: both;"></div>
<div class="main_content">
<div class="dash_block wide form">
<div class='title_block'>
<h3>Upload Object</h3>
</div>
<div class="left">
{% include '_object_form.html' with form=upload_form %}
<h3><a href="{% url dash_objects request.user.tenant container_name %}">&lt;&lt; Return to objects list</a></h3>
</div>
<div class="right">
<h3>Description:</h3>
<p>An object is the basic storage entity and any optional metadata that represents the files you store in the OpenStack Object Storage system. When you upload data to OpenStack Object Storage, the data is stored as-is (no compression or encryption) and consists of a location (container), the object's name, and any metadata consisting of key/value pairs.</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -34,6 +34,13 @@ MAILER_EMAIL_BACKEND = EMAIL_BACKEND
OPENSTACK_ADMIN_TOKEN = "999888777666"
OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0/"
# NOTE(tres): Replace all of this nonsense once Swift+Keystone
# is stable.
# SWIFT_AUTHURL = 'http://localhost:8080/auth/v1.0'
# SWIFT_ACCOUNT = 'test'
# SWIFT_USER = 'tester'
# SWIFT_PASS = 'testing'
# If you have external monitoring links
EXTERNAL_MONITORING = [
['Nagios','http://foo.com'],

View File

@ -1,7 +1,7 @@
@import url("reset.css");
/*==================
=== Misc/General
=== Misc/General
==================*/
body {
@ -60,7 +60,7 @@ p .ui-icon {
/*==================
=== Header
=== Header
==================*/
#header {
@ -96,7 +96,7 @@ p .ui-icon {
/*==================
=== Content
=== Content
==================*/
#content_wrap {
@ -483,7 +483,7 @@ input.delete, input.detach {
}
#right_content td.image_location {
text-align: left;
text-align: left;
}
h3.image_list_heading {
@ -505,7 +505,7 @@ h3.image_list_heading {
td.detail_wrapper {
padding: 0 !important;
text-align: left !important;
}
div.image_detail, div.instance_detail {
background: url(/media/dashboard/img/image_detail.png) top left no-repeat;
@ -727,20 +727,19 @@ div.image_detail, div.instance_detail {
border-bottom: none;
border-top: none;
}
#projects .project a.manage_link:hover {
#projects .project a.manage_link:hover {
background-color: #f1f1f1 !important;
border-color: #b7b7b7;
}
#projects .project.active a.manage_link { background-color: #5393c9 !important; border-color: #4c83af;}
#projects .project.active a.manage_link:hover {
#projects .project.active a.manage_link:hover {
background-color: #80afd6 !important;
border-color: #b7b7b7;
}
/* Footer */
#footer {
#footer {
clear: both;
margin-bottom: 50px;
}

View File

@ -46,7 +46,7 @@ ul, li {
h1 {
float: left;
}
h1 a{
@ -91,7 +91,7 @@ border: 1px solid #cbe0e8;
z-index: 500;
color: #8eacb7;
text-transform: uppercase;
font-size: 12px;
font-size: 12px;
position: relative;
display: block;
float: left;
@ -126,7 +126,7 @@ border: 1px solid #cbe0e8;
}
#nav li, #nav li a{
float: left;
float: left;
}
#nav li a.active {
@ -166,7 +166,7 @@ border: 1px solid #cbe0e8;
float: right;
height: 400px;
margin-bottom: -400px;
}
#login_btn {
@ -213,7 +213,7 @@ border: 1px solid #cbe0e8;
#standalone #login {
float:left;
margin: 0 !important;
-webkit-box-shadow: 0 0 10px rgba(0,0,0,0.2);
-webkit-box-shadow: 0 0 10px rgba(0,0,0,0.2);
}
.clear {
@ -880,7 +880,7 @@ ol {
ol li {
display: block;
float: left;
float: left;
position: relative;
margin-right: -28px;
}
@ -1134,23 +1134,23 @@ ol li.first a {
margin-bottom: 25px;
}
.status_box.info {
.status_box.info {
background-color: #e8f8ff;
border-color: #9ac7dc;
color: #7ab6c5;
}
.status_box.success {
.status_box.success {
background-color: #e9ffe8;
border-color: #9edd9b;
color: #7ec67b;
}
.status_box.warning {
.status_box.warning {
background-color: #ffffe8;
border-color: #ffe37b;
color: #d1b12d;
}
.status_box.error {
.status_box.error {
background-color: #ffdbdc;
border-color: #ff9a99;
color: #ff8080;
@ -1161,7 +1161,7 @@ ol li.first a {
float: left;
padding: 20px;
min-width: 120px;
}
.status_box.info h2 { color: #2a7380; border-color: #9ac7dc; }
@ -1308,18 +1308,18 @@ a.refresh:hover {
.stat_box.good {
.stat_box.good {
background-color: #e9ffe8;
border-color: #9edd9b;
color: #7ec67b;
}
.stat_box.medium {
.stat_box.medium {
background-color: #ffffe8;
border-color: #ffe37b;
color: #eada83;
}
.stat_box.bad {
.stat_box.bad {
background-color: #ffdbdc;
border-color: #ff9a99;
color: #ff8080;
@ -1376,7 +1376,7 @@ a.refresh:hover {
}
.stat_box p.avail span, .stat_box p.used span {
.stat_box p.avail span, .stat_box p.used span {
font-size: 11px;
text-transform: uppercase;
}
@ -1609,8 +1609,16 @@ li.title h4{
margin-left: 25px;
}
.container-label {
font-weight: bold;
font-size: 1.22em;
margin-left: 10px;
margin-bottom: 10px;
float: left;
}
.container-name {
float: left;
margin-left: 5px;
}

View File

@ -4,6 +4,7 @@ django-nose==0.1.2
django-mailer
django-registration==0.7
nova-adminclient
python-cloudfiles
bzr+https://launchpad.net/glance#egg=glance
-e git://github.com/jacobian/openstack.compute.git#egg=openstack