Fix H405 (multi line docstring) warnings (openstack_dashboard)

H405: multi line docstring summary not separated with an empty line

Closes-Bug: #1696996

Change-Id: Id895695663b19522d9cdc22f8b012e49680d708b
This commit is contained in:
Akihiro Motoki 2017-06-09 10:47:12 +00:00
parent 95629a337e
commit b9d0243c33
55 changed files with 438 additions and 460 deletions

View File

@ -199,8 +199,10 @@ class Quota(object):
class QuotaSet(Sequence): class QuotaSet(Sequence):
"""Wrapper for client QuotaSet objects which turns the individual quotas """Wrapper for client QuotaSet objects.
into Quota objects for easier handling/iteration.
This turns the individual quotas into Quota objects
for easier handling/iteration.
`QuotaSet` objects support a mix of `list` and `dict` methods; you can use `QuotaSet` objects support a mix of `list` and `dict` methods; you can use
the bracket notation (`qs["my_quota"] = 0`) to add new quota values, and the bracket notation (`qs["my_quota"] = 0`) to add new quota values, and
@ -229,8 +231,9 @@ class QuotaSet(Sequence):
return self.items[index] return self.items[index]
def __add__(self, other): def __add__(self, other):
"""Merge another QuotaSet into this one. Existing quotas are """Merge another QuotaSet into this one.
not overridden.
Existing quotas are not overridden.
""" """
if not isinstance(other, QuotaSet): if not isinstance(other, QuotaSet):
msg = "Can only add QuotaSet to QuotaSet, " \ msg = "Can only add QuotaSet to QuotaSet, " \

View File

@ -253,7 +253,9 @@ def update_pagination(entities, page_size, marker, sort_dir):
@profiler.trace @profiler.trace
def volume_list_paged(request, search_opts=None, marker=None, paginate=False, def volume_list_paged(request, search_opts=None, marker=None, paginate=False,
sort_dir="desc"): sort_dir="desc"):
"""To see all volumes in the cloud as an admin you can pass in a special """List volumes with pagination.
To see all volumes in the cloud as an admin you can pass in a special
search option: {'all_tenants': 1} search option: {'all_tenants': 1}
""" """
has_more_data = False has_more_data = False
@ -611,8 +613,7 @@ def volume_cg_snapshot_delete(request, cg_snapshot_id):
@memoized @memoized
def volume_backup_supported(request): def volume_backup_supported(request):
"""This method will determine if cinder supports backup. """This method will determine if cinder supports backup."""
"""
# TODO(lcheng) Cinder does not expose the information if cinder # TODO(lcheng) Cinder does not expose the information if cinder
# backup is configured yet. This is a workaround until that # backup is configured yet. This is a workaround until that
# capability is available. # capability is available.
@ -970,8 +971,7 @@ def list_extensions(cinder_api):
@memoized_with_request(list_extensions) @memoized_with_request(list_extensions)
def extension_supported(extensions, extension_name): def extension_supported(extensions, extension_name):
"""This method will determine if Cinder supports a given extension name. """This method will determine if Cinder supports a given extension name."""
"""
for extension in extensions: for extension in extensions:
if extension.name == extension_name: if extension.name == extension_name:
return True return True
@ -980,7 +980,9 @@ def extension_supported(extensions, extension_name):
@profiler.trace @profiler.trace
def transfer_list(request, detailed=True, search_opts=None): def transfer_list(request, detailed=True, search_opts=None):
"""To see all volumes transfers as an admin pass in a special """List volume transfers.
To see all volumes transfers as an admin pass in a special
search option: {'all_tenants': 1} search option: {'all_tenants': 1}
""" """
c_client = cinderclient(request) c_client = cinderclient(request)

View File

@ -239,9 +239,7 @@ def image_delete(request, image_id):
@profiler.trace @profiler.trace
def image_get(request, image_id): def image_get(request, image_id):
"""Returns an Image object populated with metadata for image """Returns an Image object populated with metadata for a given image."""
with supplied identifier.
"""
image = glanceclient(request).images.get(image_id) image = glanceclient(request).images.get(image_id)
return Image(image) return Image(image)
@ -525,8 +523,9 @@ class Namespace(BaseGlanceMetadefAPIResourceWrapper):
def filter_properties_target(namespaces_iter, def filter_properties_target(namespaces_iter,
resource_types, resource_types,
properties_target): properties_target):
"""Filter metadata namespaces based on the given resource types and """Filter metadata namespaces.
properties target.
Filtering is done based ongiven resource types and a properties target.
:param namespaces_iter: Metadata namespaces iterable. :param namespaces_iter: Metadata namespaces iterable.
:param resource_types: List of resource type names. :param resource_types: List of resource type names.

View File

@ -311,8 +311,9 @@ def get_default_domain(request, get_name=True):
def get_effective_domain_id(request): def get_effective_domain_id(request):
"""Gets the id of the default domain to use when creating Identity """Gets the id of the default domain.
objects. If the requests default domain is the same as DEFAULT_DOMAIN,
If the requests default domain is the same as DEFAULT_DOMAIN,
return None. return None.
""" """
default_domain = get_default_domain(request) default_domain = get_default_domain(request)

View File

@ -225,8 +225,9 @@ class FlavorExtraSpec(object):
def get_auth_params_from_request(request): def get_auth_params_from_request(request):
"""Extracts the properties from the request object needed by the novaclient """Extracts properties needed by novaclient call from the request object.
call below. These will be used to memoize the calls to novaclient
These will be used to memoize the calls to novaclient.
""" """
return ( return (
request.user.username, request.user.username,

View File

@ -11,8 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""API over the cinder service. """API over the cinder service."""
"""
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views import generic from django.views import generic
@ -28,14 +27,12 @@ CLIENT_KEYWORDS = {'marker', 'sort_dir', 'paginate'}
@urls.register @urls.register
class Volumes(generic.View): class Volumes(generic.View):
"""API for cinder volumes. """API for cinder volumes."""
"""
url_regex = r'cinder/volumes/$' url_regex = r'cinder/volumes/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a detailed list of volumes associated with the current user's """Get a detailed list of volumes associated with the current project.
project.
Example GET: Example GET:
http://localhost/api/cinder/volumes?paginate=true&sort_dir=asc http://localhost/api/cinder/volumes?paginate=true&sort_dir=asc
@ -99,8 +96,7 @@ class Volumes(generic.View):
@urls.register @urls.register
class Volume(generic.View): class Volume(generic.View):
"""API for cinder volume. """API for cinder volume."""
"""
url_regex = r'cinder/volumes/(?P<volume_id>[^/]+)/$' url_regex = r'cinder/volumes/(?P<volume_id>[^/]+)/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -118,8 +114,7 @@ class Volume(generic.View):
@urls.register @urls.register
class VolumeTypes(generic.View): class VolumeTypes(generic.View):
"""API for volume types. """API for volume types."""
"""
url_regex = r'cinder/volumetypes/$' url_regex = r'cinder/volumetypes/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -162,8 +157,7 @@ class VolumeMetadata(generic.View):
@urls.register @urls.register
class VolumeType(generic.View): class VolumeType(generic.View):
"""API for getting a volume type. """API for getting a volume type."""
"""
url_regex = r'cinder/volumetypes/(?P<volumetype_id>[^/]+)/$' url_regex = r'cinder/volumetypes/(?P<volumetype_id>[^/]+)/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -189,14 +183,12 @@ class VolumeType(generic.View):
@urls.register @urls.register
class VolumeSnapshots(generic.View): class VolumeSnapshots(generic.View):
"""API for cinder volume snapshots. """API for cinder volume snapshots."""
"""
url_regex = r'cinder/volumesnapshots/$' url_regex = r'cinder/volumesnapshots/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a detailed list of volume snapshots associated with the current """Get a list of volume snapshots associated with the current project.
user's project.
The listing result is an object with property "items". The listing result is an object with property "items".
""" """
@ -277,8 +269,7 @@ class VolumeTypeMetadata(generic.View):
@urls.register @urls.register
class Extensions(generic.View): class Extensions(generic.View):
"""API for cinder extensions. # API for cinder extensions.
"""
url_regex = r'cinder/extensions/$' url_regex = r'cinder/extensions/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -324,13 +315,13 @@ class TenantAbsoluteLimits(generic.View):
@urls.register @urls.register
class Services(generic.View): class Services(generic.View):
"""API for cinder services. """API for cinder services."""
"""
url_regex = r'cinder/services/$' url_regex = r'cinder/services/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a list of cinder services. """Get a list of cinder services.
Will return HTTP 501 status code if the service_list extension is Will return HTTP 501 status code if the service_list extension is
not supported. not supported.
""" """
@ -352,8 +343,7 @@ class Services(generic.View):
@urls.register @urls.register
class DefaultQuotaSets(generic.View): class DefaultQuotaSets(generic.View):
"""API for getting default quotas for cinder """API for getting default quotas for cinder"""
"""
url_regex = r'cinder/quota-sets/defaults/$' url_regex = r'cinder/quota-sets/defaults/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -400,8 +390,7 @@ class DefaultQuotaSets(generic.View):
@urls.register @urls.register
class QuotaSets(generic.View): class QuotaSets(generic.View):
"""API for setting quotas for a given project. """API for setting quotas for a given project."""
"""
url_regex = r'cinder/quota-sets/(?P<project_id>[0-9a-f]+)$' url_regex = r'cinder/quota-sets/(?P<project_id>[0-9a-f]+)$'
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)

View File

@ -11,8 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""API for the glance service. """API for the glance service."""
"""
from django import forms from django import forms
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@ -29,21 +28,18 @@ CLIENT_KEYWORDS = {'resource_type', 'marker',
@urls.register @urls.register
class Version(generic.View): class Version(generic.View):
"""API for active glance version. """API for active glance version."""
"""
url_regex = r'glance/version/$' url_regex = r'glance/version/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get active glance version. """Get active glance version."""
"""
return {'version': str(api.glance.get_version())} return {'version': str(api.glance.get_version())}
@urls.register @urls.register
class Image(generic.View): class Image(generic.View):
"""API for retrieving a single image """API for retrieving a single image"""
"""
url_regex = r'glance/images/(?P<image_id>[^/]+|default)/$' url_regex = r'glance/images/(?P<image_id>[^/]+|default)/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -95,14 +91,12 @@ class Image(generic.View):
@urls.register @urls.register
class ImageProperties(generic.View): class ImageProperties(generic.View):
"""API for retrieving only a custom properties of single image. """API for retrieving only a custom properties of single image."""
"""
url_regex = r'glance/images/(?P<image_id>[^/]+)/properties/' url_regex = r'glance/images/(?P<image_id>[^/]+)/properties/'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request, image_id): def get(self, request, image_id):
"""Get custom properties of specific image. """Get custom properties of specific image."""
"""
return api.glance.image_get(request, image_id).properties return api.glance.image_get(request, image_id).properties
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)
@ -123,8 +117,7 @@ class UploadObjectForm(forms.Form):
@urls.register @urls.register
class Images(generic.View): class Images(generic.View):
"""API for Glance images. """API for Glance images."""
"""
url_regex = r'glance/images/$' url_regex = r'glance/images/$'
@rest_utils.ajax() @rest_utils.ajax()

View File

