Add Microversion support to Horizon

This patch adds microversion support to Horizon, as well as
documentation, service references, tests and an example.

Implements: blueprint microversion-support
Change-Id: Ic5aa559dbc13aa84d8e4a14b68f26f5d84183fa9
This commit is contained in:
Rob Cresswell 2016-12-08 13:37:36 +00:00 committed by Radomir Dopieralski
parent 39eea5ce22
commit 24e52a8765
6 changed files with 270 additions and 70 deletions

View File

@ -92,6 +92,7 @@ the following topic guides.
topics/workflows
topics/tables
topics/policy
topics/microversion_support
topics/angularjs
topics/testing
topics/javascript_testing

View File

@ -0,0 +1,47 @@
============================
Horizon Microversion Support
============================
Introduction
============
Several services use API microversions, which allows consumers of that API to
specify an exact version when making a request. This can be useful in ensuring
a feature continues to work as expected across many service releases.
Adding a feature that was introduced in a microversion
======================================================
1. Add the feature to the ``MICROVERSION_FEATURES`` dict in
``openstack_dashboard/api/microversions.py`` under the appropriate
service name. The feature should have at least two versions listed; the
minimum version (i.e. the version that introduced the feature) and
the current working version. Providing multiple versions reduces project
maintenance overheads and helps Horizon work with older service
deployments.
2. Use the ``is_feature_available`` function for your service to show or hide
the function.::
from openstack_dashboard.api import service
...
def allowed(self, request):
return service.is_feature_available('feature')
3. Send the correct microversion with ``get_microversion`` function in the API
layer.::
def resource_list(request):
try:
microversion = get_microversion(request, 'feature')
client = serviceclient(request, microversion)
return client.resource_list()
Microversion references
=======================
:Nova: http://docs.openstack.org/developer/nova/api_microversion_history.html
:Cinder: http://docs.openstack.org/developer/cinder/devref/api_microversion_history.html
:API-WG: http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html

View File

@ -0,0 +1,56 @@
# Copyright 2017 Cisco Systems
#
# 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.
import logging
LOG = logging.getLogger(__name__)
# A list of features and their supported microversions. Note that these are
# explicit functioning versions, not a range.
# There should be a minimum of two versions per feature. The first entry in
# this list should always be the lowest possible API microversion for a
# feature i.e. the version at which that feature was introduced. The second
# entry should be the current service version when the feature was added to
# horizon.
# Further documentation can be found at
# http://docs.openstack.org/developer/horizon/topics/microversion_support.html
MICROVERSION_FEATURES = {
"nova": {
"locked_attribute": ["2.9", "2.42"]
},
"cinder": {
"consistency_groups": ["2.0", "3.10"],
"message_list": ["3.5", "3.29"]
}
}
# NOTE(robcresswell): Since each client implements their own wrapper class for
# API objects, we'll need to allow that to be passed in. In the future this
# should be replaced by some common handling in Oslo.
def get_microversion_for_feature(service, feature, wrapper_class,
min_ver, max_ver):
"""Retrieves that highest known functional microversion for a feature"""
try:
service_features = MICROVERSION_FEATURES[service]
except KeyError:
LOG.debug("'%s' could not be found in the MICROVERSION_FEATURES "
"dict" % service)
return None
feature_versions = service_features[feature]
for version in reversed(feature_versions):
microversion = wrapper_class(version)
if microversion.matches(min_ver, max_ver):
return microversion
return None

View File

