Merge "Drop Flavor Edit completely"
This commit is contained in:
commit
70e897729d
openstack_dashboard/dashboards/admin/flavors
@ -14,6 +14,7 @@ import django
|
||||
from django.conf import settings
|
||||
from django import http
|
||||
from django.urls import reverse
|
||||
import mock
|
||||
from mox3.mox import IsA
|
||||
from novaclient.v2 import flavors
|
||||
|
||||
@ -197,7 +198,7 @@ class BaseFlavorWorkflowTests(test.BaseAdminViewTests):
|
||||
flavor_info["flavorid"] = id
|
||||
return flavor_info
|
||||
|
||||
def _get_workflow_fields(self, flavor, id=None, access=None):
|
||||
def _get_workflow_data(self, flavor, id=None, access=None):
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
flavor_info = {"name": flavor.name,
|
||||
"vcpus": flavor.vcpus,
|
||||
@ -206,16 +207,16 @@ class BaseFlavorWorkflowTests(test.BaseAdminViewTests):
|
||||
"swap_mb": flavor.swap,
|
||||
"rxtx_factor": flavor.rxtx_factor,
|
||||
"eph_gb": eph}
|
||||
if access:
|
||||
access_field_name = 'update_flavor_access_role_member'
|
||||
flavor_info[access_field_name] = [p.id for p in access]
|
||||
self._get_access_field(flavor_info, access)
|
||||
if id:
|
||||
flavor_info['flavor_id'] = id
|
||||
return flavor_info
|
||||
|
||||
def _get_workflow_data(self, flavor, id=None, access=None):
|
||||
flavor_info = self._get_workflow_fields(flavor, access=access,
|
||||
id=id)
|
||||
def _get_access_field(self, flavor_info=None, access=None):
|
||||
flavor_info = flavor_info or {}
|
||||
if access is not None:
|
||||
access_field_name = 'flavor_access_role_member'
|
||||
flavor_info[access_field_name] = [p.id for p in access]
|
||||
return flavor_info
|
||||
|
||||
|
||||
@ -237,7 +238,7 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests):
|
||||
self.assertQuerysetEqual(
|
||||
workflow.steps,
|
||||
['<CreateFlavorInfo: createflavorinfoaction>',
|
||||
'<UpdateFlavorAccess: update_flavor_access>'])
|
||||
'<CreateFlavorAccess: flavor_access>'])
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_list',
|
||||
@ -278,8 +279,7 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests):
|
||||
# init
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)).AndReturn([projects,
|
||||
False])
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None) \
|
||||
.AndReturn([])
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None).AndReturn([])
|
||||
|
||||
# handle
|
||||
params = self._flavor_create_params(flavor, id='auto')
|
||||
@ -432,7 +432,10 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests):
|
||||
|
||||
|
||||
class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests):
|
||||
@test.create_stubs({api.nova: ('flavor_get',
|
||||
|
||||
use_mox = False
|
||||
|
||||
@test.create_mocks({api.nova: ('flavor_get',
|
||||
'flavor_access_list',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_update_flavor_get(self):
|
||||
@ -440,16 +443,9 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests):
|
||||
flavor_access = self.flavor_access.list()
|
||||
projects = self.tenants.list()
|
||||
|
||||
# GET/init, set up expected behavior
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.MultipleTimes().AndReturn(flavor)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)).AndReturn([projects,
|
||||
False])
|
||||
api.nova.flavor_access_list(IsA(http.HttpRequest), flavor.id) \
|
||||
.AndReturn(flavor_access)
|
||||
|
||||
# Put all mocks created by mox into replay mode
|
||||
self.mox.ReplayAll()
|
||||
self.mock_flavor_get.return_value = flavor
|
||||
self.mock_tenant_list.return_value = [projects, False]
|
||||
self.mock_flavor_access_list.return_value = flavor_access
|
||||
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
res = self.client.get(url)
|
||||
@ -462,323 +458,48 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests):
|
||||
|
||||
self.assertQuerysetEqual(
|
||||
workflow.steps,
|
||||
['<UpdateFlavorInfo: update_info>',
|
||||
'<UpdateFlavorAccess: update_flavor_access>'])
|
||||
['<UpdateFlavorAccess: flavor_access>'])
|
||||
|
||||
step = workflow.get_step("update_info")
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
self.assertEqual(step.action.initial['name'], flavor.name)
|
||||
self.assertEqual(step.action.initial['vcpus'], flavor.vcpus)
|
||||
self.assertEqual(step.action.initial['memory_mb'], flavor.ram)
|
||||
self.assertEqual(step.action.initial['disk_gb'], flavor.disk)
|
||||
self.assertEqual(step.action.initial['swap_mb'], flavor.swap)
|
||||
self.assertEqual(step.action.initial['rxtx_factor'],
|
||||
flavor.rxtx_factor)
|
||||
self.assertEqual(step.action.initial['eph_gb'], eph)
|
||||
|
||||
step = workflow.get_step("update_flavor_access")
|
||||
step = workflow.get_step("flavor_access")
|
||||
field_name = step.get_member_field_name('member')
|
||||
self.assertEqual(step.action.fields[field_name].initial,
|
||||
[fa.tenant_id for fa in flavor_access])
|
||||
|
||||
@test.create_stubs({api.nova: ('flavor_get',), })
|
||||
self.mock_flavor_get.assert_called_once_with(test.IsHttpRequest(),
|
||||
flavor.id)
|
||||
self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest())
|
||||
self.mock_flavor_access_list.assert_called_once_with(
|
||||
test.IsHttpRequest(), flavor.id)
|
||||
|
||||
@test.create_mocks({api.nova: ('flavor_get',), })
|
||||
def test_update_flavor_get_flavor_error(self):
|
||||
flavor = self.flavors.first()
|
||||
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.AndRaise(self.exceptions.nova)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
self.mock_flavor_get.side_effect = self.exceptions.nova
|
||||
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertRedirectsNoFollow(res, reverse(constants.FLAVORS_INDEX_URL))
|
||||
self.mock_flavor_get.assert_called_once_with(test.IsHttpRequest(),
|
||||
flavor.id)
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',),
|
||||
@test.create_mocks({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_get',
|
||||
'flavor_get_extras',
|
||||
'flavor_list',
|
||||
'flavor_delete',
|
||||
'flavor_create')})
|
||||
def test_update_flavor_without_extra_specs(self):
|
||||
# The first element has no extra specs
|
||||
flavor = self.flavors.first()
|
||||
projects = self.tenants.list()
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
extra_specs = getattr(flavor, 'extra_specs')
|
||||
new_flavor = flavors.Flavor(flavors.FlavorManager(None),
|
||||
{'id':
|
||||
"cccccccc-cccc-cccc-cccc-cccccccccccc",
|
||||
'name': flavor.name,
|
||||
'vcpus': flavor.vcpus + 1,
|
||||
'disk': flavor.disk,
|
||||
'ram': flavor.ram,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'swap': 0,
|
||||
'OS-FLV-EXT-DATA:ephemeral': eph,
|
||||
'extra_specs': extra_specs})
|
||||
|
||||
# GET/init, set up expected behavior
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.MultipleTimes().AndReturn(flavor)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn([projects, False])
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None) \
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
# POST/init
|
||||
api.nova.flavor_get_extras(IsA(http.HttpRequest),
|
||||
flavor.id, raw=True) \
|
||||
.AndReturn(extra_specs)
|
||||
api.nova.flavor_delete(IsA(http.HttpRequest), flavor.id)
|
||||
api.nova.flavor_create(IsA(http.HttpRequest),
|
||||
new_flavor.name,
|
||||
new_flavor.ram,
|
||||
new_flavor.vcpus,
|
||||
new_flavor.disk,
|
||||
swap=new_flavor.swap,
|
||||
rxtx_factor=new_flavor.rxtx_factor,
|
||||
ephemeral=eph,
|
||||
is_public=True).AndReturn(new_flavor)
|
||||
|
||||
# Put mocks in replay mode
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# run get test
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE)
|
||||
|
||||
# run post test
|
||||
workflow_data = {'flavor_id': flavor.id,
|
||||
'name': new_flavor.name,
|
||||
'vcpus': new_flavor.vcpus,
|
||||
'memory_mb': new_flavor.ram,
|
||||
'disk_gb': new_flavor.disk,
|
||||
'swap_mb': new_flavor.swap,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'eph_gb': eph,
|
||||
'is_public': True}
|
||||
resp = self.client.post(url, workflow_data)
|
||||
self.assertNoFormErrors(resp)
|
||||
self.assertMessageCount(success=1)
|
||||
self.assertRedirectsNoFollow(resp,
|
||||
reverse(constants.FLAVORS_INDEX_URL))
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_get',
|
||||
'flavor_get_extras',
|
||||
'flavor_list',
|
||||
'flavor_delete',
|
||||
'flavor_create',
|
||||
'flavor_extra_set')})
|
||||
def test_update_flavor_with_extra_specs(self):
|
||||
# The second element has extra specs
|
||||
flavor = self.flavors.list()[1]
|
||||
projects = self.tenants.list()
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
extra_specs = getattr(flavor, 'extra_specs')
|
||||
new_flavor = flavors.Flavor(flavors.FlavorManager(None),
|
||||
{'id':
|
||||
"cccccccc-cccc-cccc-cccc-cccccccccccc",
|
||||
'name': flavor.name,
|
||||
'vcpus': flavor.vcpus + 1,
|
||||
'disk': flavor.disk,
|
||||
'ram': flavor.ram,
|
||||
'swap': flavor.swap,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'OS-FLV-EXT-DATA:ephemeral': eph,
|
||||
'extra_specs': extra_specs})
|
||||
|
||||
# GET/init, set up expected behavior
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.MultipleTimes().AndReturn(flavor)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn([projects, False])
|
||||
|
||||
# POST/init
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None) \
|
||||
.AndReturn(self.flavors.list())
|
||||
api.nova.flavor_get_extras(IsA(http.HttpRequest),
|
||||
flavor.id, raw=True) \
|
||||
.AndReturn(extra_specs)
|
||||
api.nova.flavor_delete(IsA(http.HttpRequest), flavor.id)
|
||||
api.nova.flavor_create(IsA(http.HttpRequest),
|
||||
new_flavor.name,
|
||||
new_flavor.ram,
|
||||
new_flavor.vcpus,
|
||||
new_flavor.disk,
|
||||
swap=new_flavor.swap,
|
||||
rxtx_factor=new_flavor.rxtx_factor,
|
||||
ephemeral=eph,
|
||||
is_public=True).AndReturn(new_flavor)
|
||||
api.nova.flavor_extra_set(IsA(http.HttpRequest),
|
||||
new_flavor.id, extra_specs)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# run get test
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE)
|
||||
|
||||
# run post test
|
||||
workflow_data = {'flavor_id': flavor.id,
|
||||
'name': new_flavor.name,
|
||||
'vcpus': new_flavor.vcpus,
|
||||
'memory_mb': new_flavor.ram,
|
||||
'disk_gb': new_flavor.disk,
|
||||
'swap_mb': new_flavor.swap,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'eph_gb': eph,
|
||||
'is_public': True}
|
||||
resp = self.client.post(url, workflow_data)
|
||||
self.assertNoFormErrors(resp)
|
||||
self.assertMessageCount(success=1)
|
||||
self.assertRedirectsNoFollow(resp,
|
||||
reverse(constants.FLAVORS_INDEX_URL))
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_get',
|
||||
'flavor_get_extras',
|
||||
'flavor_list',
|
||||
'flavor_delete',
|
||||
'flavor_create')})
|
||||
def test_update_flavor_update_flavor_error(self):
|
||||
# The first element has no extra specs
|
||||
flavor = self.flavors.first()
|
||||
projects = self.tenants.list()
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
extra_specs = getattr(flavor, 'extra_specs')
|
||||
new_flavor = flavors.Flavor(flavors.FlavorManager(None),
|
||||
{'id':
|
||||
"cccccccc-cccc-cccc-cccc-cccccccccccc",
|
||||
'name': flavor.name,
|
||||
'vcpus': flavor.vcpus + 1,
|
||||
'disk': flavor.disk,
|
||||
'ram': flavor.ram,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'swap': 0,
|
||||
'OS-FLV-EXT-DATA:ephemeral': eph,
|
||||
'extra_specs': extra_specs})
|
||||
|
||||
# GET/init, set up expected behavior
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.MultipleTimes().AndReturn(flavor)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn([projects, False])
|
||||
|
||||
# POST
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None) \
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
# POST/init
|
||||
api.nova.flavor_get_extras(IsA(http.HttpRequest),
|
||||
flavor.id, raw=True) \
|
||||
.AndReturn(extra_specs)
|
||||
api.nova.flavor_delete(IsA(http.HttpRequest), flavor.id)
|
||||
api.nova.flavor_create(IsA(http.HttpRequest),
|
||||
new_flavor.name,
|
||||
new_flavor.ram,
|
||||
new_flavor.vcpus,
|
||||
new_flavor.disk,
|
||||
swap=new_flavor.swap,
|
||||
rxtx_factor=new_flavor.rxtx_factor,
|
||||
ephemeral=eph,
|
||||
is_public=True)\
|
||||
.AndRaise(self.exceptions.nova)
|
||||
|
||||
# Put mocks in replay mode
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# run get test
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE)
|
||||
|
||||
# run post test
|
||||
workflow_data = {'flavor_id': flavor.id,
|
||||
'name': new_flavor.name,
|
||||
'vcpus': new_flavor.vcpus,
|
||||
'memory_mb': new_flavor.ram,
|
||||
'disk_gb': new_flavor.disk,
|
||||
'swap_mb': new_flavor.swap,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'eph_gb': eph,
|
||||
'is_public': True}
|
||||
resp = self.client.post(url, workflow_data)
|
||||
self.assertNoFormErrors(resp)
|
||||
self.assertMessageCount(error=1)
|
||||
self.assertRedirectsNoFollow(resp,
|
||||
reverse(constants.FLAVORS_INDEX_URL))
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_get',
|
||||
'flavor_get_extras',
|
||||
'flavor_list',
|
||||
'flavor_delete',
|
||||
'flavor_create',
|
||||
'flavor_access_list',
|
||||
'remove_tenant_from_flavor',
|
||||
'add_tenant_to_flavor')})
|
||||
def test_update_flavor_update_projects_error(self):
|
||||
# The first element has no extra specs
|
||||
flavor = self.flavors.first()
|
||||
def test_update_flavor_access(self):
|
||||
# The third element is private (is_public False)
|
||||
flavor = self.flavors.list()[2]
|
||||
projects = self.tenants.list()
|
||||
flavor_projects = [self.tenants.first()]
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
extra_specs = getattr(flavor, 'extra_specs')
|
||||
new_flavor = flavors.Flavor(flavors.FlavorManager(None),
|
||||
{'id':
|
||||
"cccccccc-cccc-cccc-cccc-cccccccccccc",
|
||||
'name': flavor.name,
|
||||
'vcpus': flavor.vcpus + 1,
|
||||
'disk': flavor.disk,
|
||||
'ram': flavor.ram,
|
||||
'swap': 0,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'OS-FLV-EXT-DATA:ephemeral': eph,
|
||||
'os-flavor-access:is_public': False,
|
||||
'extra_specs': extra_specs})
|
||||
flavor_accesses = self.flavor_access.list()
|
||||
|
||||
# GET/init, set up expected behavior
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.MultipleTimes().AndReturn(flavor)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn([projects, False])
|
||||
|
||||
# POST/init
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None) \
|
||||
.AndReturn(self.flavors.list())
|
||||
api.nova.flavor_get_extras(IsA(http.HttpRequest),
|
||||
flavor.id, raw=True) \
|
||||
.AndReturn(extra_specs)
|
||||
|
||||
api.nova.flavor_delete(IsA(http.HttpRequest), flavor.id)
|
||||
api.nova.flavor_create(IsA(http.HttpRequest),
|
||||
new_flavor.name,
|
||||
new_flavor.ram,
|
||||
new_flavor.vcpus,
|
||||
new_flavor.disk,
|
||||
swap=new_flavor.swap,
|
||||
rxtx_factor=new_flavor.rxtx_factor,
|
||||
ephemeral=eph,
|
||||
is_public=new_flavor.is_public) \
|
||||
.AndReturn(new_flavor)
|
||||
|
||||
new_flavor_projects = flavor_projects
|
||||
for project in new_flavor_projects:
|
||||
expect = api.nova.add_tenant_to_flavor(IsA(http.HttpRequest),
|
||||
new_flavor.id, project.id)
|
||||
if project == projects[0]:
|
||||
expect.AndRaise(self.exceptions.nova)
|
||||
|
||||
# Put mocks in replay mode
|
||||
self.mox.ReplayAll()
|
||||
self.mock_flavor_get.return_value = flavor
|
||||
self.mock_tenant_list.return_value = [projects, False]
|
||||
self.mock_flavor_access_list.return_value = flavor_accesses
|
||||
self.mock_add_tenant_to_flavor.return_value = None
|
||||
self.mock_remove_tenant_from_flavor.return_value = None
|
||||
|
||||
# run get test
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
@ -787,175 +508,68 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests):
|
||||
self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE)
|
||||
|
||||
# run post test
|
||||
data = self._get_workflow_data(new_flavor, access=flavor_projects)
|
||||
data['flavor_id'] = flavor.id
|
||||
data = self._get_access_field(access=projects[1:3])
|
||||
resp = self.client.post(url, data)
|
||||
self.assertNoFormErrors(resp)
|
||||
self.assertMessageCount(success=1, error=0, warning=0)
|
||||
self.assertRedirectsNoFollow(resp,
|
||||
reverse(constants.FLAVORS_INDEX_URL))
|
||||
|
||||
self.assert_mock_multiple_calls_with_same_arguments(
|
||||
self.mock_flavor_get, 2,
|
||||
mock.call(test.IsHttpRequest(), flavor.id))
|
||||
self.assert_mock_multiple_calls_with_same_arguments(
|
||||
self.mock_tenant_list, 2,
|
||||
mock.call(test.IsHttpRequest()))
|
||||
self.assert_mock_multiple_calls_with_same_arguments(
|
||||
self.mock_flavor_access_list, 2,
|
||||
mock.call(test.IsHttpRequest(), flavor.id))
|
||||
# NOTE: This test assumes self.tenants.list() and
|
||||
# self.flavor_access.list() contains project IDs in a same order.
|
||||
# Otherwise, mocking below will fail.
|
||||
self.mock_add_tenant_to_flavor.assert_called_once_with(
|
||||
test.IsHttpRequest(), flavor.id, projects[2].id)
|
||||
self.mock_remove_tenant_from_flavor.assert_called_once_with(
|
||||
test.IsHttpRequest(), flavor.id, projects[0].id)
|
||||
|
||||
@test.create_mocks({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_get',
|
||||
'flavor_access_list',
|
||||
'add_tenant_to_flavor')})
|
||||
def test_update_flavor_access_with_error(self):
|
||||
# The third element is private (is_public False)
|
||||
flavor = self.flavors.list()[2]
|
||||
projects = self.tenants.list()
|
||||
flavor_projects = [self.tenants.first()]
|
||||
|
||||
self.mock_flavor_get.return_value = flavor
|
||||
self.mock_tenant_list.return_value = [projects, False]
|
||||
self.mock_flavor_access_list.return_value = []
|
||||
self.mock_add_tenant_to_flavor.side_effect = self.exceptions.nova
|
||||
|
||||
# run get test
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE)
|
||||
|
||||
# run post test
|
||||
data = self._get_access_field(access=flavor_projects)
|
||||
resp = self.client.post(url, data)
|
||||
self.assertNoFormErrors(resp)
|
||||
self.assertMessageCount(error=1, warning=0)
|
||||
self.assertRedirectsNoFollow(resp,
|
||||
reverse(constants.FLAVORS_INDEX_URL))
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_get',
|
||||
'flavor_list',)})
|
||||
def test_update_flavor_set_invalid_name_length(self):
|
||||
flavor = self.flavors.first()
|
||||
projects = self.tenants.list()
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
invalid_flavor_name = "a" * 256
|
||||
self.assert_mock_multiple_calls_with_same_arguments(
|
||||
self.mock_flavor_get, 2,
|
||||
mock.call(test.IsHttpRequest(), flavor.id))
|
||||
self.assert_mock_multiple_calls_with_same_arguments(
|
||||
self.mock_tenant_list, 2,
|
||||
mock.call(test.IsHttpRequest()))
|
||||
self.assert_mock_multiple_calls_with_same_arguments(
|
||||
self.mock_flavor_access_list, 2,
|
||||
mock.call(test.IsHttpRequest(), flavor.id))
|
||||
|
||||
# init
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.MultipleTimes().AndReturn(flavor)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn([projects, False])
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None) \
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# run get test
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE)
|
||||
|
||||
# run post test
|
||||
workflow_data = {'flavor_id': flavor.id,
|
||||
'name': invalid_flavor_name,
|
||||
'vcpus': flavor.vcpus + 1,
|
||||
'memory_mb': flavor.ram,
|
||||
'disk_gb': flavor.disk,
|
||||
'swap_mb': flavor.swap,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'eph_gb': eph,
|
||||
'is_public': True}
|
||||
resp = self.client.post(url, workflow_data)
|
||||
self.assertFormErrors(resp)
|
||||
self.assertContains(resp,
|
||||
"Ensure this value has at most 255 characters")
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_get',
|
||||
'flavor_list',)})
|
||||
def test_update_flavor_set_existing_name(self):
|
||||
flavor_a = self.flavors.list()[0]
|
||||
flavor_b = self.flavors.list()[1]
|
||||
projects = self.tenants.list()
|
||||
eph = getattr(flavor_a, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
extra_specs = getattr(flavor_a, 'extra_specs')
|
||||
new_flavor = flavors.Flavor(flavors.FlavorManager(None),
|
||||
{'id': flavor_a.id,
|
||||
'name': flavor_b.name,
|
||||
'vcpus': flavor_a.vcpus,
|
||||
'disk': flavor_a.disk,
|
||||
'ram': flavor_a.ram,
|
||||
'swap': flavor_a.swap,
|
||||
'rxtx_factor': flavor_a.rxtx_factor,
|
||||
'OS-FLV-EXT-DATA:ephemeral': eph,
|
||||
'extra_specs': extra_specs})
|
||||
|
||||
# GET
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor_a.id) \
|
||||
.MultipleTimes().AndReturn(flavor_a)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn([projects, False])
|
||||
|
||||
# POST
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None) \
|
||||
.AndReturn(self.flavors.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# get test
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor_a.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE)
|
||||
|
||||
# post test
|
||||
data = {'flavor_id': new_flavor.id,
|
||||
'name': new_flavor.name,
|
||||
'vcpus': new_flavor.vcpus,
|
||||
'memory_mb': new_flavor.ram,
|
||||
'disk_gb': new_flavor.disk,
|
||||
'swap_mb': new_flavor.swap,
|
||||
'rxtx_factor': new_flavor.rxtx_factor,
|
||||
'eph_gb': eph,
|
||||
'is_public': True}
|
||||
resp = self.client.post(url, data)
|
||||
self.assertFormErrors(resp, 1, 'The name "m1.massive" '
|
||||
'is already used by another flavor.')
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',),
|
||||
api.nova: ('flavor_get',
|
||||
'flavor_list',)})
|
||||
def generic_update_flavor_invalid_data_form_fails(self, override_data,
|
||||
error_msg):
|
||||
flavor = self.flavors.first()
|
||||
projects = self.tenants.list()
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.MultipleTimes().AndReturn(flavor)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn([projects, False])
|
||||
api.nova.flavor_list(IsA(http.HttpRequest), None) \
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# run get test
|
||||
url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE)
|
||||
|
||||
# run post test
|
||||
workflow_data = {'flavor_id': flavor.id,
|
||||
'name': flavor.name,
|
||||
'vcpus': flavor.vcpus,
|
||||
'memory_mb': flavor.ram,
|
||||
'disk_gb': flavor.disk,
|
||||
'swap_mb': flavor.swap,
|
||||
'rxtx_factor': flavor.rxtx_factor,
|
||||
'eph_gb': eph,
|
||||
'is_public': True}
|
||||
workflow_data.update(override_data)
|
||||
resp = self.client.post(url, workflow_data)
|
||||
self.assertFormErrors(resp, 1, error_msg)
|
||||
|
||||
def test_update_flavor_invalid_vcpu_fails(self):
|
||||
error = 'Ensure this value is greater than or equal to 1.'
|
||||
data = {'vcpus': 0}
|
||||
self.generic_update_flavor_invalid_data_form_fails(override_data=data,
|
||||
error_msg=error)
|
||||
|
||||
def test_update_flavor_invalid_ram_fails(self):
|
||||
error = 'Ensure this value is greater than or equal to 1.'
|
||||
data = {'memory_mb': 0}
|
||||
self.generic_update_flavor_invalid_data_form_fails(override_data=data,
|
||||
error_msg=error)
|
||||
|
||||
def test_update_flavor_invalid_disk_gb_fails(self):
|
||||
error = 'Ensure this value is greater than or equal to 0.'
|
||||
data = {'disk_gb': -1}
|
||||
self.generic_update_flavor_invalid_data_form_fails(override_data=data,
|
||||
error_msg=error)
|
||||
|
||||
def test_update_flavor_invalid_swap_mb_fails(self):
|
||||
error = 'Ensure this value is greater than or equal to 0.'
|
||||
data = {'swap_mb': -1}
|
||||
self.generic_update_flavor_invalid_data_form_fails(override_data=data,
|
||||
error_msg=error)
|
||||
|
||||
def test_update_flavor_invalid_eph_gb_fails(self):
|
||||
error = 'Ensure this value is greater than or equal to 0.'
|
||||
data = {'eph_gb': -1}
|
||||
self.generic_update_flavor_invalid_data_form_fails(override_data=data,
|
||||
error_msg=error)
|
||||
|
||||
def test_update_flavor_invalid_rxtx_factor_fails(self):
|
||||
error = 'Ensure this value is greater than or equal to 1.'
|
||||
data = {'rxtx_factor': 0}
|
||||
self.generic_update_flavor_invalid_data_form_fails(override_data=data,
|
||||
error_msg=error)
|
||||
self.mock_add_tenant_to_flavor.assert_called_once_with(
|
||||
test.IsHttpRequest(), flavor.id, flavor_projects[0].id)
|
||||
|
@ -86,19 +86,17 @@ class UpdateView(workflows.WorkflowView):
|
||||
|
||||
def get_initial(self):
|
||||
flavor_id = self.kwargs['id']
|
||||
|
||||
try:
|
||||
# Get initial flavor information
|
||||
flavor = api.nova.flavor_get(self.request, flavor_id)
|
||||
if flavor.is_public:
|
||||
flavor_access = []
|
||||
else:
|
||||
flavor_access = api.nova.flavor_access_list(self.request,
|
||||
flavor_id)
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve flavor details.'),
|
||||
redirect=reverse_lazy(INDEX_URL))
|
||||
return {'flavor_id': flavor.id,
|
||||
'name': flavor.name,
|
||||
'vcpus': flavor.vcpus,
|
||||
'memory_mb': flavor.ram,
|
||||
'disk_gb': flavor.disk,
|
||||
'swap_mb': flavor.swap or 0,
|
||||
'rxtx_factor': flavor.rxtx_factor or 1,
|
||||
'eph_gb': getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral', None)}
|
||||
return {'flavor': flavor,
|
||||
'current_flavor_access': flavor_access}
|
||||
|
@ -109,11 +109,9 @@ class CreateFlavorInfo(workflows.Step):
|
||||
"rxtx_factor")
|
||||
|
||||
|
||||
class UpdateFlavorAccessAction(workflows.MembershipAction):
|
||||
class FlavorAccessAction(workflows.MembershipAction):
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdateFlavorAccessAction, self).__init__(request,
|
||||
*args,
|
||||
**kwargs)
|
||||
super(FlavorAccessAction, self).__init__(request, *args, **kwargs)
|
||||
err_msg = _('Unable to retrieve flavor access list. '
|
||||
'Please try again later.')
|
||||
context = args[0]
|
||||
@ -144,15 +142,12 @@ class UpdateFlavorAccessAction(workflows.MembershipAction):
|
||||
return
|
||||
|
||||
# Get list of flavor projects if the flavor is not public.
|
||||
flavor_id = context.get('flavor_id')
|
||||
flavor = context.get('flavor')
|
||||
flavor_access = []
|
||||
try:
|
||||
if flavor_id:
|
||||
flavor = api.nova.flavor_get(request, flavor_id)
|
||||
if not flavor.is_public:
|
||||
flavor_access = [project.tenant_id for project in
|
||||
api.nova.flavor_access_list(request,
|
||||
flavor_id)]
|
||||
if flavor and not flavor.is_public:
|
||||
flavor_access = [project.tenant_id for project in
|
||||
context['current_flavor_access']]
|
||||
except Exception:
|
||||
exceptions.handle(request, err_msg)
|
||||
|
||||
@ -160,11 +155,11 @@ class UpdateFlavorAccessAction(workflows.MembershipAction):
|
||||
|
||||
class Meta(object):
|
||||
name = _("Flavor Access")
|
||||
slug = "update_flavor_access"
|
||||
slug = "flavor_access"
|
||||
|
||||
|
||||
class UpdateFlavorAccess(workflows.UpdateMembersStep):
|
||||
action_class = UpdateFlavorAccessAction
|
||||
class FlavorAccess(workflows.UpdateMembersStep):
|
||||
action_class = FlavorAccessAction
|
||||
help_text = _("Select the projects where the flavors will be used. If no "
|
||||
"projects are selected, then the flavor will be available "
|
||||
"in all projects.")
|
||||
@ -174,8 +169,6 @@ class UpdateFlavorAccess(workflows.UpdateMembersStep):
|
||||
no_members_text = _("No projects selected. "
|
||||
"All projects can use the flavor.")
|
||||
show_roles = False
|
||||
depends_on = ("flavor_id",)
|
||||
contributes = ("flavor_access",)
|
||||
|
||||
def contribute(self, data, context):
|
||||
if data:
|
||||
@ -184,6 +177,15 @@ class UpdateFlavorAccess(workflows.UpdateMembersStep):
|
||||
return context
|
||||
|
||||
|
||||
class CreateFlavorAccess(FlavorAccess):
|
||||
contributes = ("flavor_access",)
|
||||
|
||||
|
||||
class UpdateFlavorAccess(FlavorAccess):
|
||||
depends_on = ("flavor", "current_flavor_access")
|
||||
contributes = ("flavor_access",)
|
||||
|
||||
|
||||
class CreateFlavor(workflows.Workflow):
|
||||
slug = "create_flavor"
|
||||
name = _("Create Flavor")
|
||||
@ -192,7 +194,7 @@ class CreateFlavor(workflows.Workflow):
|
||||
failure_message = _('Unable to create flavor "%s".')
|
||||
success_url = "horizon:admin:flavors:index"
|
||||
default_steps = (CreateFlavorInfo,
|
||||
UpdateFlavorAccess)
|
||||
CreateFlavorAccess)
|
||||
|
||||
def format_status_message(self, message):
|
||||
return message % self.context['name']
|
||||
@ -234,89 +236,29 @@ class CreateFlavor(workflows.Workflow):
|
||||
return True
|
||||
|
||||
|
||||
class UpdateFlavorInfoAction(CreateFlavorInfoAction):
|
||||
flavor_id = forms.CharField(widget=forms.widgets.HiddenInput)
|
||||
|
||||
class Meta(object):
|
||||
name = _("Flavor Information")
|
||||
slug = 'update_info'
|
||||
help_text = _("Edit the flavor details. Flavors define the sizes for "
|
||||
"RAM, disk, number of cores, and other resources. "
|
||||
"Flavors are selected when users deploy instances.")
|
||||
|
||||
def clean(self):
|
||||
name = self.cleaned_data.get('name')
|
||||
flavor_id = self.cleaned_data.get('flavor_id')
|
||||
try:
|
||||
flavors = api.nova.flavor_list(self.request, None)
|
||||
except Exception:
|
||||
flavors = []
|
||||
msg = _('Unable to get flavor list')
|
||||
exceptions.check_message(["Connection", "refused"], msg)
|
||||
raise
|
||||
# Check if there is no flavor with the same name
|
||||
if flavors is not None and name is not None:
|
||||
for flavor in flavors:
|
||||
if (flavor.name.lower() == name.lower() and
|
||||
flavor.id != flavor_id):
|
||||
error_msg = _('The name "%s" is already used by '
|
||||
'another flavor.') % name
|
||||
self._errors['name'] = self.error_class([error_msg])
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class UpdateFlavorInfo(workflows.Step):
|
||||
action_class = UpdateFlavorInfoAction
|
||||
depends_on = ("flavor_id",)
|
||||
contributes = ("name",
|
||||
"vcpus",
|
||||
"memory_mb",
|
||||
"disk_gb",
|
||||
"eph_gb",
|
||||
"swap_mb",
|
||||
"rxtx_factor")
|
||||
|
||||
|
||||
class UpdateFlavor(workflows.Workflow):
|
||||
slug = "update_flavor"
|
||||
name = _("Edit Flavor")
|
||||
finalize_button_name = _("Save")
|
||||
success_message = _('Modified flavor "%s".')
|
||||
failure_message = _('Unable to modify flavor "%s".')
|
||||
success_message = _('Modified flavor access of "%s".')
|
||||
failure_message = _('Unable to modify flavor access of "%s".')
|
||||
success_url = "horizon:admin:flavors:index"
|
||||
default_steps = (UpdateFlavorInfo,
|
||||
UpdateFlavorAccess)
|
||||
default_steps = (UpdateFlavorAccess,)
|
||||
|
||||
def format_status_message(self, message):
|
||||
return message % self.context['name']
|
||||
return message % self.context['flavor'].name
|
||||
|
||||
def handle(self, request, data):
|
||||
|
||||
flavor_projects = data["flavor_access"]
|
||||
is_public = not flavor_projects
|
||||
flavor = self.context['flavor']
|
||||
|
||||
def is_changed(flavor):
|
||||
return not (data['name'] == flavor.name and
|
||||
data['memory_mb'] == flavor.ram and
|
||||
data['vcpus'] == flavor.vcpus and
|
||||
data['disk_gb'] == flavor.disk and
|
||||
data['swap_mb'] == (flavor.swap or 0) and
|
||||
data['rxtx_factor'] == flavor.rxtx_factor and
|
||||
data['eph_gb'] == flavor.ephemeral)
|
||||
|
||||
def setup_access():
|
||||
for project in flavor_projects:
|
||||
api.nova.add_tenant_to_flavor(request,
|
||||
flavor.id,
|
||||
project)
|
||||
|
||||
def modify_access(flavor):
|
||||
# Check if the flavor info is not actually changed
|
||||
try:
|
||||
if flavor.is_public:
|
||||
old_flavor_projects = []
|
||||
else:
|
||||
old_flavor_projects = [project.tenant_id for project in
|
||||
api.nova.flavor_access_list(request,
|
||||
flavor.id)]
|
||||
self.context['current_flavor_access']]
|
||||
to_remove = [project for project in old_flavor_projects if project
|
||||
not in flavor_projects]
|
||||
to_add = [project for project in flavor_projects if project not in
|
||||
@ -329,53 +271,7 @@ class UpdateFlavor(workflows.Workflow):
|
||||
api.nova.add_tenant_to_flavor(request,
|
||||
flavor.id,
|
||||
project)
|
||||
|
||||
# Update flavor information
|
||||
try:
|
||||
flavor_id = data['flavor_id']
|
||||
flavor = api.nova.flavor_get(self.request, flavor_id)
|
||||
|
||||
# Check if the flavor info is not actually changed
|
||||
if not is_changed(flavor):
|
||||
try:
|
||||
modify_access(flavor)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to modify flavor access.'))
|
||||
return True
|
||||
|
||||
# Grab any existing extra specs, because flavor edit is currently
|
||||
# implemented as a delete followed by a create.
|
||||
extras_dict = api.nova.flavor_get_extras(self.request,
|
||||
flavor_id,
|
||||
raw=True)
|
||||
|
||||
# Mark the existing flavor as deleted.
|
||||
api.nova.flavor_delete(request, flavor_id)
|
||||
# Then create a new flavor with the same name but a new ID.
|
||||
# This is in the same try/except block as the delete call
|
||||
# because if the delete fails the API will error out because
|
||||
# active flavors can't have the same name.
|
||||
flavor = api.nova.flavor_create(request,
|
||||
data['name'],
|
||||
data['memory_mb'],
|
||||
data['vcpus'],
|
||||
data['disk_gb'],
|
||||
ephemeral=data['eph_gb'],
|
||||
swap=data['swap_mb'],
|
||||
is_public=is_public,
|
||||
rxtx_factor=data['rxtx_factor'])
|
||||
if (extras_dict):
|
||||
api.nova.flavor_extra_set(request, flavor.id, extras_dict)
|
||||
return True
|
||||
except Exception:
|
||||
exceptions.handle(request, ignore=True)
|
||||
# Error message will be shown by the workflow view.
|
||||
return False
|
||||
|
||||
# Add flavor access if the flavor is not public.
|
||||
try:
|
||||
setup_access()
|
||||
except Exception:
|
||||
exceptions.handle(request, _('Modified flavor information, '
|
||||
'but unable to modify flavor '
|
||||
'access.'))
|
||||
return True
|
||||
|
Loading…
x
Reference in New Issue
Block a user