@ -9,8 +9,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""API for the heat service. """API for the heat service."""
"""
from django.views import generic from django.views import generic
@ -21,8 +20,7 @@ from openstack_dashboard.api.rest import utils as rest_utils
@urls.register @urls.register
class Validate(generic.View): class Validate(generic.View):
"""API for validating a template """API for validating a template"""
"""
url_regex = r'heat/validate/$' url_regex = r'heat/validate/$'
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)
@ -40,14 +38,12 @@ class Validate(generic.View):
@urls.register @urls.register
class Services(generic.View): class Services(generic.View):
"""API for heat services. """API for heat services."""
"""
url_regex = r'heat/services/$' url_regex = r'heat/services/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a list of heat services. """Get a list of heat services."""
"""
if api.base.is_service_enabled(request, 'orchestration'): if api.base.is_service_enabled(request, 'orchestration'):
result = api.heat.service_list(request) result = api.heat.service_list(request)
return {'items': [u.to_dict() for u in result]} return {'items': [u.to_dict() for u in result]}

View File

@ -25,7 +25,9 @@ class NaNJSONEncoder(json.JSONEncoder):
super(NaNJSONEncoder, self).__init__(**kwargs) super(NaNJSONEncoder, self).__init__(**kwargs)
def iterencode(self, o, _one_shot=False): def iterencode(self, o, _one_shot=False):
"""The sole purpose of defining a custom JSONEncoder class is to """JSON encoder with NaN and float inf support.
The sole purpose of defining a custom JSONEncoder class is to
override floatstr() inner function, or more specifically the override floatstr() inner function, or more specifically the
representation of NaN and +/-float('inf') values in a JSON. Although representation of NaN and +/-float('inf') values in a JSON. Although
Infinity values are not supported by JSON standard, we still can Infinity values are not supported by JSON standard, we still can

View File

@ -11,8 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""API over the keystone service. """API over the keystone service."""
"""
from django.conf import settings from django.conf import settings
import django.http import django.http
@ -25,21 +24,18 @@ from openstack_dashboard.api.rest import utils as rest_utils
@urls.register @urls.register
class Version(generic.View): class Version(generic.View):
"""API for active keystone version. """API for active keystone version."""
"""
url_regex = r'keystone/version/$' url_regex = r'keystone/version/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get active keystone version. """Get active keystone version."""
"""
return {'version': str(api.keystone.get_version())} return {'version': str(api.keystone.get_version())}
@urls.register @urls.register
class Users(generic.View): class Users(generic.View):
"""API for keystone users. """API for keystone users."""
"""
url_regex = r'keystone/users/$' url_regex = r'keystone/users/$'
client_keywords = {'project_id', 'domain_id', 'group_id'} client_keywords = {'project_id', 'domain_id', 'group_id'}
@ -113,8 +109,7 @@ class Users(generic.View):
@urls.register @urls.register
class User(generic.View): class User(generic.View):
"""API for a single keystone user. """API for a single keystone user."""
"""
url_regex = r'keystone/users/(?P<id>[0-9a-f]+|current)$' url_regex = r'keystone/users/(?P<id>[0-9a-f]+|current)$'
@rest_utils.ajax() @rest_utils.ajax()
@ -172,8 +167,7 @@ class User(generic.View):
@urls.register @urls.register
class Roles(generic.View): class Roles(generic.View):
"""API over all roles. """API over all roles."""
"""
url_regex = r'keystone/roles/$' url_regex = r'keystone/roles/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -230,8 +224,7 @@ class Roles(generic.View):
@urls.register @urls.register
class Role(generic.View): class Role(generic.View):
"""API for a single role. """API for a single role."""
"""
url_regex = r'keystone/roles/(?P<id>[0-9a-f]+|default)$' url_regex = r'keystone/roles/(?P<id>[0-9a-f]+|default)$'
@rest_utils.ajax() @rest_utils.ajax()
@ -269,8 +262,7 @@ class Role(generic.View):
@urls.register @urls.register
class Domains(generic.View): class Domains(generic.View):
"""API over all domains. """API over all domains."""
"""
url_regex = r'keystone/domains/$' url_regex = r'keystone/domains/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -321,8 +313,7 @@ class Domains(generic.View):
@urls.register @urls.register
class Domain(generic.View): class Domain(generic.View):
"""API over a single domains. """API over a single domains."""
"""
url_regex = r'keystone/domains/(?P<id>[0-9a-f]+|default)$' url_regex = r'keystone/domains/(?P<id>[0-9a-f]+|default)$'
@rest_utils.ajax() @rest_utils.ajax()
@ -477,8 +468,7 @@ class Project(generic.View):
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request, id): def get(self, request, id):
"""Get a specific project by id. """Get a specific project by id."""
"""
return api.keystone.tenant_get(request, id).to_dict() return api.keystone.tenant_get(request, id).to_dict()
@rest_utils.ajax() @rest_utils.ajax()
@ -532,16 +522,13 @@ class ServiceCatalog(generic.View):
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Return the Keystone service catalog associated with the current """Return the service catalog associated with the current user."""
user.
"""
return request.user.service_catalog return request.user.service_catalog
@urls.register @urls.register
class UserSession(generic.View): class UserSession(generic.View):
"""API for a single keystone user. """API for a single keystone user."""
"""
url_regex = r'keystone/user-session/$' url_regex = r'keystone/user-session/$'
allowed_fields = { allowed_fields = {
'available_services_regions', 'available_services_regions',
@ -561,8 +548,7 @@ class UserSession(generic.View):
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get the current user session. """Get the current user session."""
"""
res = {k: getattr(request.user, k, None) for k in self.allowed_fields} res = {k: getattr(request.user, k, None) for k in self.allowed_fields}
if getattr(settings, 'ENABLE_CLIENT_TOKEN', True): if getattr(settings, 'ENABLE_CLIENT_TOKEN', True):
res['token'] = request.user.token.id res['token'] = request.user.token.id
@ -571,14 +557,12 @@ class UserSession(generic.View):
@urls.register @urls.register
class Services(generic.View): class Services(generic.View):
"""API for keystone services. """API for keystone services."""
"""
url_regex = r'keystone/services/$' url_regex = r'keystone/services/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a list of keystone services. """Get a list of keystone services."""
"""
region = request.user.services_region region = request.user.services_region
services = [] services = []
for i, service in enumerate(request.user.service_catalog): for i, service in enumerate(request.user.service_catalog):
@ -591,13 +575,13 @@ class Services(generic.View):
@urls.register @urls.register
class Groups(generic.View): class Groups(generic.View):
"""API over all groups. """API over all groups."""
"""
url_regex = r'keystone/groups/$' url_regex = r'keystone/groups/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a list of groups. """Get a list of groups.
The listing result is an object with property "items". The listing result is an object with property "items".
""" """
domain_context = request.session.get('domain_context') domain_context = request.session.get('domain_context')

View File