@ -43,6 +43,7 @@ from horizon.utils.memoized import memoized
from horizon.utils.memoized import memoized_with_request
from openstack_dashboard.api import base
from openstack_dashboard.api import microversions
from openstack_dashboard.api import network_base
from openstack_dashboard.contrib.developer.profiler import api as profiler
@ -52,7 +53,6 @@ LOG = logging.getLogger(__name__)
VERSIONS = base.APIVersionManager("compute", preferred_version=2)
VERSIONS.load_supported_version(1.1, {"client": nova_client, "version": 1.1})
VERSIONS.load_supported_version(2, {"client": nova_client, "version": 2})
VERSIONS.load_supported_version(2.9, {"client": nova_client, "version": 2.9})
# API static values
INSTANCE_ACTIVE_STATE = 'ACTIVE'
@ -62,6 +62,17 @@ INSECURE = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
CACERT = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
def get_microversion(request, feature):
client = novaclient(request)
min_ver, max_ver = api_versions._get_server_version_range(client)
return (microversions.get_microversion_for_feature(
'nova', feature, api_versions.APIVersion, min_ver, max_ver))
def is_feature_available(request, feature):
return bool(get_microversion(request, feature))
class VNCConsole(base.APIDictWrapper):
"""Wrapper for the "console" dictionary.
@ -849,12 +860,14 @@ def server_stop(request, instance_id):
@profiler.trace
def server_lock(request, instance_id):
novaclient(request).servers.lock(instance_id)
microversion = get_microversion(request, "locked_attribute")
novaclient(request, version=microversion).servers.lock(instance_id)
@profiler.trace
def server_unlock(request, instance_id):
novaclient(request).servers.unlock(instance_id)
microversion = get_microversion(request, "locked_attribute")
novaclient(request, version=microversion).servers.unlock(instance_id)
@profiler.trace

View File

@ -884,11 +884,12 @@ class LockInstance(policy.PolicyTargetMixin, tables.BatchAction):
# to only allow unlocked instances to be locked
def allowed(self, request, instance):
# if not locked, lock should be available
if getattr(instance, 'locked', False):
return False
if not api.nova.extension_supported('AdminActions', request):
return False
if not api.nova.is_feature_available(request, "locked_attribute"):
return False
return True
def action(self, request, obj_id):
@ -921,6 +922,8 @@ class UnlockInstance(policy.PolicyTargetMixin, tables.BatchAction):
return False
if not api.nova.extension_supported('AdminActions', request):
return False
if not api.nova.is_feature_available(request, "locked_attribute"):
return False
return True
def action(self, request, obj_id):

View File

@ -120,6 +120,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'server_list',
'tenant_absolute_limits',
'extension_supported',
'is_feature_available',
),
api.glance: ('image_list_detailed',),
api.network: (
@ -130,9 +131,11 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
})
def _get_index(self):
servers = self.servers.list()
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -191,7 +194,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({
api.nova: ('flavor_list', 'server_list', 'flavor_get',
'tenant_absolute_limits', 'extension_supported',),
'tenant_absolute_limits', 'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('floating_ip_simple_associate_supported',
'floating_ip_supported',
@ -202,9 +206,11 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
flavors = self.flavors.list()
full_flavors = OrderedDict([(f.id, f) for f in flavors])
search_opts = {'marker': None, 'paginate': True}
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \
@ -235,7 +241,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({
api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits',
'extension_supported',),
'extension_supported', 'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('floating_ip_simple_associate_supported',
'floating_ip_supported',
@ -248,9 +254,11 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
servers = self.servers.list()
servers[0] = volume_server
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -380,15 +388,15 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_pause',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_pause_instance(self):
servers = self.servers.list()
server = servers[0]
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -410,15 +418,15 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_pause',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_pause_instance_exception(self):
servers = self.servers.list()
server = servers[0]
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -441,15 +449,15 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_unpause',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_unpause_instance(self):
servers = self.servers.list()
server = servers[0]
server.status = "PAUSED"
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -471,7 +479,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_unpause',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_unpause_instance_exception(self):
@ -479,8 +488,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
server = servers[0]
server.status = "PAUSED"
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -584,15 +592,15 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_suspend',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_suspend_instance(self):
servers = self.servers.list()
server = servers[0]
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -615,15 +623,15 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_suspend',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_suspend_instance_exception(self):
servers = self.servers.list()
server = servers[0]
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -633,8 +641,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \
.AndReturn([servers, False])
api.network.servers_update_addresses(IsA(http.HttpRequest), servers)
api.nova.server_suspend(IsA(http.HttpRequest), six.text_type(server.id)) \
.AndRaise(self.exceptions.nova)
api.nova.server_suspend(
IsA(http.HttpRequest), six.text_type(server.id)
).AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
@ -646,7 +655,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_resume',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_resume_instance(self):
@ -654,8 +664,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
server = servers[0]
server.status = "SUSPENDED"
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -677,7 +686,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_resume',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available'),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_resume_instance_exception(self):
@ -685,8 +695,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
server = servers[0]
server.status = "SUSPENDED"
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
@ -710,7 +719,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_shelve',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_shelve_instance(self):
@ -739,7 +749,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_shelve',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_shelve_instance_exception(self):
@ -770,7 +781,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_unshelve',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_unshelve_instance(self):
@ -801,7 +813,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_unshelve',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_unshelve_instance_exception(self):
@ -833,7 +846,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_lock',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_lock_instance(self):
@ -842,6 +856,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.nova.extension_supported('AdminActions', IsA(
http.HttpRequest)).MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.glance.image_list_detailed(IgnoreArg()).AndReturn((
self.images.list(), False, False))
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -863,7 +880,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_lock',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_lock_instance_exception(self):
@ -872,6 +890,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.nova.extension_supported('AdminActions', IsA(
http.HttpRequest)).MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.glance.image_list_detailed(IgnoreArg()).AndReturn((
self.images.list(), False, False))
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -894,7 +915,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_unlock',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available'),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_unlock_instance(self):
@ -902,6 +924,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
server = servers[0]
api.nova.extension_supported('AdminActions', IsA(
http.HttpRequest)).MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.glance.image_list_detailed(IgnoreArg()).AndReturn((
@ -923,7 +948,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_unlock',
'server_list',
'flavor_list',
'extension_supported',),
'extension_supported',
'is_feature_available'),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_unlock_instance_exception(self):
@ -932,6 +958,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.nova.extension_supported('AdminActions', IsA(
http.HttpRequest)).MultipleTimes().AndReturn(True)
api.nova.is_feature_available(IsA(
http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.glance.image_list_detailed(IgnoreArg()).AndReturn((
self.images.list(), False, False))
search_opts = {'marker': None, 'paginate': True}
@ -956,7 +985,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
"server_get",
"instance_volumes_list",
"flavor_get",
"extension_supported"
"extension_supported",
'is_feature_available',
),
api.network: (
"server_security_groups",
@ -1002,6 +1032,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
.MultipleTimes().AndReturn(True)
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
@ -1365,7 +1398,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({
api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits',
'extension_supported',),
'extension_supported', 'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('floating_ip_simple_associate_supported',
'floating_ip_supported',
@ -1373,9 +1406,11 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
})
def _test_instances_index_retrieve_password_action(self):
servers = self.servers.list()
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -1556,6 +1591,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@helpers.create_stubs({api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'server_group_list',
@ -1791,6 +1827,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
self.test_launch_instance_get(only_one_network=True)
@helpers.create_stubs({api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'server_group_list',
@ -1890,6 +1927,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'port_create',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',
@ -2001,6 +2039,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'port_create',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',
@ -2123,6 +2162,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'port_list'),
api.nova: ('server_create',
'extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',
@ -2222,6 +2262,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.neutron: ('network_list',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list'),
@ -2298,6 +2339,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'port_create',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',
@ -2423,6 +2465,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'port_create',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',
@ -2494,6 +2537,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'volume_snapshot_list',),
api.network: ('security_group_list',),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',),
@ -2549,6 +2593,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'port_delete',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',
@ -2656,6 +2701,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.neutron: ('network_list',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',),
@ -2734,6 +2780,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.neutron: ('network_list',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'server_group_list',
@ -2839,6 +2886,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.neutron: ('network_list',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',),
@ -2936,6 +2984,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.neutron: ('network_list',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',),
@ -3079,6 +3128,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.neutron: ('network_list',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'server_group_list',
@ -3192,7 +3242,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({
api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits',
'extension_supported',),
'extension_supported', 'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('floating_ip_simple_associate_supported',
'floating_ip_supported',
@ -3203,9 +3253,11 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
limits = self.limits['absolute']
limits['totalInstancesUsed'] = 0
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -3239,7 +3291,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({
api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits',
'extension_supported',),
'extension_supported', 'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('floating_ip_simple_associate_supported',
'floating_ip_supported',
@ -3250,9 +3302,11 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
limits = self.limits['absolute']
limits['totalInstancesUsed'] = limits['maxTotalInstances']
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -3287,6 +3341,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.neutron: ('network_list',
'port_list'),
api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list',
@ -3388,7 +3443,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({
api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits',
'extension_supported',),
'extension_supported', 'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('floating_ip_simple_associate_supported',
'floating_ip_supported',
@ -3398,9 +3453,12 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
servers = self.servers.list()
server = self.servers.first()
server.status = "VERIFY_RESIZE"
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.extension_supported(
'AdminActions', IsA(http.HttpRequest)
).MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -3425,6 +3483,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
self.assertContains(res, "instances__revert")
@helpers.create_stubs({api.nova: ('extension_supported',
'is_feature_available',
'flavor_list',
'keypair_list',
'availability_zone_list'),
@ -3550,6 +3609,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@helpers.create_stubs({api.nova: ('server_get',
'flavor_list',
'tenant_absolute_limits',
'is_feature_available',
'extension_supported')})
def test_instance_resize_get(self):
server = self.servers.first()
@ -3622,6 +3682,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'flavor_list',
'flavor_get',
'tenant_absolute_limits',
'is_feature_available',
'extension_supported')})
def test_instance_resize_get_current_flavor_not_found(self):
server = self.servers.first()
@ -3659,6 +3720,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
instance_resize_post_stubs = {
api.nova: ('server_get', 'server_resize',
'flavor_list', 'flavor_get',
'is_feature_available',
'extension_supported')}
@helpers.create_stubs(instance_resize_post_stubs)
@ -3712,7 +3774,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@helpers.create_stubs({api.glance: ('image_list_detailed',),
api.nova: ('extension_supported',)})
api.nova: ('extension_supported',
'is_feature_available',)})
def test_rebuild_instance_get(self, expect_password_fields=True):
server = self.servers.first()
self._mock_glance_image_list_detailed(self.images.list())
@ -3754,7 +3817,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
instance_rebuild_post_stubs = {
api.nova: ('server_rebuild',
'extension_supported'),
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',)}
@helpers.create_stubs(instance_rebuild_post_stubs)
@ -3879,7 +3943,7 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
@django.test.utils.override_settings(API_RESULT_PAGE_SIZE=2)
@helpers.create_stubs({
api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits',
'extension_supported',),
'extension_supported', 'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('floating_ip_simple_associate_supported',
'floating_ip_supported',
@ -3892,9 +3956,12 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 2)
servers = self.servers.list()[:3]
api.nova.extension_supported('AdminActions',
IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.extension_supported(
'AdminActions', IsA(http.HttpRequest)
).MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -4032,7 +4099,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
class InstanceAjaxTests(helpers.TestCase):
@helpers.create_stubs({api.nova: ("server_get",
"flavor_get",
"extension_supported"),
"extension_supported",
"is_feature_available"),
api.network: ('servers_update_addresses',),
api.neutron: ("is_extension_supported",)})
def test_row_update(self):
@ -4042,8 +4110,11 @@ class InstanceAjaxTests(helpers.TestCase):
flavors = self.flavors.list()
full_flavors = OrderedDict([(f.id, f) for f in flavors])
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest))\
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
@ -4066,6 +4137,7 @@ class InstanceAjaxTests(helpers.TestCase):
@helpers.create_stubs({api.nova: ("server_get",
"flavor_get",
'is_feature_available',
"extension_supported"),
api.neutron: ("is_extension_supported",),
api.network: ('servers_update_addresses',)})
@ -4089,6 +4161,9 @@ class InstanceAjaxTests(helpers.TestCase):
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest))\
.MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
@ -4121,6 +4196,7 @@ class InstanceAjaxTests(helpers.TestCase):
@helpers.create_stubs({api.nova: ("server_get",
"flavor_get",
'is_feature_available',
"extension_supported"),
api.neutron: ("is_extension_supported",
"servers_update_addresses",)})
@ -4128,8 +4204,12 @@ class InstanceAjaxTests(helpers.TestCase):
server = self.servers.first()
instance_id = server.id
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest))\
.MultipleTimes().AndReturn(True)
api.nova.extension_supported(
'AdminActions', IsA(http.HttpRequest)
).MultipleTimes().AndReturn(True)
api.nova.is_feature_available(
IsA(http.HttpRequest), 'locked_attribute'
).MultipleTimes().AndReturn(True)
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.neutron.is_extension_supported(IsA(http.HttpRequest),