Skip to content

Commit

Permalink
Volume Type Extra Specs support
Browse files Browse the repository at this point in the history
Add support for set/unset Extra Specs to volume type.

Change-Id: I51413251dcf55d22ca9ff0c36d670028e598fa8a
Implements: blueprint volume-type-extra-specs
  • Loading branch information
niuzhenguo committed Jul 17, 2014
1 parent 0b814c2 commit d72ba10
Show file tree
Hide file tree
Showing 16 changed files with 532 additions and 1 deletion.
33 changes: 33 additions & 0 deletions openstack_dashboard/api/cinder.py
Expand Up @@ -108,6 +108,14 @@ def volume(self, value):
self._volume = value


class VolTypeExtraSpec(object):
def __init__(self, type_id, key, val):
self.type_id = type_id
self.id = key
self.key = key
self.value = val


def cinderclient(request):
api_version = VERSIONS.get_active_version()

Expand Down Expand Up @@ -320,6 +328,31 @@ def volume_type_delete(request, volume_type_id):
return cinderclient(request).volume_types.delete(volume_type_id)


def volume_type_get(request, volume_type_id):
return cinderclient(request).volume_types.get(volume_type_id)


def volume_type_extra_get(request, type_id, raw=False):
vol_type = volume_type_get(request, type_id)
extras = vol_type.get_keys()
if raw:
return extras
return [VolTypeExtraSpec(type_id, key, value) for
key, value in extras.items()]


def volume_type_extra_set(request, type_id, metadata):
vol_type = volume_type_get(request, type_id)
if not metadata:
return None
return vol_type.set_keys(metadata)


def volume_type_extra_delete(request, type_id, keys):
vol_type = volume_type_get(request, type_id)
return vol_type.unset_keys([keys])


def tenant_absolute_limits(request):
limits = cinderclient(request).limits.get().absolute
limits_dict = {}
Expand Down
Empty file.
55 changes: 55 additions & 0 deletions openstack_dashboard/dashboards/admin/volumes/extras/forms.py
@@ -0,0 +1,55 @@
# 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.

from django.utils.translation import ugettext_lazy as _

from openstack_dashboard import api

from horizon import exceptions
from horizon import forms
from horizon import messages


class CreateExtraSpec(forms.SelfHandlingForm):
key = forms.CharField(max_length="255", label=_("Key"))
value = forms.CharField(max_length="255", label=_("Value"))

def handle(self, request, data):
type_id = self.initial['type_id']
try:
api.cinder.volume_type_extra_set(request,
type_id,
{data['key']: data['value']})
msg = _('Created extra spec "%s".') % data['key']
messages.success(request, msg)
return True
except Exception:
exceptions.handle(request,
_("Unable to create volume type extra spec."))


class EditExtraSpec(forms.SelfHandlingForm):
value = forms.CharField(max_length="255", label=_("Value"))

def handle(self, request, data):
key = self.initial['key']
type_id = self.initial['type_id']
try:
api.cinder.volume_type_extra_set(request,
type_id,
{key: data['value']})
msg = _('Saved extra spec "%s".') % key
messages.success(request, msg)
return True
except Exception:
exceptions.handle(request,
_("Unable to edit volume type extra spec."))
66 changes: 66 additions & 0 deletions openstack_dashboard/dashboards/admin/volumes/extras/tables.py
@@ -0,0 +1,66 @@
# 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.

from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _

from horizon import tables

from openstack_dashboard import api


class ExtraSpecDelete(tables.DeleteAction):
data_type_singular = _("Extra Spec")
data_type_plural = _("Extra Specs")

def delete(self, request, obj_ids):
api.cinder.volume_type_extra_delete(request,
self.table.kwargs['type_id'],
obj_ids)


class ExtraSpecCreate(tables.LinkAction):
name = "create"
verbose_name = _("Create")
url = "horizon:admin:volumes:extras:create"
classes = ("btn-create", "ajax-modal")

def get_link_url(self, extra_spec=None):
return reverse(self.url, args=[self.table.kwargs['type_id']])


class ExtraSpecEdit(tables.LinkAction):
name = "edit"
verbose_name = _("Edit")
url = "horizon:admin:volumes:extras:edit"
classes = ("btn-edit", "ajax-modal")

def get_link_url(self, extra_spec):
return reverse(self.url, args=[self.table.kwargs['type_id'],
extra_spec.key])


class ExtraSpecsTable(tables.DataTable):
key = tables.Column('key', verbose_name=_('Key'))
value = tables.Column('value', verbose_name=_('Value'))

class Meta:
name = "extras"
verbose_name = _("Extra Specs")
table_actions = (ExtraSpecCreate, ExtraSpecDelete)
row_actions = (ExtraSpecEdit, ExtraSpecDelete)

def get_object_id(self, datum):
return datum.key

def get_object_display(self, datum):
return datum.key
131 changes: 131 additions & 0 deletions openstack_dashboard/dashboards/admin/volumes/extras/tests.py
@@ -0,0 +1,131 @@
# 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.