@ -13,8 +13,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""API for the network abstraction APIs. """API for the network abstraction APIs."""
"""
from django.views import generic from django.views import generic
@ -49,8 +48,7 @@ class SecurityGroups(generic.View):
@urls.register @urls.register
class FloatingIP(generic.View): class FloatingIP(generic.View):
"""API for a single floating IP address. """API for a single floating IP address."""
"""
url_regex = r'network/floatingip/$' url_regex = r'network/floatingip/$'
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)
@ -84,8 +82,7 @@ class FloatingIP(generic.View):
@urls.register @urls.register
class FloatingIPs(generic.View): class FloatingIPs(generic.View):
"""API for floating IP addresses. """API for floating IP addresses."""
"""
url_regex = r'network/floatingips/$' url_regex = r'network/floatingips/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -104,8 +101,7 @@ class FloatingIPs(generic.View):
@urls.register @urls.register
class FloatingIPPools(generic.View): class FloatingIPPools(generic.View):
"""API for floating IP pools. """API for floating IP pools."""
"""
url_regex = r'network/floatingippools/$' url_regex = r'network/floatingippools/$'
@rest_utils.ajax() @rest_utils.ajax()

View File

@ -12,8 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""API over the neutron service. """API over the neutron service."""
"""
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views import generic from django.views import generic
@ -72,7 +71,8 @@ class Networks(generic.View):
@urls.register @urls.register
class Subnets(generic.View): class Subnets(generic.View):
"""API for Neutron SubNets """API for Neutron Subnets
http://developer.openstack.org/api-ref-networking-v2.html#subnets http://developer.openstack.org/api-ref-networking-v2.html#subnets
""" """
url_regex = r'neutron/subnets/$' url_regex = r'neutron/subnets/$'
@ -119,6 +119,7 @@ class Subnets(generic.View):
@urls.register @urls.register
class Ports(generic.View): class Ports(generic.View):
"""API for Neutron Ports """API for Neutron Ports
http://developer.openstack.org/api-ref-networking-v2.html#ports http://developer.openstack.org/api-ref-networking-v2.html#ports
""" """
url_regex = r'neutron/ports/$' url_regex = r'neutron/ports/$'
@ -138,8 +139,7 @@ class Ports(generic.View):
@urls.register @urls.register
class Trunks(generic.View): class Trunks(generic.View):
"""API for neutron Trunks """API for neutron Trunks"""
"""
url_regex = r'neutron/trunks/$' url_regex = r'neutron/trunks/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -155,14 +155,12 @@ class Trunks(generic.View):
@urls.register @urls.register
class Services(generic.View): class Services(generic.View):
"""API for Neutron agents """API for Neutron agents"""
"""
url_regex = r'neutron/agents/$' url_regex = r'neutron/agents/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a list of agents """Get a list of agents"""
"""
if api.base.is_service_enabled(request, 'network') and \ if api.base.is_service_enabled(request, 'network') and \
api.neutron.is_extension_supported(request, 'agent'): api.neutron.is_extension_supported(request, 'agent'):
result = api.neutron.agent_list(request, **request.GET) result = api.neutron.agent_list(request, **request.GET)
@ -173,8 +171,7 @@ class Services(generic.View):
@urls.register @urls.register
class Extensions(generic.View): class Extensions(generic.View):
"""API for neutron extensions. """API for neutron extensions."""
"""
url_regex = r'neutron/extensions/$' url_regex = r'neutron/extensions/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -192,8 +189,7 @@ class Extensions(generic.View):
class DefaultQuotaSets(generic.View): class DefaultQuotaSets(generic.View):
"""API for getting default quotas for neutron """API for getting default quotas for neutron"""
"""
url_regex = r'neutron/quota-sets/defaults/$' url_regex = r'neutron/quota-sets/defaults/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -218,8 +214,7 @@ class DefaultQuotaSets(generic.View):
@urls.register @urls.register
class QuotasSets(generic.View): class QuotasSets(generic.View):
"""API for setting quotas of a given project. """API for setting quotas of a given project."""
"""
url_regex = r'neutron/quotas-sets/(?P<project_id>[0-9a-f]+)$' url_regex = r'neutron/quotas-sets/(?P<project_id>[0-9a-f]+)$'
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)

View File

@ -11,8 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""API over the nova service. """API over the nova service."""
"""
from collections import OrderedDict from collections import OrderedDict
from django.http import HttpResponse from django.http import HttpResponse
@ -34,8 +33,7 @@ from openstack_dashboard.usage import quotas
@urls.register @urls.register
class Snapshots(generic.View): class Snapshots(generic.View):
"""API for nova snapshots. """API for nova snapshots."""
"""
url_regex = r'nova/snapshots/$' url_regex = r'nova/snapshots/$'
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)
@ -50,14 +48,12 @@ class Snapshots(generic.View):
@urls.register @urls.register
class Keypairs(generic.View): class Keypairs(generic.View):
"""API for nova keypairs. """API for nova keypairs."""
"""
url_regex = r'nova/keypairs/$' url_regex = r'nova/keypairs/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a list of keypairs associated with the current logged-in """Get a list of keypairs associated with the current logged-in user.
account.
The listing result is an object with property "items". The listing result is an object with property "items".
""" """
@ -130,13 +126,13 @@ class Keypair(generic.View):
@urls.register @urls.register
class Services(generic.View): class Services(generic.View):
"""API for nova services. """API for nova services."""
"""
url_regex = r'nova/services/$' url_regex = r'nova/services/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get a list of nova services. """Get a list of nova services.
Will return HTTP 501 status code if the service_list extension is Will return HTTP 501 status code if the service_list extension is
not supported. not supported.
""" """
@ -150,8 +146,7 @@ class Services(generic.View):
@urls.register @urls.register
class AvailabilityZones(generic.View): class AvailabilityZones(generic.View):
"""API for nova availability zones. """API for nova availability zones."""
"""
url_regex = r'nova/availzones/$' url_regex = r'nova/availzones/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -173,8 +168,7 @@ class AvailabilityZones(generic.View):
@urls.register @urls.register
class Limits(generic.View): class Limits(generic.View):
"""API for nova limits. """API for nova limits."""
"""
url_regex = r'nova/limits/$' url_regex = r'nova/limits/$'
@rest_utils.ajax(json_encoder=json_encoder.NaNJSONEncoder) @rest_utils.ajax(json_encoder=json_encoder.NaNJSONEncoder)
@ -199,8 +193,7 @@ class Limits(generic.View):
@urls.register @urls.register
class ServerActions(generic.View): class ServerActions(generic.View):
"""API over all server actions. """API over all server actions."""
"""
url_regex = r'nova/servers/(?P<server_id>[^/]+)/actions/$' url_regex = r'nova/servers/(?P<server_id>[^/]+)/actions/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -219,8 +212,7 @@ class ServerActions(generic.View):
@urls.register @urls.register
class SecurityGroups(generic.View): class SecurityGroups(generic.View):
"""API over all server security groups. """API over all server security groups."""
"""
url_regex = r'nova/servers/(?P<server_id>[^/]+)/security-groups/$' url_regex = r'nova/servers/(?P<server_id>[^/]+)/security-groups/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -239,8 +231,7 @@ class SecurityGroups(generic.View):
@urls.register @urls.register
class Volumes(generic.View): class Volumes(generic.View):
"""API over all server volumes. """API over all server volumes."""
"""
url_regex = r'nova/servers/(?P<server_id>[^/]+)/volumes/$' url_regex = r'nova/servers/(?P<server_id>[^/]+)/volumes/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -259,14 +250,12 @@ class Volumes(generic.View):
@urls.register @urls.register
class RemoteConsoleInfo(generic.View): class RemoteConsoleInfo(generic.View):
"""API for remote console information. """API for remote console information."""
"""
url_regex = r'nova/servers/(?P<server_id>[^/]+)/console-info/$' url_regex = r'nova/servers/(?P<server_id>[^/]+)/console-info/$'
@rest_utils.ajax() @rest_utils.ajax()
def post(self, request, server_id): def post(self, request, server_id):
"""Gets information about an available remote console for the given """Gets information of a remote console for the given server.
server.
Example POST: Example POST:
http://localhost/api/nova/servers/abcd/console-info/ http://localhost/api/nova/servers/abcd/console-info/
@ -317,8 +306,7 @@ class RemoteConsoleInfo(generic.View):
@urls.register @urls.register
class ConsoleOutput(generic.View): class ConsoleOutput(generic.View):
"""API for console output. """API for console output."""
"""
url_regex = r'nova/servers/(?P<server_id>[^/]+)/console-output/$' url_regex = r'nova/servers/(?P<server_id>[^/]+)/console-output/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -339,8 +327,7 @@ class ConsoleOutput(generic.View):
@urls.register @urls.register
class Servers(generic.View): class Servers(generic.View):
"""API over all servers. """API over all servers."""
"""
url_regex = r'nova/servers/$' url_regex = r'nova/servers/$'
_optional_create = [ _optional_create = [
@ -415,8 +402,7 @@ class Servers(generic.View):
@urls.register @urls.register
class Server(generic.View): class Server(generic.View):
"""API for retrieving a single server """API for retrieving a single server"""
"""
url_regex = r'nova/servers/(?P<server_id>[^/]+|default)$' url_regex = r'nova/servers/(?P<server_id>[^/]+|default)$'
@rest_utils.ajax() @rest_utils.ajax()
@ -429,8 +415,7 @@ class Server(generic.View):
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)
def post(self, request, server_id): def post(self, request, server_id):
"""Perform a change to a server """Perform a change to a server"""
"""
operation = request.DATA.get('operation', 'none') operation = request.DATA.get('operation', 'none')
operations = { operations = {
'stop': api.nova.server_stop, 'stop': api.nova.server_stop,
@ -451,8 +436,7 @@ class Server(generic.View):
@urls.register @urls.register
class ServerGroups(generic.View): class ServerGroups(generic.View):
"""API for nova server groups. """API for nova server groups."""
"""
url_regex = r'nova/servergroups/$' url_regex = r'nova/servergroups/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -467,8 +451,7 @@ class ServerGroups(generic.View):
@urls.register @urls.register
class ServerMetadata(generic.View): class ServerMetadata(generic.View):
"""API for server metadata. """API for server metadata."""
"""
url_regex = r'nova/servers/(?P<server_id>[^/]+|default)/metadata$' url_regex = r'nova/servers/(?P<server_id>[^/]+|default)/metadata$'
@rest_utils.ajax() @rest_utils.ajax()
@ -496,8 +479,7 @@ class ServerMetadata(generic.View):
@urls.register @urls.register
class Extensions(generic.View): class Extensions(generic.View):
"""API for nova extensions. """API for nova extensions."""
"""
url_regex = r'nova/extensions/$' url_regex = r'nova/extensions/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -516,8 +498,7 @@ class Extensions(generic.View):
@urls.register @urls.register
class Flavors(generic.View): class Flavors(generic.View):
"""API for nova flavors. """API for nova flavors."""
"""
url_regex = r'nova/flavors/$' url_regex = r'nova/flavors/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -580,8 +561,7 @@ class Flavors(generic.View):
@urls.register @urls.register
class Flavor(generic.View): class Flavor(generic.View):
"""API for retrieving a single flavor """API for retrieving a single flavor"""
"""
url_regex = r'nova/flavors/(?P<flavor_id>[^/]+)/$' url_regex = r'nova/flavors/(?P<flavor_id>[^/]+)/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -654,8 +634,7 @@ class Flavor(generic.View):
@urls.register @urls.register
class FlavorExtraSpecs(generic.View): class FlavorExtraSpecs(generic.View):
"""API for managing flavor extra specs """API for managing flavor extra specs"""
"""
url_regex = r'nova/flavors/(?P<flavor_id>[^/]+)/extra-specs/$' url_regex = r'nova/flavors/(?P<flavor_id>[^/]+)/extra-specs/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -684,8 +663,7 @@ class FlavorExtraSpecs(generic.View):
@urls.register @urls.register
class AggregateExtraSpecs(generic.View): class AggregateExtraSpecs(generic.View):
"""API for managing aggregate extra specs """API for managing aggregate extra specs"""
"""
url_regex = r'nova/aggregates/(?P<aggregate_id>[^/]+)/extra-specs/$' url_regex = r'nova/aggregates/(?P<aggregate_id>[^/]+)/extra-specs/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -712,8 +690,7 @@ class AggregateExtraSpecs(generic.View):
@urls.register @urls.register
class DefaultQuotaSets(generic.View): class DefaultQuotaSets(generic.View):
"""API for getting default quotas for nova """API for getting default quotas for nova"""
"""
url_regex = r'nova/quota-sets/defaults/$' url_regex = r'nova/quota-sets/defaults/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -771,8 +748,7 @@ class DefaultQuotaSets(generic.View):
@urls.register @urls.register
class EditableQuotaSets(generic.View): class EditableQuotaSets(generic.View):
"""API for editable quotas. """API for editable quotas."""
"""
url_regex = r'nova/quota-sets/editable/$' url_regex = r'nova/quota-sets/editable/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -791,8 +767,7 @@ class EditableQuotaSets(generic.View):
@urls.register @urls.register
class QuotaSets(generic.View): class QuotaSets(generic.View):
"""API for setting quotas for a given project. """API for setting quotas for a given project."""
"""
url_regex = r'nova/quota-sets/(?P<project_id>[0-9a-f]+)$' url_regex = r'nova/quota-sets/(?P<project_id>[0-9a-f]+)$'
@rest_utils.ajax(data_required=True) @rest_utils.ajax(data_required=True)

View File

@ -11,8 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""API for the swift service. """API for the swift service."""
"""
import os import os
from django import forms from django import forms
@ -31,22 +31,19 @@ from openstack_dashboard.api import swift
@urls.register @urls.register
class Info(generic.View): class Info(generic.View):
"""API for information about the Swift installation. """API for information about the Swift installation."""
"""
url_regex = r'swift/info/$' url_regex = r'swift/info/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request): def get(self, request):
"""Get information about the Swift installation. """Get information about the Swift installation."""
"""
capabilities = api.swift.swift_get_capabilities(request) capabilities = api.swift.swift_get_capabilities(request)
return {'info': capabilities} return {'info': capabilities}
@urls.register @urls.register
class Containers(generic.View): class Containers(generic.View):
"""API for swift container listing for an account """API for swift container listing for an account"""
"""
url_regex = r'swift/containers/$' url_regex = r'swift/containers/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -71,15 +68,13 @@ class Containers(generic.View):
@urls.register @urls.register
class Container(generic.View): class Container(generic.View):
"""API for swift container level information """API for swift container level information"""
"""
url_regex = r'swift/containers/(?P<container>[^/]+)/metadata/$' url_regex = r'swift/containers/(?P<container>[^/]+)/metadata/$'
@rest_utils.ajax() @rest_utils.ajax()
def get(self, request, container): def get(self, request, container):
"""Get the container details """Get the container details"""
"""
return api.swift.swift_get_container(request, container).to_dict() return api.swift.swift_get_container(request, container).to_dict()
@rest_utils.ajax() @rest_utils.ajax()
@ -117,8 +112,7 @@ class Container(generic.View):
@urls.register @urls.register
class Objects(generic.View): class Objects(generic.View):
"""API for a list of swift objects """API for a list of swift objects"""
"""
url_regex = r'swift/containers/(?P<container>[^/]+)/objects/$' url_regex = r'swift/containers/(?P<container>[^/]+)/objects/$'
@rest_utils.ajax() @rest_utils.ajax()
@ -158,8 +152,7 @@ class UploadObjectForm(forms.Form):
@urls.register @urls.register
class Object(generic.View): class Object(generic.View):
"""API for a single swift object or pseudo-folder """API for a single swift object or pseudo-folder"""
"""
url_regex = r'swift/containers/(?P<container>[^/]+)/object/' \ url_regex = r'swift/containers/(?P<container>[^/]+)/object/' \
'(?P<object_name>.+)$' '(?P<object_name>.+)$'
@ -219,8 +212,7 @@ class Object(generic.View):
api.swift.swift_delete_object(request, container, object_name) api.swift.swift_delete_object(request, container, object_name)
def get(self, request, container, object_name): def get(self, request, container, object_name):
"""Get the object contents. """Get the object contents."""
"""
obj = api.swift.swift_get_object( obj = api.swift.swift_get_object(
request, request,
container, container,
@ -245,8 +237,7 @@ class Object(generic.View):
@urls.register @urls.register
class ObjectMetadata(generic.View): class ObjectMetadata(generic.View):
"""API for a single swift object """API for a single swift object"""
"""
url_regex = r'swift/containers/(?P<container>[^/]+)/metadata/' \ url_regex = r'swift/containers/(?P<container>[^/]+)/metadata/' \
'(?P<object_name>.+)$' '(?P<object_name>.+)$'
@ -262,8 +253,7 @@ class ObjectMetadata(generic.View):
@urls.register @urls.register
class ObjectCopy(generic.View): class ObjectCopy(generic.View):
"""API to copy a swift object """API to copy a swift object"""
"""
url_regex = r'swift/containers/(?P<container>[^/]+)/copy/' \ url_regex = r'swift/containers/(?P<container>[^/]+)/copy/' \
'(?P<object_name>.+)$' '(?P<object_name>.+)$'

View File

@ -20,13 +20,13 @@ urlpatterns = []
# @register below, and the import the endpoint module in the # @register below, and the import the endpoint module in the
# rest_api/__init__.py module # rest_api/__init__.py module
def register(view): def register(view):
'''Register API views to respond to a regex pattern (url_regex on the """Register API views to respond to a regex pattern.
view class).
``url_regex`` on a wrapped view class is used as the regex pattern.
The view should be a standard Django class-based view implementing an The view should be a standard Django class-based view implementing an
as_view() method. The url_regex attribute of the view should be a standard as_view() method. The url_regex attribute of the view should be a standard
Django URL regex pattern. Django URL regex pattern.
''' """
p = urls.url(view.url_regex, view.as_view()) p = urls.url(view.url_regex, view.as_view())
urlpatterns.append(p) urlpatterns.append(p)
return view return view

View File

@ -75,7 +75,9 @@ class JSONResponse(_RestResponse):
def ajax(authenticated=True, data_required=False, def ajax(authenticated=True, data_required=False,
json_encoder=json.JSONEncoder): json_encoder=json.JSONEncoder):
'''Provide a decorator to wrap a view method so that it may exist in an """Decorator to allow the wrappered view to exist in an AJAX environment.
Provide a decorator to wrap a view method so that it may exist in an
entirely AJAX environment: entirely AJAX environment:
- data decoded from JSON as input and data coded as JSON as output - data decoded from JSON as input and data coded as JSON as output
@ -98,7 +100,7 @@ def ajax(authenticated=True, data_required=False,
Methods returning nothing (or None explicitly) will result in a 204 "NO Methods returning nothing (or None explicitly) will result in a 204 "NO
CONTENT" being returned to the caller. CONTENT" being returned to the caller.
''' """
def decorator(function, authenticated=authenticated, def decorator(function, authenticated=authenticated,
data_required=data_required): data_required=data_required):
@functools.wraps(function, @functools.wraps(function,
@ -173,7 +175,9 @@ def parse_filters_kwargs(request, client_keywords=None):
def post2data(func): def post2data(func):
"""The sole purpose of this decorator is to restore original form values """Decorator to restore original form values along with their types.
The sole purpose of this decorator is to restore original form values
along with their types stored on client-side under key $$originalJSON. along with their types stored on client-side under key $$originalJSON.
This in turn prevents the loss of field types when they are passed with This in turn prevents the loss of field types when they are passed with
header 'Content-Type: multipart/form-data', which is needed to pass a header 'Content-Type: multipart/form-data', which is needed to pass a

View File

@ -71,9 +71,9 @@ class HypervisorViewTest(test.BaseAdminViewTests):
'hypervisor_stats', 'hypervisor_stats',
'service_list')}) 'service_list')})
def test_service_list_unavailable(self): def test_service_list_unavailable(self):
"""test that error message should be returned when # test that error message should be returned when
nova.service_list isn't available # nova.service_list isn't available.
"""
hypervisors = self.hypervisors.list() hypervisors = self.hypervisors.list()
stats = self.hypervisors.stats stats = self.hypervisors.stats
api.nova.hypervisor_list(IsA(http.HttpRequest)).AndReturn(hypervisors) api.nova.hypervisor_list(IsA(http.HttpRequest)).AndReturn(hypervisors)

View File

@ -45,9 +45,8 @@ class ImageURLField(forms.URLField):
def create_image_metadata(data): def create_image_metadata(data):
"""Use the given dict of image form data to generate the metadata used for """Generate metadata dict for a new image from a given form data."""
creating the image in glance.
"""
# Glance does not really do anything with container_format at the # Glance does not really do anything with container_format at the
# moment. It requires it is set to the same disk_format for the three # moment. It requires it is set to the same disk_format for the three
# Amazon image types, otherwise it just treats them as 'bare.' As such # Amazon image types, otherwise it just treats them as 'bare.' As such

View File

@ -19,13 +19,12 @@ from openstack_dashboard.api import glance
def get_available_images(request, project_id=None, images_cache=None): def get_available_images(request, project_id=None, images_cache=None):
"""Returns a list of images that are public or owned by the given """Returns a list of images that are public or owned by the given project.
project_id. If project_id is not specified, only public images
are returned. If project_id is not specified, only public images are returned.
:param images_cache: An optional dict-like object in which to :param images_cache: An optional dict-like object in which to
cache public and per-project id image metadata. cache public and per-project id image metadata.
""" """
if images_cache is None: if images_cache is None:
images_cache = {} images_cache = {}

View File

@ -101,9 +101,6 @@ class DeleteInstance(policy.PolicyTargetMixin, tables.DeleteAction):
) )
def allowed(self, request, instance=None): def allowed(self, request, instance=None):
"""Allow delete action if instance is in error state or not currently
being deleted.
"""
error_state = False error_state = False
if instance: if instance:
error_state = (instance.status == 'ERROR') error_state = (instance.status == 'ERROR')

View File

@ -3920,9 +3920,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.network: ('servers_update_addresses',), api.network: ('servers_update_addresses',),
}) })
def test_index_form_action_with_pagination(self): def test_index_form_action_with_pagination(self):
"""The form action on the next page should have marker # The form action on the next page should have marker
object from the previous page last element. # object from the previous page last element.
"""
page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 2) page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 2)
servers = self.servers.list()[:3] servers = self.servers.list()[:3]
@ -3984,9 +3984,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.glance: ('image_list_detailed',), api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)}) api.network: ('servers_update_addresses',)})
def test_delete_instance_with_pagination(self): def test_delete_instance_with_pagination(self):
"""Instance should be deleted from # Instance should be deleted from the next page.
the next page.
"""
page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 2) page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 2)
servers = self.servers.list()[:3] servers = self.servers.list()[:3]
server = servers[-1] server = servers[-1]

View File

@ -35,10 +35,10 @@ def flavor_list(request):
def sort_flavor_list(request, flavors): def sort_flavor_list(request, flavors):
"""Utility method to sort a list of flavors. """Utility method to sort a list of flavors.
By default, returns the available flavors, sorted by RAM
usage (ascending). Override these behaviours with a By default, returns the available flavors, sorted by RAM usage (ascending).
CREATE_INSTANCE_FLAVOR_SORT dict Override these behaviours with a ``CREATE_INSTANCE_FLAVOR_SORT`` dict
in local_settings.py. in ``local_settings.py``.
""" """
def get_key(flavor, sort_key): def get_key(flavor, sort_key):
try: try:

View File

@ -85,8 +85,10 @@ console_invalid_status = {
class TranslationHelper(object): class TranslationHelper(object):
"""Helper class to provide the translations of instances, networks, """Helper class to provide the translations.
routers and ports from other parts of the code to the network topology
This allows the network topology to access the translated strings
for various resources defined in other parts of the code.
""" """
def __init__(self): def __init__(self):
# turn translation tuples into dicts for easy access # turn translation tuples into dicts for easy access

View File

@ -228,8 +228,7 @@ class SecurityGroupsViewTests(test.TestCase):
self._create_security_group(sec_group) self._create_security_group(sec_group)
def test_create_security_groups_special_chars(self): def test_create_security_groups_special_chars(self):
"""Ensure that a group name is not restricted to alphanumeric """Ensure non-alphanumeric characters can be used as a group name.
characters.
bug #1233501 Security group names cannot contain at characters bug #1233501 Security group names cannot contain at characters
bug #1224576 Security group names cannot contain spaces bug #1224576 Security group names cannot contain spaces

View File

@ -566,9 +566,6 @@ class DetachVolume(tables.BatchAction):
class AttachedInstanceColumn(tables.WrappingColumn): class AttachedInstanceColumn(tables.WrappingColumn):
"""Customized column class that does complex processing on the attachments
for a volume instance.
"""
def get_raw_data(self, attachment): def get_raw_data(self, attachment):
request = self.table.request request = self.table.request
return safestring.mark_safe(get_attachment_name(request, attachment)) return safestring.mark_safe(get_attachment_name(request, attachment))

View File

@ -93,8 +93,9 @@ class BaseWebObject(unittest.TestCase):
self.driver.implicitly_wait(self.conf.selenium.implicit_wait) self.driver.implicitly_wait(self.conf.selenium.implicit_wait)
def _wait_until(self, predicate, timeout=None, poll_frequency=0.5): def _wait_until(self, predicate, timeout=None, poll_frequency=0.5):
"""Wait until the value returned by predicate is not False or """Wait until the value returned by predicate is not False.
the timeout is elapsed.
It also returns when the timeout is elapsed.
'predicate' takes the driver as argument. 'predicate' takes the driver as argument.
""" """
if not timeout: if not timeout:
@ -103,10 +104,12 @@ class BaseWebObject(unittest.TestCase):
predicate) predicate)
def _wait_till_text_present_in_element(self, element, texts, timeout=None): def _wait_till_text_present_in_element(self, element, texts, timeout=None):
"""Waiting for a text to appear in a certain element very often is """Waiting for a text to appear in a certain element.
actually waiting for a _different_ element with a different text to
appear in place of an old element. So a way to avoid capturing stale Most frequent usage is actually to wait for a _different_ element
element reference should be provided for this use case. with a different text to appear in place of an old element.
So a way to avoid capturing stale element reference should be provided
for this use case.
Better to wrap getting entity status cell in a lambda Better to wrap getting entity status cell in a lambda
to avoid problems with cell being replaced with totally different to avoid problems with cell being replaced with totally different

View File

@ -32,7 +32,9 @@ def _is_test_cls(cls):
def _mark_method_skipped(meth, reason): def _mark_method_skipped(meth, reason):
"""Mark method as skipped by replacing the actual method with wrapper """Decorate to mark method as skipped.
This marks method as skipped by replacing the actual method with wrapper
that raises the testtools.testcase.TestSkipped exception. that raises the testtools.testcase.TestSkipped exception.
""" """
@ -71,8 +73,9 @@ def _get_skip_method(obj):
def services_required(*req_services): def services_required(*req_services):
"""Decorator for marking test's service requirements, """Decorator for marking test's service requirements.
if requirements are not met in the configuration file
If requirements are not met in the configuration file
test is marked as skipped. test is marked as skipped.
Usage: Usage:
@ -110,8 +113,9 @@ def services_required(*req_services):
def _parse_compound_config_option_value(option_name): def _parse_compound_config_option_value(option_name):
"""Parses the value of a given config option where option's section name is """Parses the value of a given config option.
separated from option name by '.'.
The section name of the option is separated from option name by '.'.
""" """
name_parts = option_name.split('.') name_parts = option_name.split('.')
name_parts.reverse() name_parts.reverse()
@ -163,8 +167,7 @@ def skip_because(**kwargs):
def attach_video(func): def attach_video(func):
"""Notify test runner to attach test video in any case """Notify test runner to attach test video in any case"""
"""
@functools.wraps(func) @functools.wraps(func)
def wrapper(self, *args, **kwgs): def wrapper(self, *args, **kwgs):

View File