from django.core.urlresolvers import reverse
from django import http

from mox import IsA # noqa

from openstack_dashboard import api
from openstack_dashboard.test import helpers as test


class VolTypeExtrasTests(test.BaseAdminViewTests):

@test.create_stubs({api.cinder: ('volume_type_extra_get',
'volume_type_get'), })
def test_list_extras_when_none_exists(self):
vol_type = self.cinder_volume_types.first()
extras = [api.cinder.VolTypeExtraSpec(vol_type.id, 'k1', 'v1')]

api.cinder.volume_type_get(IsA(http.HttpRequest),
vol_type.id).AndReturn(vol_type)
api.cinder.volume_type_extra_get(IsA(http.HttpRequest),
vol_type.id).AndReturn(extras)
self.mox.ReplayAll()
url = reverse('horizon:admin:volumes:extras:index', args=[vol_type.id])
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertTemplateUsed(resp, "admin/volumes/extras/index.html")

@test.create_stubs({api.cinder: ('volume_type_extra_get',
'volume_type_get'), })
def test_extras_view_with_exception(self):
vol_type = self.cinder_volume_types.first()

api.cinder.volume_type_get(IsA(http.HttpRequest),
vol_type.id).AndReturn(vol_type)
api.cinder.volume_type_extra_get(IsA(http.HttpRequest),
vol_type.id) \
.AndRaise(self.exceptions.cinder)
self.mox.ReplayAll()
url = reverse('horizon:admin:volumes:extras:index', args=[vol_type.id])
resp = self.client.get(url)
self.assertEqual(len(resp.context['extras_table'].data), 0)
self.assertMessageCount(resp, error=1)

@test.create_stubs({api.cinder: ('volume_type_extra_set', ), })
def test_extra_create_post(self):
vol_type = self.cinder_volume_types.first()
create_url = reverse('horizon:admin:volumes:extras:create',
args=[vol_type.id])
index_url = reverse('horizon:admin:volumes:extras:index',
args=[vol_type.id])

data = {'key': u'k1',
'value': u'v1'}

api.cinder.volume_type_extra_set(IsA(http.HttpRequest),
vol_type.id,
{data['key']: data['value']})
self.mox.ReplayAll()

resp = self.client.post(create_url, data)
self.assertNoFormErrors(resp)
self.assertMessageCount(success=1)
self.assertRedirectsNoFollow(resp, index_url)

@test.create_stubs({api.cinder: ('volume_type_get', ), })
def test_extra_create_get(self):
vol_type = self.cinder_volume_types.first()
create_url = reverse('horizon:admin:volumes:extras:create',
args=[vol_type.id])

api.cinder.volume_type_get(IsA(http.HttpRequest),
vol_type.id).AndReturn(vol_type)
self.mox.ReplayAll()

resp = self.client.get(create_url)
self.assertEqual(resp.status_code, 200)
self.assertTemplateUsed(resp,
'admin/volumes/extras/create.html')

@test.create_stubs({api.cinder: ('volume_type_extra_set', ), })
def test_extra_edit(self):
vol_type = self.cinder_volume_types.first()
key = 'foo'
edit_url = reverse('horizon:admin:volumes:extras:edit',
args=[vol_type.id, key])
index_url = reverse('horizon:admin:volumes:extras:index',
args=[vol_type.id])

data = {'value': u'v1'}

api.cinder.volume_type_extra_set(IsA(http.HttpRequest),
vol_type.id,
{key: data['value']})
self.mox.ReplayAll()

resp = self.client.post(edit_url, data)
self.assertNoFormErrors(resp)
self.assertMessageCount(success=1)
self.assertRedirectsNoFollow(resp, index_url)

@test.create_stubs({api.cinder: ('volume_type_extra_get',
'volume_type_extra_delete'), })
def test_extra_delete(self):
vol_type = self.cinder_volume_types.first()
extras = [api.cinder.VolTypeExtraSpec(vol_type.id, 'k1', 'v1')]
formData = {'action': 'extras__delete__k1'}
index_url = reverse('horizon:admin:volumes:extras:index',
args=[vol_type.id])

api.cinder.volume_type_extra_get(IsA(http.HttpRequest),
vol_type.id).AndReturn(extras)
api.cinder.volume_type_extra_delete(IsA(http.HttpRequest),
vol_type.id,
'k1').AndReturn(vol_type)
self.mox.ReplayAll()

res = self.client.post(index_url, formData)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, index_url)
22 changes: 22 additions & 0 deletions openstack_dashboard/dashboards/admin/volumes/extras/urls.py
@@ -0,0 +1,22 @@
# 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.

from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa

from openstack_dashboard.dashboards.admin.volumes.extras import views

urlpatterns = patterns('',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^create/$', views.CreateView.as_view(), name='create'),
url(r'^(?P<key>[^/]+)/edit/$', views.EditView.as_view(), name='edit')
)

0 comments on commit d72ba10

Please sign in to comment.