@ -200,8 +200,9 @@ class BaseTestCase(testtools.TestCase):
super(BaseTestCase, self).addOnException(wrapped_handler) super(BaseTestCase, self).addOnException(wrapped_handler)
def _configure_log(self): def _configure_log(self):
"""Configure log to capture test logs include selenium logs in order """Configure log to capture test logs include selenium logs.
to attach them if test will be broken.
This allows us to attach them if test will be broken.
""" """
# clear other handlers to set target handler # clear other handlers to set target handler
ROOT_LOGGER.handlers[:] = [] ROOT_LOGGER.handlers[:] = []
@ -276,8 +277,10 @@ class BaseTestCase(testtools.TestCase):
return rec(_log) return rec(_log)
def zoom_out(self, times=3): def zoom_out(self, times=3):
"""Zooming out prevents different elements being driven out of xvfb """Zooming out a specified element.
viewport (which in Selenium>=2.50.1 prevents interaction with them.
It prevents different elements being driven out of xvfb viewport
(which in Selenium>=2.50.1 prevents interaction with them).
""" """
html = self.driver.find_element(by.By.TAG_NAME, 'html') html = self.driver.find_element(by.By.TAG_NAME, 'html')
html.send_keys(keys.Keys.NULL) html.send_keys(keys.Keys.NULL)

View File

@ -61,6 +61,7 @@ class MetadatadefinitionsPage(basepage.BaseNavigationPage):
def json_load_template(self, namespace_template_name): def json_load_template(self, namespace_template_name):
"""Read template for namespace creation """Read template for namespace creation
:param namespace_template_name: Path to template :param namespace_template_name: Path to template
:return = json data container :return = json data container
""" """

View File

@ -56,6 +56,7 @@ class PageObject(basewebobject.BaseWebObject):
def switch_window(self, window_name=None, window_index=None): def switch_window(self, window_name=None, window_index=None):
"""Switches focus between the webdriver windows. """Switches focus between the webdriver windows.
Args: Args:
- window_name: The name of the window to switch to. - window_name: The name of the window to switch to.
- window_index: The index of the window handle to switch to. - window_index: The index of the window handle to switch to.

View File

@ -42,15 +42,15 @@ class BaseRegion(basewebobject.BaseWebObject):
self._dynamic_properties = {} self._dynamic_properties = {}
def __getattr__(self, name): def __getattr__(self, name):
"""It is not possible to create property bounded just to object # It is not possible to create property bounded just to object
and not class at runtime, therefore it is necessary to # and not class at runtime, therefore it is necessary to
override __getattr__ and make fake 'properties' by storing them in # override __getattr__ and make fake 'properties' by storing them in
the protected attribute _dynamic_attributes and returning result # the protected attribute _dynamic_attributes and returning result
of the method associated with the specified attribute. # of the method associated with the specified attribute.
# This way the feeling of having regions accessed as 'properties'
# is created, which is one of the requirement of page object pattern.
This way the feeling of having regions accessed as 'properties'
is created, which is one of the requirement of page object pattern.
"""
try: try:
return self._dynamic_properties[name] return self._dynamic_properties[name]
except KeyError: except KeyError:

View File

@ -287,9 +287,8 @@ class BaseFormRegion(baseregion.BaseRegion):
_default_form_locator = (by.By.CSS_SELECTOR, 'div.modal-dialog') _default_form_locator = (by.By.CSS_SELECTOR, 'div.modal-dialog')
def __init__(self, driver, conf, src_elem=None): def __init__(self, driver, conf, src_elem=None):
"""In most cases forms can be located through _default_form_locator, # In most cases forms can be located through _default_form_locator,
so specifying source element can be skipped. # so specifying source element can be skipped.
"""
if src_elem is None: if src_elem is None:
# fake self.src_elem must be set up in order self._get_element work # fake self.src_elem must be set up in order self._get_element work
self.src_elem = driver self.src_elem = driver
@ -449,9 +448,10 @@ class TabbedFormRegion(FormRegion):
class DateFormRegion(BaseFormRegion): class DateFormRegion(BaseFormRegion):
"""Form that queries data to table that is regularly below the form, """Form that queries data to table that is regularly below the form.
typical example is located on Project/Compute/Overview page.
""" A typical example is located on Project/Compute/Overview page.
"""
_from_field_locator = (by.By.CSS_SELECTOR, 'input#id_start') _from_field_locator = (by.By.CSS_SELECTOR, 'input#id_start')
_to_field_locator = (by.By.CSS_SELECTOR, 'input#id_end') _to_field_locator = (by.By.CSS_SELECTOR, 'input#id_end')

View File

@ -221,8 +221,9 @@ class DropDownMenuRegion(baseregion.BaseRegion):
class UserDropDownMenuRegion(DropDownMenuRegion): class UserDropDownMenuRegion(DropDownMenuRegion):
"""Drop down menu located in the right side of the topbar, """Drop down menu located in the right side of the topbar.
contains links to settings and help.
This menu contains links to settings and help.
""" """
_settings_link_locator = (by.By.CSS_SELECTOR, _settings_link_locator = (by.By.CSS_SELECTOR,
'a[href*="/settings/"]') 'a[href*="/settings/"]')

View File

@ -208,6 +208,7 @@ class TableRegion(baseregion.BaseRegion):
def assert_definition(self, expected_table_definition, sorting=False): def assert_definition(self, expected_table_definition, sorting=False):
"""Checks that actual table is expected one. """Checks that actual table is expected one.
Items to compare: 'next' and 'prev' links, count of rows and names of Items to compare: 'next' and 'prev' links, count of rows and names of
elements in list elements in list
:param expected_table_definition: expected values (dictionary) :param expected_table_definition: expected values (dictionary)
@ -225,8 +226,7 @@ class TableRegion(baseregion.BaseRegion):
def bind_table_action(action_name): def bind_table_action(action_name):
"""A decorator to bind table region method to an actual table action """Decorator to bind table region method to an actual table action button.
button.
Many table actions when started (by clicking a corresponding button Many table actions when started (by clicking a corresponding button
in UI) lead to some form showing up. To further interact with this form, in UI) lead to some form showing up. To further interact with this form,

View File

@ -43,6 +43,7 @@ class TestDownloadRCFile(helpers.AdminTestCase):
def test_download_rc_v2_file(self): def test_download_rc_v2_file(self):
"""This is a basic scenario test: """This is a basic scenario test:
Steps: Steps:
1) Login to Horizon Dashboard as admin user 1) Login to Horizon Dashboard as admin user
2) Navigate to Project > Compute > Access & Security > API Access tab 2) Navigate to Project > Compute > Access & Security > API Access tab
@ -62,6 +63,7 @@ class TestDownloadRCFile(helpers.AdminTestCase):
@decorators.skip_because(bugs=['1584057']) @decorators.skip_because(bugs=['1584057'])
def test_download_rc_v3_file(self): def test_download_rc_v3_file(self):
"""This is a basic scenario test: """This is a basic scenario test:
Steps: Steps:
1) Login to Horizon Dashboard as admin user 1) Login to Horizon Dashboard as admin user
2) Navigate to Project > Compute > Access & Security > API Access tab 2) Navigate to Project > Compute > Access & Security > API Access tab

View File

@ -25,6 +25,7 @@ class TestDefaults(helpers.AdminTestCase):
def test_update_defaults(self): def test_update_defaults(self):
"""Tests the Update Default Quotas functionality: """Tests the Update Default Quotas functionality:
1) Login as Admin and go to Admin > System > Defaults 1) Login as Admin and go to Admin > System > Defaults
2) Updates default Quotas by adding a random number between 1 and 10 2) Updates default Quotas by adding a random number between 1 and 10
3) Verifies that the updated values are present in the 3) Verifies that the updated values are present in the

View File

@ -80,6 +80,7 @@ class TestFlavors(helpers.AdminTestCase):
def test_flavor_create(self): def test_flavor_create(self):
"""tests the flavor creation and deletion functionalities: """tests the flavor creation and deletion functionalities:
* creates a new flavor * creates a new flavor
* verifies the flavor appears in the flavors table * verifies the flavor appears in the flavors table
* deletes the newly created flavor * deletes the newly created flavor
@ -89,8 +90,7 @@ class TestFlavors(helpers.AdminTestCase):
self._delete_flavor(self.FLAVOR_NAME) self._delete_flavor(self.FLAVOR_NAME)
def test_flavor_update_info(self): def test_flavor_update_info(self):
"""Tests the flavor Edit row action functionality: """Tests the flavor Edit row action functionality"""
"""
self._create_flavor(self.FLAVOR_NAME) self._create_flavor(self.FLAVOR_NAME)

View File

@ -20,6 +20,7 @@ class TestHostAggregates(helpers.AdminTestCase):
def test_host_aggregate_create(self): def test_host_aggregate_create(self):
"""tests the host aggregate creation and deletion functionalities: """tests the host aggregate creation and deletion functionalities:
* creates a new host aggregate * creates a new host aggregate
* verifies the host aggregate appears in the host aggregates table * verifies the host aggregate appears in the host aggregates table
* deletes the newly created host aggregate * deletes the newly created host aggregate

View File

@ -74,6 +74,7 @@ class TestImagesBasic(TestImagesLegacy):
@decorators.skip_because(bugs=['1595335']) @decorators.skip_because(bugs=['1595335'])
def test_image_create_delete(self): def test_image_create_delete(self):
"""tests the image creation and deletion functionalities: """tests the image creation and deletion functionalities:
* creates a new image from horizon.conf http_image * creates a new image from horizon.conf http_image
* verifies the image appears in the images table as active * verifies the image appears in the images table as active
* deletes the newly created image * deletes the newly created image
@ -84,6 +85,7 @@ class TestImagesBasic(TestImagesLegacy):
def test_image_create_delete_from_local_file(self): def test_image_create_delete_from_local_file(self):
"""tests the image creation and deletion functionalities: """tests the image creation and deletion functionalities:
* downloads image from horizon.conf stated in http_image * downloads image from horizon.conf stated in http_image
* creates the image from the downloaded file * creates the image from the downloaded file
* verifies the image appears in the images table as active * verifies the image appears in the images table as active
@ -96,20 +98,21 @@ class TestImagesBasic(TestImagesLegacy):
def test_images_pagination(self): def test_images_pagination(self):
"""This test checks images pagination """This test checks images pagination
Steps:
1) Login to Horizon Dashboard as horizon user Steps:
2) Navigate to user settings page 1) Login to Horizon Dashboard as horizon user
3) Change 'Items Per Page' value to 1 2) Navigate to user settings page
4) Go to Project -> Compute -> Images page 3) Change 'Items Per Page' value to 1
5) Check that only 'Next' link is available, only one image is 4) Go to Project -> Compute -> Images page
available (and it has correct name) 5) Check that only 'Next' link is available, only one image is
6) Click 'Next' and check that both 'Prev' and 'Next' links are available (and it has correct name)
available, only one image is available (and it has correct name) 6) Click 'Next' and check that both 'Prev' and 'Next' links are
7) Click 'Next' and check that only 'Prev' link is available, available, only one image is available (and it has correct name)
only one image is visible (and it has correct name) 7) Click 'Next' and check that only 'Prev' link is available,
8) Click 'Prev' and check results (should be the same as for step6) only one image is visible (and it has correct name)
9) Click 'Prev' and check results (should be the same as for step5) 8) Click 'Prev' and check results (should be the same as for step6)
10) Go to user settings page and restore 'Items Per Page' 9) Click 'Prev' and check results (should be the same as for step5)
10) Go to user settings page and restore 'Items Per Page'
""" """
default_image_list = self.CONFIG.image.images_list default_image_list = self.CONFIG.image.images_list
items_per_page = 1 items_per_page = 1
@ -148,6 +151,7 @@ class TestImagesBasic(TestImagesLegacy):
def test_update_image_metadata(self): def test_update_image_metadata(self):
"""Test update image metadata """Test update image metadata
* logs in as admin user * logs in as admin user
* creates image from locally downloaded file * creates image from locally downloaded file
* verifies the image appears in the images table as active * verifies the image appears in the images table as active
@ -176,6 +180,7 @@ class TestImagesBasic(TestImagesLegacy):
def test_remove_protected_image(self): def test_remove_protected_image(self):
"""tests that protected image is not deletable """tests that protected image is not deletable
* logs in as admin user * logs in as admin user
* creates image from locally downloaded file * creates image from locally downloaded file
* verifies the image appears in the images table as active * verifies the image appears in the images table as active
@ -215,6 +220,7 @@ class TestImagesBasic(TestImagesLegacy):
def test_edit_image_description_and_name(self): def test_edit_image_description_and_name(self):
"""tests that image description is editable """tests that image description is editable
* creates image from locally downloaded file * creates image from locally downloaded file
* verifies the image appears in the images table as active * verifies the image appears in the images table as active
* toggle edit action and adds some description * toggle edit action and adds some description
@ -263,12 +269,13 @@ class TestImagesAdvanced(TestImagesLegacy):
"""Login as demo user""" """Login as demo user"""
def test_create_volume_from_image(self): def test_create_volume_from_image(self):
"""This test case checks create volume from image functionality: """This test case checks create volume from image functionality:
Steps:
1. Login to Horizon Dashboard as regular user Steps:
2. Navigate to Project -> Compute -> Images 1. Login to Horizon Dashboard as regular user
3. Create new volume from image 2. Navigate to Project -> Compute -> Images
4. Check that volume is created with expected name 3. Create new volume from image
5. Check that volume status is Available 4. Check that volume is created with expected name
5. Check that volume status is Available
""" """
images_page = self.images_page images_page = self.images_page
source_image = self.CONFIG.image.images_list[0] source_image = self.CONFIG.image.images_list[0]
@ -290,13 +297,14 @@ class TestImagesAdvanced(TestImagesLegacy):
def test_launch_instance_from_image(self): def test_launch_instance_from_image(self):
"""This test case checks launch instance from image functionality: """This test case checks launch instance from image functionality:
Steps:
1. Login to Horizon Dashboard as regular user Steps:
2. Navigate to Project -> Compute -> Images 1. Login to Horizon Dashboard as regular user
3. Launch new instance from image 2. Navigate to Project -> Compute -> Images
4. Check that instance is create 3. Launch new instance from image
5. Check that status of newly created instance is Active 4. Check that instance is create
6. Check that image_name in correct in instances table 5. Check that status of newly created instance is Active
6. Check that image_name in correct in instances table
""" """
images_page = self.images_page images_page = self.images_page
source_image = self.CONFIG.image.images_list[0] source_image = self.CONFIG.image.images_list[0]
@ -331,15 +339,16 @@ class TestImagesAdmin(helpers.AdminTestCase, TestImagesLegacy):
def test_filter_images(self): def test_filter_images(self):
"""This test checks filtering of images """This test checks filtering of images
Steps:
1) Login to Horizon dashboard as admin user Steps:
2) Go to Admin -> System -> Images 1) Login to Horizon dashboard as admin user
3) Use filter by Image Name 2) Go to Admin -> System -> Images
4) Check that filtered table has one image only (which name is 3) Use filter by Image Name
equal to filter value) 4) Check that filtered table has one image only (which name is
5) Check that no other images in the table equal to filter value)
6) Clear filter and set nonexistent image name. Check that 0 rows 5) Check that no other images in the table
are displayed 6) Clear filter and set nonexistent image name. Check that 0 rows
are displayed
""" """
images_list = self.CONFIG.image.images_list images_list = self.CONFIG.image.images_list
images_page = self.images_page images_page = self.images_page

View File

@ -24,6 +24,7 @@ class TestInstances(helpers.TestCase):
def test_create_delete_instance(self): def test_create_delete_instance(self):
"""tests the instance creation and deletion functionality: """tests the instance creation and deletion functionality:
* creates a new instance in Project > Compute > Instances page * creates a new instance in Project > Compute > Instances page
* verifies the instance appears in the instances table as active * verifies the instance appears in the instances table as active
* deletes the newly created instance via proper page (depends on user) * deletes the newly created instance via proper page (depends on user)
@ -49,6 +50,7 @@ class TestInstances(helpers.TestCase):
@decorators.skip_because(bugs=['1584057']) @decorators.skip_because(bugs=['1584057'])
def test_instances_pagination(self): def test_instances_pagination(self):
"""This test checks instance pagination """This test checks instance pagination
Steps: Steps:
1) Login to Horizon Dashboard as regular user 1) Login to Horizon Dashboard as regular user
2) Navigate to user settings page 2) Navigate to user settings page
@ -57,9 +59,9 @@ class TestInstances(helpers.TestCase):
5) Create 2 instances 5) Create 2 instances
6) Go to appropriate page (depends on user) 6) Go to appropriate page (depends on user)
7) Check that only 'Next' link is available, only one instance is 7) Check that only 'Next' link is available, only one instance is
available (and it has correct name) on the first page available (and it has correct name) on the first page
8) Click 'Next' and check that on the second page only one instance is 8) Click 'Next' and check that on the second page only one instance is
available (and it has correct name), there is no 'Next' link on page available (and it has correct name), there is no 'Next' link on page
9) Go to user settings page and restore 'Items Per Page' 9) Go to user settings page and restore 'Items Per Page'
10) Delete created instances via proper page (depends on user) 10) Delete created instances via proper page (depends on user)
""" """
@ -110,6 +112,7 @@ class TestInstances(helpers.TestCase):
def test_instances_pagination_and_filtration(self): def test_instances_pagination_and_filtration(self):
"""This test checks instance pagination and filtration """This test checks instance pagination and filtration
Steps: Steps:
1) Login to Horizon Dashboard as regular user 1) Login to Horizon Dashboard as regular user
2) Go to to user settings page 2) Go to to user settings page
@ -118,11 +121,11 @@ class TestInstances(helpers.TestCase):
5) Create 2 instances 5) Create 2 instances
6) Go to appropriate page (depends on user) 6) Go to appropriate page (depends on user)
7) Check filter by Name of the first and the second instance in order 7) Check filter by Name of the first and the second instance in order
to have one instance in the list (and it should have correct name) and to have one instance in the list (and it should have correct name)
no 'Next' link is available and no 'Next' link is available
8) Check filter by common part of Name of in order to have one instance 8) Check filter by common part of Name of in order to have one instance
in the list (and it should have correct name) and 'Next' link is in the list (and it should have correct name) and 'Next' link is
available on the first page and is not available on the second page available on the first page and is not available on the second page
9) Go to user settings page and restore 'Items Per Page' 9) Go to user settings page and restore 'Items Per Page'
10) Delete created instances via proper page (depends on user) 10) Delete created instances via proper page (depends on user)
@ -181,6 +184,7 @@ class TestInstances(helpers.TestCase):
def test_filter_instances(self): def test_filter_instances(self):
"""This test checks filtering of instances by Instance Name """This test checks filtering of instances by Instance Name
Steps: Steps:
1) Login to Horizon dashboard as regular user 1) Login to Horizon dashboard as regular user
2) Go to Project > Compute > Instances 2) Go to Project > Compute > Instances
@ -188,9 +192,9 @@ class TestInstances(helpers.TestCase):
4) Go to appropriate page (depends on user) 4) Go to appropriate page (depends on user)
5) Use filter by Instance Name 5) Use filter by Instance Name
6) Check that filtered table has one instance only (which name is equal 6) Check that filtered table has one instance only (which name is equal
to filter value) and no other instances in the table to filter value) and no other instances in the table
7) Check that filtered table has both instances (search by common part 7) Check that filtered table has both instances (search by common part
of instance names) of instance names)
8) Set nonexistent instance name. Check that 0 rows are displayed 8) Set nonexistent instance name. Check that 0 rows are displayed
9) Clear filter and delete instances via proper page (depends on user) 9) Clear filter and delete instances via proper page (depends on user)
""" """

View File

@ -16,6 +16,7 @@ from openstack_dashboard.test.integration_tests.pages import loginpage
class TestLogin(helpers.BaseTestCase): class TestLogin(helpers.BaseTestCase):
"""This is a basic scenario test: """This is a basic scenario test:
* checks that the login page is available * checks that the login page is available
* logs in as a regular user * logs in as a regular user
* checks that the user home page loads without error * checks that the user home page loads without error

View File

@ -31,6 +31,7 @@ class TestMetadataDefinitions(helpers.AdminTestCase):
is_public=True, is_protected=False, template_path=None, is_public=True, is_protected=False, template_path=None,
checks=(PUBLIC, PROTECTED)): checks=(PUBLIC, PROTECTED)):
"""Create NameSpace and run checks """Create NameSpace and run checks
:param namespace_name: Display name of namespace in template :param namespace_name: Display name of namespace in template
:param page: Connection point :param page: Connection point
:param template_json_container: JSON container with NameSpace content :param template_json_container: JSON container with NameSpace content
@ -71,6 +72,7 @@ class TestMetadataDefinitions(helpers.AdminTestCase):
def namespace_delete_with_checks(self, namespace_name, page): def namespace_delete_with_checks(self, namespace_name, page):
"""Delete NameSpace and run checks """Delete NameSpace and run checks
:param namespace_name: Display name of namespace in template :param namespace_name: Display name of namespace in template
:param page: Connection point :param page: Connection point
:return: Nothing :return: Nothing
@ -83,6 +85,7 @@ class TestMetadataDefinitions(helpers.AdminTestCase):
def test_namespace_create_delete(self): def test_namespace_create_delete(self):
"""Tests the NameSpace creation and deletion functionality: """Tests the NameSpace creation and deletion functionality:
* Actions: * Actions:
* 1) Login to Horizon Dashboard as admin user. * 1) Login to Horizon Dashboard as admin user.
* 2) Navigate to Admin -> System -> Metadata Definitions. * 2) Navigate to Admin -> System -> Metadata Definitions.

View File

@ -23,6 +23,7 @@ class TestNetworks(helpers.TestCase):
def test_private_network_create(self): def test_private_network_create(self):
"""tests the network creation and deletion functionalities: """tests the network creation and deletion functionalities:
* creates a new private network and a new subnet associated with it * creates a new private network and a new subnet associated with it
* verifies the network appears in the networks table as active * verifies the network appears in the networks table as active
* deletes the newly created network * deletes the newly created network

View File

@ -44,6 +44,7 @@ class TestRouters(helpers.TestCase):
def test_router_create(self): def test_router_create(self):
"""tests the router creation and deletion functionalities: """tests the router creation and deletion functionalities:
* creates a new router for public network * creates a new router for public network
* verifies the router appears in the routers table as active * verifies the router appears in the routers table as active
* deletes the newly created router * deletes the newly created router
@ -73,6 +74,7 @@ class TestRouters(helpers.TestCase):
def test_router_add_delete_interface(self): def test_router_add_delete_interface(self):
"""Tests the router interface creation and deletion functionalities: """Tests the router interface creation and deletion functionalities:
* Follows the steps to create a new router * Follows the steps to create a new router
* Clicks on the new router name from the routers table * Clicks on the new router name from the routers table
* Moves to the Interfaces page/tab * Moves to the Interfaces page/tab
@ -102,8 +104,8 @@ class TestRouters(helpers.TestCase):
self._delete_router() self._delete_router()
def test_router_delete_interface_by_row(self): def test_router_delete_interface_by_row(self):
"""Tests the router interface creation and deletion by """Tests the router interface creation and deletion by row action:
row action functionalities:
* Follows the steps to create a new router * Follows the steps to create a new router
* Clicks on the new router name from the routers table * Clicks on the new router name from the routers table
* Moves to the Interfaces page/tab * Moves to the Interfaces page/tab
@ -160,6 +162,7 @@ class TestAdminRouters(helpers.AdminTestCase):
@decorators.services_required("neutron") @decorators.services_required("neutron")
def test_router_create_admin(self): def test_router_create_admin(self):
"""tests the router creation and deletion functionalities: """tests the router creation and deletion functionalities:
* creates a new router for public network * creates a new router for public network
* verifies the router appears in the routers table as active * verifies the router appears in the routers table as active
* edits router name * edits router name

View File

@ -20,25 +20,26 @@ class TestRouters(helpers.TestCase):
@decorators.services_required("neutron") @decorators.services_required("neutron")
def test_router_create(self): def test_router_create(self):
"""This test case checks create, clear/set gateway, """Checks create, clear/set gateway, delete router functionality
delete router functionality
executed by non-admin user:: Executed by non-admin user.
Steps:
1. Login to Horizon Dashboard as horizon user Steps:
2. Navigate to Project -> Network -> Routers page 1. Login to Horizon Dashboard as horizon user
3. Create new router 2. Navigate to Project -> Network -> Routers page
4. Check that the router appears in the routers table as active 3. Create new router
5. Check that no Error messages present 4. Check that the router appears in the routers table as active
6. Clear the gateway 5. Check that no Error messages present
7. Check that the router is still in the routers table 6. Clear the gateway
with no external network 7. Check that the router is still in the routers table
8. Check that no Error messages present with no external network
9. Set the gateway to 'public' network 8. Check that no Error messages present
10. Check that no Error messages present 9. Set the gateway to 'public' network
11. Check that router's external network is set to 'public' 10. Check that no Error messages present
12. Delete the router 11. Check that router's external network is set to 'public'
13. Check that the router is absent in the routers table 12. Delete the router
14. Check that no Error messages present 13. Check that the router is absent in the routers table
14. Check that no Error messages present
""" """
routers_page = self.home_pg.go_to_network_routerspage() routers_page = self.home_pg.go_to_network_routerspage()

View File

@ -64,17 +64,19 @@ class TestSecuritygroup(helpers.TestCase):
def test_securitygroup_create_delete(self): def test_securitygroup_create_delete(self):
"""tests the security group creation and deletion functionalities: """tests the security group creation and deletion functionalities:
* creates a new security group * creates a new security group
* verifies the security group appears in the security groups table * verifies the security group appears in the security groups table
* deletes the newly created security group * deletes the newly created security group
* verifies the security group does not appear in the table after * verifies the security group does not appear in the table after
deletion deletion
""" """
self._create_securitygroup() self._create_securitygroup()
self._delete_securitygroup() self._delete_securitygroup()
def test_managerules_create_delete_by_row(self): def test_managerules_create_delete_by_row(self):
"""tests the manage rules creation and deletion functionalities: """tests the manage rules creation and deletion functionalities:
* create a new security group * create a new security group
* verifies the security group appears in the security groups table * verifies the security group appears in the security groups table
* creates a new rule * creates a new rule
@ -83,7 +85,7 @@ class TestSecuritygroup(helpers.TestCase):
* verifies the rule does not appear in the table after deletion * verifies the rule does not appear in the table after deletion
* deletes the newly created security group * deletes the newly created security group
* verifies the security group does not appear in the table after * verifies the security group does not appear in the table after
deletion deletion
""" """
self._create_securitygroup() self._create_securitygroup()
self._add_rule() self._add_rule()
@ -92,6 +94,7 @@ class TestSecuritygroup(helpers.TestCase):
def test_managerules_create_delete_by_table(self): def test_managerules_create_delete_by_table(self):
"""tests the manage rules creation and deletion functionalities: """tests the manage rules creation and deletion functionalities:
* create a new security group * create a new security group
* verifies the security group appears in the security groups table * verifies the security group appears in the security groups table
* creates a new rule * creates a new rule
@ -100,7 +103,7 @@ class TestSecuritygroup(helpers.TestCase):
* verifies the rule does not appear in the table after deletion * verifies the rule does not appear in the table after deletion
* deletes the newly created security group * deletes the newly created security group
* verifies the security group does not appear in the table after * verifies the security group does not appear in the table after
deletion deletion
""" """
self._create_securitygroup() self._create_securitygroup()
self._add_rule() self._add_rule()

View File

@ -43,9 +43,10 @@ class TestStacks(helpers.AdminTestCase):
@decorators.services_required("heat") @decorators.services_required("heat")
def test_create_delete_stack(self): def test_create_delete_stack(self):
"""tests the stack creation and deletion functionality """tests the stack creation and deletion functionality
* creates a new stack * creates a new stack
* verifies the stack appears in the stacks table in Create Complete * verifies the stack appears in the stacks table in Create Complete
state state
* deletes the newly created stack * deletes the newly created stack
* verifies the stack does not appear in the table after deletion * verifies the stack does not appear in the table after deletion
""" """

View File

@ -56,9 +56,8 @@ class TestPasswordChange(helpers.TestCase):
"Failed to login with default password") "Failed to login with default password")
def test_password_change(self): def test_password_change(self):
"""Changes the password, verifies it was indeed changed and resets to # Changes the password, verifies it was indeed changed and
default password. # resets to default password.
"""
passwordchange_page = self.home_pg.go_to_settings_changepasswordpage() passwordchange_page = self.home_pg.go_to_settings_changepasswordpage()
try: try:
@ -74,9 +73,8 @@ class TestPasswordChange(helpers.TestCase):
self._login() self._login()
def test_show_message_after_logout(self): def test_show_message_after_logout(self):
"""Ensure an informational message is shown on the login page after the # Ensure an informational message is shown on the login page
user is logged out. # after the user is logged out.
"""
passwordchange_page = self.home_pg.go_to_settings_changepasswordpage() passwordchange_page = self.home_pg.go_to_settings_changepasswordpage()
try: try:
@ -111,6 +109,7 @@ class TestUserSettings(helpers.TestCase):
def test_user_settings_change(self): def test_user_settings_change(self):
"""tests the user's settings options: """tests the user's settings options:
* changes the system's language * changes the system's language
* changes the timezone * changes the timezone
* changes the number of items per page (page size) * changes the number of items per page (page size)

View File

@ -45,16 +45,17 @@ class TestVolumeSnapshotsBasic(helpers.TestCase):
def test_create_edit_delete_volume_snapshot(self): def test_create_edit_delete_volume_snapshot(self):
"""Test checks create/delete volume snapshot action """Test checks create/delete volume snapshot action
Steps:
1. Login to Horizon Dashboard Steps:
2. Navigate to Project -> Compute -> Volumes page 1. Login to Horizon Dashboard
3. Create snapshot for existed volume 2. Navigate to Project -> Compute -> Volumes page
4. Check that no ERROR appears 3. Create snapshot for existed volume
5. Check that snapshot is in the list 4. Check that no ERROR appears
6. Check that snapshot has reference to correct volume 5. Check that snapshot is in the list
7. Edit snapshot name and description 6. Check that snapshot has reference to correct volume
8. Delete volume snapshot from proper page 7. Edit snapshot name and description
9. Check that volume snapshot not in the list 8. Delete volume snapshot from proper page
9. Check that volume snapshot not in the list
""" """
volumes_page = self.home_pg.go_to_compute_volumes_volumespage() volumes_page = self.home_pg.go_to_compute_volumes_volumespage()
volumes_snapshot_page = volumes_page.create_volume_snapshot( volumes_snapshot_page = volumes_page.create_volume_snapshot(
@ -90,25 +91,26 @@ class TestVolumeSnapshotsBasic(helpers.TestCase):
def test_volume_snapshots_pagination(self): def test_volume_snapshots_pagination(self):
"""This test checks volumes snapshots pagination """This test checks volumes snapshots pagination
Steps:
1) Login to Horizon Dashboard Steps:
2) Go to Project -> Compute -> Volumes -> Volumes tab, create 1) Login to Horizon Dashboard
volumes and 3 snapshots 2) Go to Project -> Compute -> Volumes -> Volumes tab, create
3) Navigate to user settings page volumes and 3 snapshots
4) Change 'Items Per Page' value to 1 3) Navigate to user settings page
5) Go to Project -> Compute -> Volumes -> Volumes Snapshot tab 4) Change 'Items Per Page' value to 1
or Admin -> System -> Volumes -> Volumes Snapshot tab 5) Go to Project -> Compute -> Volumes -> Volumes Snapshot tab
(depends on user) or Admin -> System -> Volumes -> Volumes Snapshot tab
6) Check that only 'Next' link is available, only one snapshot is (depends on user)
available (and it has correct name) 6) Check that only 'Next' link is available, only one snapshot is
7) Click 'Next' and check that both 'Prev' and 'Next' links are available (and it has correct name)
available, only one snapshot is available (and it has correct name) 7) Click 'Next' and check that both 'Prev' and 'Next' links are
8) Click 'Next' and check that only 'Prev' link is available, available, only one snapshot is available (and it has correct name)
only one snapshot is visible (and it has correct name) 8) Click 'Next' and check that only 'Prev' link is available,
9) Click 'Prev' and check result (should be the same as for step7) only one snapshot is visible (and it has correct name)
10) Click 'Prev' and check result (should be the same as for step6) 9) Click 'Prev' and check result (should be the same as for step7)
11) Go to user settings page and restore 'Items Per Page' 10) Click 'Prev' and check result (should be the same as for step6)
12) Delete created snapshots and volumes 11) Go to user settings page and restore 'Items Per Page'
12) Delete created snapshots and volumes
""" """
volumes_page = self.home_pg.go_to_compute_volumes_volumespage() volumes_page = self.home_pg.go_to_compute_volumes_volumespage()
count = 3 count = 3
@ -223,14 +225,15 @@ class TestVolumeSnapshotsAdvanced(helpers.TestCase):
def test_create_volume_from_snapshot(self): def test_create_volume_from_snapshot(self):
"""Test checks possibility to create volume from snapshot """Test checks possibility to create volume from snapshot
Steps:
1. Login to Horizon Dashboard as regular user Steps:
2. Navigate to Project -> Compute -> Volumes page 1. Login to Horizon Dashboard as regular user
3. Create snapshot for existed volume 2. Navigate to Project -> Compute -> Volumes page
4. Create new volume from snapshot 3. Create snapshot for existed volume
5. Check the volume is created and has 'Available' status 4. Create new volume from snapshot
6. Delete volume snapshot 5. Check the volume is created and has 'Available' status
7. Delete volume 6. Delete volume snapshot
7. Delete volume
""" """
volumes_page = self.home_pg.go_to_compute_volumes_volumespage() volumes_page = self.home_pg.go_to_compute_volumes_volumespage()
volumes_snapshot_page = volumes_page.create_volume_snapshot( volumes_snapshot_page = volumes_page.create_volume_snapshot(

View File

@ -27,18 +27,19 @@ class TestVolumesBasic(helpers.TestCase):
def test_volume_create_edit_delete(self): def test_volume_create_edit_delete(self):
"""This test case checks create, edit, delete volume functionality: """This test case checks create, edit, delete volume functionality:
Steps:
1. Login to Horizon Dashboard Steps:
2. Navigate to Project -> Compute -> Volumes page 1. Login to Horizon Dashboard
3. Create new volume 2. Navigate to Project -> Compute -> Volumes page
4. Check that the volume is in the list 3. Create new volume
5. Check that no Error messages present 4. Check that the volume is in the list
6. Edit the volume 5. Check that no Error messages present
7. Check that the volume is still in the list 6. Edit the volume
8. Check that no Error messages present 7. Check that the volume is still in the list
9. Delete the volume via proper page (depends on user) 8. Check that no Error messages present
10. Check that the volume is absent in the list 9. Delete the volume via proper page (depends on user)
11. Check that no Error messages present 10. Check that the volume is absent in the list
11. Check that no Error messages present
""" """
volumes_page = self.home_pg.go_to_compute_volumes_volumespage() volumes_page = self.home_pg.go_to_compute_volumes_volumespage()
volumes_page.create_volume(self.VOLUME_NAME) volumes_page.create_volume(self.VOLUME_NAME)
@ -78,24 +79,25 @@ class TestVolumesBasic(helpers.TestCase):
def test_volumes_pagination(self): def test_volumes_pagination(self):
"""This test checks volumes pagination """This test checks volumes pagination
Steps:
1) Login to Horizon Dashboard Steps:
2) Go to Project -> Compute -> Volumes -> Volumes tab and create 1) Login to Horizon Dashboard
three volumes 2) Go to Project -> Compute -> Volumes -> Volumes tab and create
3) Navigate to user settings page three volumes
4) Change 'Items Per Page' value to 1 3) Navigate to user settings page
5) Go to Project -> Compute -> Volumes -> Volumes tab or 4) Change 'Items Per Page' value to 1
Admin -> System -> Volumes -> Volumes tab (depends on user) 5) Go to Project -> Compute -> Volumes -> Volumes tab or
6) Check that only 'Next' link is available, only one volume is Admin -> System -> Volumes -> Volumes tab (depends on user)
available (and it has correct name) 6) Check that only 'Next' link is available, only one volume is
7) Click 'Next' and check that both 'Prev' and 'Next' links are available (and it has correct name)
available, only one volume is available (and it has correct name) 7) Click 'Next' and check that both 'Prev' and 'Next' links are
8) Click 'Next' and check that only 'Prev' link is available, available, only one volume is available (and it has correct name)
only one volume is visible (and it has correct name) 8) Click 'Next' and check that only 'Prev' link is available,
9) Click 'Prev' and check result (should be the same as for step7) only one volume is visible (and it has correct name)
10) Click 'Prev' and check result (should be the same as for step6) 9) Click 'Prev' and check result (should be the same as for step7)
11) Go to user settings page and restore 'Items Per Page' 10) Click 'Prev' and check result (should be the same as for step6)
12) Delete created volumes 11) Go to user settings page and restore 'Items Per Page'
12) Delete created volumes
""" """
volumes_page = self.home_pg.go_to_compute_volumes_volumespage() volumes_page = self.home_pg.go_to_compute_volumes_volumespage()
count = 3 count = 3
@ -167,15 +169,16 @@ class TestVolumesAdvanced(helpers.TestCase):
@decorators.skip_because(bugs=['1584057']) @decorators.skip_because(bugs=['1584057'])
def test_manage_volume_attachments(self): def test_manage_volume_attachments(self):
"""This test case checks attach/detach actions for volume """This test case checks attach/detach actions for volume
Steps:
1. Login to Horizon Dashboard as horizon user Steps:
2. Navigate to Project -> Compute -> Instances, create instance 1. Login to Horizon Dashboard as horizon user
3. Navigate to Project -> Compute -> Volumes, create volume 2. Navigate to Project -> Compute -> Instances, create instance
4. Attach volume to instance from step2 3. Navigate to Project -> Compute -> Volumes, create volume
5. Check that volume status and link to instance 4. Attach volume to instance from step2
6. Detach volume from instance 5. Check that volume status and link to instance
7. Check volume status 6. Detach volume from instance
8. Delete volume and instance 7. Check volume status
8. Delete volume and instance
""" """
instance_name = helpers.gen_random_resource_name('instance') instance_name = helpers.gen_random_resource_name('instance')
instances_page = self.home_pg.go_to_compute_instancespage() instances_page = self.home_pg.go_to_compute_instancespage()
@ -251,12 +254,13 @@ class TestVolumesActions(helpers.TestCase):
def test_volume_extend(self): def test_volume_extend(self):
"""This test case checks extend volume functionality: """This test case checks extend volume functionality:
Steps:
1. Check current volume size Steps:
2. Extend volume 1. Check current volume size
3. Check that no Error messages present 2. Extend volume
4. Check that the volume is still in the list 3. Check that no Error messages present
5. Check that the volume size is changed 4. Check that the volume is still in the list
5. Check that the volume size is changed
""" """
orig_size = self.volumes_page.get_size(self.VOLUME_NAME) orig_size = self.volumes_page.get_size(self.VOLUME_NAME)
self.volumes_page.extend_volume(self.VOLUME_NAME, orig_size + 1) self.volumes_page.extend_volume(self.VOLUME_NAME, orig_size + 1)
@ -272,12 +276,13 @@ class TestVolumesActions(helpers.TestCase):
@decorators.skip_because(bugs=['1584057']) @decorators.skip_because(bugs=['1584057'])
def test_volume_upload_to_image(self): def test_volume_upload_to_image(self):
"""This test case checks upload volume to image functionality: """This test case checks upload volume to image functionality:
Steps:
1. Upload volume to image with some disk format Steps:
2. Check that image is created 1. Upload volume to image with some disk format
3. Check that no Error messages present 2. Check that image is created
4. Delete the image 3. Check that no Error messages present
5. Repeat actions for all disk formats 4. Delete the image
5. Repeat actions for all disk formats
""" """
self.volumes_page = self.home_pg.go_to_compute_volumes_volumespage() self.volumes_page = self.home_pg.go_to_compute_volumes_volumespage()
all_formats = {"qcow2": u'QCOW2', "raw": u'Raw', "vdi": u'VDI', all_formats = {"qcow2": u'QCOW2', "raw": u'Raw', "vdi": u'VDI',
@ -306,13 +311,14 @@ class TestVolumesActions(helpers.TestCase):
def test_volume_launch_as_instance(self): def test_volume_launch_as_instance(self):
"""This test case checks launch volume as instance functionality: """This test case checks launch volume as instance functionality:
Steps:
1. Launch volume as instance Steps:
2. Check that instance is created 1. Launch volume as instance
3. Check that no Error messages present 2. Check that instance is created
4. Check that instance status is 'active' 3. Check that no Error messages present
5. Check that volume status is 'in use' 4. Check that instance status is 'active'
6. Delete instance 5. Check that volume status is 'in use'
6. Delete instance
""" """
self.volumes_page.launch_instance(self.VOLUME_NAME, self.INSTANCE_NAME) self.volumes_page.launch_instance(self.VOLUME_NAME, self.INSTANCE_NAME)
self.assertTrue( self.assertTrue(

View File

@ -19,15 +19,16 @@ class TestAdminVolumeTypes(helpers.AdminTestCase):
def test_volume_type_create_delete(self): def test_volume_type_create_delete(self):
"""This test case checks create, delete volume type: """This test case checks create, delete volume type:
Steps:
1. Login to Horizon Dashboard as admin user Steps:
2. Navigate to Admin -> System -> Volumes -> Volume Types page 1. Login to Horizon Dashboard as admin user
3. Create new volume type 2. Navigate to Admin -> System -> Volumes -> Volume Types page
4. Check that the volume type is in the list 3. Create new volume type
5. Check that no Error messages present 4. Check that the volume type is in the list
6. Delete the volume type 5. Check that no Error messages present
7. Check that the volume type is absent in the list 6. Delete the volume type
8. Check that no Error messages present 7. Check that the volume type is absent in the list
8. Check that no Error messages present
""" """
volume_types_page = self.home_pg.go_to_system_volumes_volumetypespage() volume_types_page = self.home_pg.go_to_system_volumes_volumetypespage()
@ -55,6 +56,7 @@ class TestQoSSpec(helpers.AdminTestCase):
def test_qos_spec_create_delete(self): def test_qos_spec_create_delete(self):
"""tests the QoS Spec creation and deletion functionality """tests the QoS Spec creation and deletion functionality
* creates a new QoS Spec * creates a new QoS Spec
* verifies the QoS Spec appears in the QoS Specs table * verifies the QoS Spec appears in the QoS Specs table
* deletes the newly created QoS Spec * deletes the newly created QoS Spec
@ -78,10 +80,11 @@ class TestQoSSpec(helpers.AdminTestCase):
def test_qos_spec_edit_consumer(self): def test_qos_spec_edit_consumer(self):
"""tests Edit Consumer of QoS Spec functionality """tests Edit Consumer of QoS Spec functionality
* creates a new QoS Spec * creates a new QoS Spec
* verifies the QoS Spec appears in the QoS Specs table * verifies the QoS Spec appears in the QoS Specs table
* edit consumer of created QoS Spec (check all options - front-end, * edit consumer of created QoS Spec (check all options - front-end,
both, back-end) both, back-end)
* verifies current consumer of the QoS Spec in the QoS Specs table * verifies current consumer of the QoS Spec in the QoS Specs table
* deletes the newly created QoS Spec * deletes the newly created QoS Spec
* verifies the QoS Spec does not appear in the table after deletion * verifies the QoS Spec does not appear in the table after deletion

View File

@ -43,9 +43,11 @@ def load_test_data(load_onto=None):
class TestData(object): class TestData(object):
"""Holder object for test data. Any functions passed to the init method """Holder object for test data.
will be called with the ``TestData`` object as their only argument. They
can then load data onto the object as desired. Any functions passed to the init method will be called with the
``TestData`` object as their only argument.
They can then load data onto the object as desired.
The idea is to use the instantiated object like this:: The idea is to use the instantiated object like this::
@ -93,9 +95,7 @@ class TestDataContainer(object):
return self._objects return self._objects
def filter(self, filtered=None, **kwargs): def filter(self, filtered=None, **kwargs):
"""Returns objects in this container whose attributes match the given """Returns objects whose attributes match the given kwargs."""
keyword arguments.
"""
if filtered is None: if filtered is None:
filtered = self._objects filtered = self._objects
try: try:
@ -111,8 +111,9 @@ class TestDataContainer(object):
return self.filter(filtered=filtered, **kwargs) return self.filter(filtered=filtered, **kwargs)
def get(self, **kwargs): def get(self, **kwargs):
"""Returns the single object in this container whose attributes match """Returns a single object whose attributes match the given kwargs.
the given keyword arguments. An error will be raised if the arguments
An error will be raised if the arguments
provided don't return exactly one match. provided don't return exactly one match.
""" """
matches = self.filter(**kwargs) matches = self.filter(**kwargs)

View File

@ -396,6 +396,7 @@ def _get_tenant_volume_usages(request, usages, disabled_quotas, tenant_id):
@memoized @memoized
def tenant_quota_usages(request, tenant_id=None): def tenant_quota_usages(request, tenant_id=None):
"""Get our quotas and construct our usage object. """Get our quotas and construct our usage object.
If no tenant_id is provided, a the request.user.project_id If no tenant_id is provided, a the request.user.project_id
is assumed to be used is assumed to be used
""" """

View File

@ -30,7 +30,9 @@ def get_int_or_uuid(value):
def get_display_label(choices, status): def get_display_label(choices, status):
"""This method is used in places where a resource's status or """Get a display label for resource status.
This method is used in places where a resource's status or
admin state labels need to assigned before they are sent to the admin state labels need to assigned before they are sent to the
view template. view template.
""" """

View File

@ -117,8 +117,7 @@ commands =
[flake8] [flake8]
exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,panel_template,dash_template,local_settings.py,*/local/*,*/test/test_plugins/*,.ropeproject,node_modules exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,panel_template,dash_template,local_settings.py,*/local/*,*/test/test_plugins/*,.ropeproject,node_modules
# H405 multi line docstring summary not separated with an empty line ignore =
ignore = H405
# Enable the following hacking rules which are disabled by default # Enable the following hacking rules which are disabled by default
# H203 Use assertIs(Not)None to check for None # H203 Use assertIs(Not)None to check for None
# H904 Delay string interpolations at logging calls # H904 Delay string interpolations at logging calls