فهرست منبع

Merge pull request #139 from CloudVE/better-exceptions

Change Resource Group and Storage Account Handling
Nuwan Goonasekera 7 سال پیش
والد
کامیت
cd34ce5977

+ 3 - 0
.gitignore

@@ -65,3 +65,6 @@ launch.json
 settings.json
 settings.json
 run_nose.py
 run_nose.py
 *ipynb*
 *ipynb*
+
+# PyCharm
+.idea/

+ 1 - 1
.travis.yml

@@ -64,7 +64,7 @@ install:
     - pip install coveralls
     - pip install coveralls
     - pip install codecov
     - pip install codecov
 script:
 script:
-    - travis_wait 30 tox -r -e $TOX_ENV
+    - tox -r -e $TOX_ENV
 after_script:
 after_script:
     - |
     - |
       case "$TRAVIS_EVENT_TYPE" in
       case "$TRAVIS_EVENT_TYPE" in

+ 2 - 2
cloudbridge/cloud/base/resources.py

@@ -9,6 +9,8 @@ import re
 import shutil
 import shutil
 import time
 import time
 
 
+import six
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.interfaces.exceptions \
 from cloudbridge.cloud.interfaces.exceptions \
     import InvalidConfigurationException
     import InvalidConfigurationException
@@ -50,8 +52,6 @@ from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
 from cloudbridge.cloud.interfaces.resources import Volume
 from cloudbridge.cloud.interfaces.resources import VolumeState
 from cloudbridge.cloud.interfaces.resources import VolumeState
 
 
-import six
-
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
 
 

+ 2 - 2
cloudbridge/cloud/providers/aws/services.py

@@ -4,6 +4,8 @@ import string
 
 
 from botocore.exceptions import ClientError
 from botocore.exceptions import ClientError
 
 
+import requests
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseBucketService
 from cloudbridge.cloud.base.services import BaseBucketService
@@ -32,8 +34,6 @@ from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
 from cloudbridge.cloud.interfaces.resources import Volume
 
 
-import requests
-
 from .helpers import BotoEC2Service
 from .helpers import BotoEC2Service
 from .helpers import BotoS3Service
 from .helpers import BotoS3Service
 from .resources import AWSBucket
 from .resources import AWSBucket

+ 66 - 5
cloudbridge/cloud/providers/azure/azure_client.py

@@ -14,12 +14,13 @@ from azure.storage.blob import BlobPermissions
 from azure.storage.blob import BlockBlobService
 from azure.storage.blob import BlockBlobService
 from azure.storage.common import TokenCredential
 from azure.storage.common import TokenCredential
 
 
-from cloudbridge.cloud.interfaces.exceptions import WaitStateException
-
 from msrestazure.azure_exceptions import CloudError
 from msrestazure.azure_exceptions import CloudError
 
 
 import tenacity
 import tenacity
 
 
+from cloudbridge.cloud.interfaces.exceptions import \
+    InvalidNameException, ProviderConnectionException, WaitStateException
+
 from . import helpers as azure_helpers
 from . import helpers as azure_helpers
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
@@ -167,6 +168,7 @@ class AzureClient(object):
         self._access_key_result = None
         self._access_key_result = None
         self._block_blob_service = None
         self._block_blob_service = None
         self._table_service = None
         self._table_service = None
+        self._storage_account = None
 
 
         log.debug("azure subscription : %s", self.subscription_id)
         log.debug("azure subscription : %s", self.subscription_id)
 
 
@@ -247,6 +249,7 @@ class AzureClient(object):
 
 
     @property
     @property
     def blob_service(self):
     def blob_service(self):
+        self._get_or_create_storage_account()
         if not self._block_blob_service:
         if not self._block_blob_service:
             if self._access_token:
             if self._access_token:
                 token_credential = TokenCredential(self._access_token)
                 token_credential = TokenCredential(self._access_token)
@@ -261,6 +264,7 @@ class AzureClient(object):
 
 
     @property
     @property
     def table_service(self):
     def table_service(self):
+        self._get_or_create_storage_account()
         if not self._table_service:
         if not self._table_service:
             self._table_service = TableService(
             self._table_service = TableService(
                 self.storage_account,
                 self.storage_account,
@@ -286,6 +290,65 @@ class AzureClient(object):
         return self.storage_client.storage_accounts. \
         return self.storage_client.storage_accounts. \
             create(self.resource_group, name.lower(), params).result()
             create(self.resource_group, name.lower(), params).result()
 
 
+    # Create a storage account. To prevent a race condition, try
+    # to get or create at least twice
+    @tenacity.retry(stop=tenacity.stop_after_attempt(2),
+                    retry=tenacity.retry_if_exception_type(CloudError),
+                    reraise=True)
+    def _get_or_create_storage_account(self):
+        if self._storage_account:
+            return self._storage_account
+        else:
+            try:
+                self._storage_account = \
+                    self.get_storage_account(self.storage_account)
+            except CloudError as cloud_error:
+                if cloud_error.error.error == "ResourceNotFound":
+                    storage_account_params = {
+                        'sku': {
+                            'name': 'Standard_LRS'
+                        },
+                        'kind': 'storage',
+                        'location': self.region_name,
+                    }
+                    try:
+                        self._storage_account = \
+                            self.create_storage_account(self.storage_account,
+                                                        storage_account_params)
+                    except CloudError as cloud_error2:
+                        if cloud_error2.error.error == "AuthorizationFailed":
+                            mess = 'The following error was returned by ' \
+                                   'Azure:\n%s\n\nThis is likely because the' \
+                                   ' Role associated with the provided ' \
+                                   'credentials does not allow for Storage ' \
+                                   'Account creation.\nA Storage Account is ' \
+                                   'necessary in order to perform the ' \
+                                   'desired operation. You must either ' \
+                                   'provide an existing Storage Account name' \
+                                   ' as part of the configuration, or ' \
+                                   'elevate the associated Role.\nFor more ' \
+                                   'information on roles, see: https://docs.' \
+                                   'microsoft.com/en-us/azure/role-based-' \
+                                   'access-control/overview\n' % cloud_error2
+                            raise ProviderConnectionException(mess)
+
+                        elif cloud_error2.error.error == \
+                                "StorageAccountAlreadyTaken":
+                            mess = 'The following error was ' \
+                                   'returned by Azure:\n%s\n\n' \
+                                   'Note that Storage Account names must be ' \
+                                   'unique across Azure (not just in your ' \
+                                   'subscription).\nFor more information ' \
+                                   'see https://docs.microsoft.com/en-us/' \
+                                   'azure/azure-resource-manager/resource-' \
+                                   'manager-storage-account-name-errors\n' \
+                                   % cloud_error2
+                            raise InvalidNameException(mess)
+                        else:
+                            raise cloud_error2
+                else:
+                    raise cloud_error
+
     def list_locations(self):
     def list_locations(self):
         return self.subscription_client.subscriptions. \
         return self.subscription_client.subscriptions. \
             list_locations(self.subscription_id)
             list_locations(self.subscription_id)
@@ -584,9 +647,7 @@ class AzureClient(object):
     def __if_subnet_in_use(e):
     def __if_subnet_in_use(e):
         # return True if the CloudError exception is due to subnet being in use
         # return True if the CloudError exception is due to subnet being in use
         if isinstance(e, CloudError):
         if isinstance(e, CloudError):
-            error_message = e.message
-            if "Subnet" in error_message \
-                    and 'in use' in error_message:
+            if e.error.error == "InUseSubnetCannotBeDeleted":
                 return True
                 return True
         return False
         return False
 
 

+ 45 - 33
cloudbridge/cloud/providers/azure/provider.py

@@ -1,16 +1,18 @@
 import logging
 import logging
 import os
 import os
+import uuid
+
+from msrestazure.azure_exceptions import CloudError
+
+import tenacity
 
 
 from cloudbridge.cloud.base import BaseCloudProvider
 from cloudbridge.cloud.base import BaseCloudProvider
+from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
 from cloudbridge.cloud.providers.azure.azure_client import AzureClient
 from cloudbridge.cloud.providers.azure.azure_client import AzureClient
 from cloudbridge.cloud.providers.azure.services \
 from cloudbridge.cloud.providers.azure.services \
     import AzureComputeService, AzureNetworkingService, \
     import AzureComputeService, AzureNetworkingService, \
     AzureSecurityService, AzureStorageService
     AzureSecurityService, AzureStorageService
 
 
-from msrestazure.azure_exceptions import CloudError
-
-import tenacity
-
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
 
 
@@ -41,16 +43,17 @@ class AzureCloudProvider(BaseCloudProvider):
             'azure_resource_group', os.environ.get('AZURE_RESOURCE_GROUP',
             'azure_resource_group', os.environ.get('AZURE_RESOURCE_GROUP',
                                                    'cloudbridge'))
                                                    'cloudbridge'))
         # Storage account name is limited to a max length of 24 alphanum chars
         # Storage account name is limited to a max length of 24 alphanum chars
-        # and unique across an account. Yet, all our operations are tied to a
-        # resource group, making it impossible to use a storage account
-        # defined in a different resource group from the one used by the
-        # current session. With that, base the name of the storage account on
-        # the current resource group, up to 24 chars in length.
+        # and unique across all of Azure. Thus, a uuid is used to generate a
+        # unique name for the Storage Account based on the resource group,
+        # while also using the subscription ID to ensure that different users
+        # having the same resource group name do not have the same SA name.
         self.storage_account = self._get_config_value(
         self.storage_account = self._get_config_value(
             'azure_storage_account',
             'azure_storage_account',
             os.environ.get(
             os.environ.get(
-                'AZURE_STORAGE_ACCOUNT', 'storageacc' + ''.join(
-                    ch for ch in self.resource_group if ch.isalnum())[-12:]))
+                'AZURE_STORAGE_ACCOUNT',
+                'storacc' + self.subscription_id[-6:] +
+                str(uuid.uuid5(uuid.NAMESPACE_OID,
+                               str(self.resource_group)))[-6:]))
 
 
         self.vm_default_user_name = self._get_config_value(
         self.vm_default_user_name = self._get_config_value(
             'azure_vm_default_user_name', os.environ.get
             'azure_vm_default_user_name', os.environ.get
@@ -108,6 +111,9 @@ class AzureCloudProvider(BaseCloudProvider):
             self._initialize()
             self._initialize()
         return self._azure_client
         return self._azure_client
 
 
+    @tenacity.retry(stop=tenacity.stop_after_attempt(2),
+                    retry=tenacity.retry_if_exception_type(CloudError),
+                    reraise=True)
     def _initialize(self):
     def _initialize(self):
         """
         """
         Verifying that resource group and storage account exists
         Verifying that resource group and storage account exists
@@ -116,25 +122,31 @@ class AzureCloudProvider(BaseCloudProvider):
         """
         """
         try:
         try:
             self._azure_client.get_resource_group(self.resource_group)
             self._azure_client.get_resource_group(self.resource_group)
-        except CloudError:
-            resource_group_params = {'location': self.region_name}
-            self._azure_client.create_resource_group(self.resource_group,
-                                                     resource_group_params)
-        # Create a storage account. To prevent a race condition, try
-        # to get or create at least twice
-        self._get_or_create_storage_account()
-
-    @tenacity.retry(stop=tenacity.stop_after_attempt(2), reraise=True)
-    def _get_or_create_storage_account(self):
-        try:
-            return self._azure_client.get_storage_account(self.storage_account)
-        except CloudError:
-            storage_account_params = {
-                'sku': {
-                    'name': 'Standard_LRS'
-                },
-                'kind': 'storage',
-                'location': self.region_name,
-            }
-            self._azure_client.create_storage_account(self.storage_account,
-                                                      storage_account_params)
+
+        except CloudError as cloud_error:
+            if cloud_error.error.error == "ResourceGroupNotFound":
+                resource_group_params = {'location': self.region_name}
+                try:
+                    self._azure_client.\
+                        create_resource_group(self.resource_group,
+                                              resource_group_params)
+                except CloudError as cloud_error2:
+                    if cloud_error2.error.error == "AuthorizationFailed":
+                        mess = 'The following error was returned by Azure:\n' \
+                               '%s\n\nThis is likely because the Role' \
+                               'associated with the given credentials does ' \
+                               'not allow for Resource Group creation.\nA ' \
+                               'Resource Group is necessary to manage ' \
+                               'resources in Azure. You must either ' \
+                               'provide an existing Resource Group as part ' \
+                               'of the configuration, or elevate the ' \
+                               'associated role.\nFor more information on ' \
+                               'roles, see: https://docs.microsoft.com/' \
+                               'en-us/azure/role-based-access-control/' \
+                               'overview\n' % cloud_error2
+                        raise ProviderConnectionException(mess)
+                    else:
+                        raise cloud_error2
+
+            else:
+                raise cloud_error

+ 4 - 4
cloudbridge/cloud/providers/azure/resources.py

@@ -9,6 +9,10 @@ from azure.common import AzureException
 from azure.mgmt.devtestlabs.models import GalleryImageReference
 from azure.mgmt.devtestlabs.models import GalleryImageReference
 from azure.mgmt.network.models import NetworkSecurityGroup
 from azure.mgmt.network.models import NetworkSecurityGroup
 
 
+from msrestazure.azure_exceptions import CloudError
+
+import pysftp
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
     BaseBucket, BaseBucketContainer, BaseBucketObject, BaseFloatingIP, \
     BaseBucket, BaseBucketContainer, BaseBucketObject, BaseFloatingIP, \
@@ -22,10 +26,6 @@ from cloudbridge.cloud.interfaces.resources import Instance, \
     MachineImageState, NetworkState, RouterState, \
     MachineImageState, NetworkState, RouterState, \
     SnapshotState, SubnetState, TrafficDirection
     SnapshotState, SubnetState, TrafficDirection
 
 
-from msrestazure.azure_exceptions import CloudError
-
-import pysftp
-
 from . import helpers as azure_helpers
 from . import helpers as azure_helpers
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 3 - 4
cloudbridge/cloud/providers/azure/services.py

@@ -5,6 +5,8 @@ import uuid
 from azure.common import AzureException
 from azure.common import AzureException
 from azure.mgmt.compute.models import DiskCreateOption
 from azure.mgmt.compute.models import DiskCreateOption
 
 
+from msrestazure.azure_exceptions import CloudError
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList, \
 from cloudbridge.cloud.base.resources import ClientPagedResultList, \
     ServerPagedResultList
     ServerPagedResultList
@@ -20,8 +22,6 @@ from cloudbridge.cloud.interfaces.exceptions import \
 from cloudbridge.cloud.interfaces.resources import MachineImage, \
 from cloudbridge.cloud.interfaces.resources import MachineImage, \
     Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
     Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
 
 
-from msrestazure.azure_exceptions import CloudError
-
 from . import helpers as azure_helpers
 from . import helpers as azure_helpers
 from .resources import AzureBucket, \
 from .resources import AzureBucket, \
     AzureInstance, AzureKeyPair, \
     AzureInstance, AzureKeyPair, \
@@ -972,8 +972,7 @@ class AzureSubnetService(BaseSubnetService):
                         net.id
                         net.id
                     ))
                     ))
                 except CloudError as cloud_error:
                 except CloudError as cloud_error:
-                    message = cloud_error.message
-                    if "not found" in message and "virtualNetworks" in message:
+                    if cloud_error.error.error == "ResourceNotFound":
                         log.exception(cloud_error)
                         log.exception(cloud_error)
                     else:
                     else:
                         raise cloud_error
                         raise cloud_error

+ 2 - 2
cloudbridge/cloud/providers/openstack/provider.py

@@ -5,8 +5,6 @@ import os
 
 
 from cinderclient import client as cinder_client
 from cinderclient import client as cinder_client
 
 
-from cloudbridge.cloud.base import BaseCloudProvider
-
 from keystoneauth1 import session
 from keystoneauth1 import session
 
 
 from keystoneclient import client as keystone_client
 from keystoneclient import client as keystone_client
@@ -20,6 +18,8 @@ from openstack import connection
 
 
 from swiftclient import client as swift_client
 from swiftclient import client as swift_client
 
 
+from cloudbridge.cloud.base import BaseCloudProvider
+
 from .services import OpenStackComputeService
 from .services import OpenStackComputeService
 from .services import OpenStackNetworkingService
 from .services import OpenStackNetworkingService
 from .services import OpenStackSecurityService
 from .services import OpenStackSecurityService

+ 14 - 14
cloudbridge/cloud/providers/openstack/resources.py

@@ -12,6 +12,20 @@ except ImportError:  # python 2
     from urlparse import urlparse
     from urlparse import urlparse
     from urlparse import urljoin
     from urlparse import urljoin
 
 
+from keystoneclient.v3.regions import Region
+
+from neutronclient.common.exceptions import PortNotFoundClient
+
+import novaclient.exceptions as novaex
+
+from openstack.exceptions import HttpException
+from openstack.exceptions import NotFoundException
+from openstack.exceptions import ResourceNotFound
+
+import swiftclient
+from swiftclient.service import SwiftService, SwiftUploadObject
+from swiftclient.utils import generate_temp_url
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
 from cloudbridge.cloud.base.resources import BaseBucket
@@ -48,20 +62,6 @@ from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VolumeState
 from cloudbridge.cloud.interfaces.resources import VolumeState
 from cloudbridge.cloud.providers.openstack import helpers as oshelpers
 from cloudbridge.cloud.providers.openstack import helpers as oshelpers
 
 
-from keystoneclient.v3.regions import Region
-
-from neutronclient.common.exceptions import PortNotFoundClient
-
-import novaclient.exceptions as novaex
-
-from openstack.exceptions import HttpException
-from openstack.exceptions import NotFoundException
-from openstack.exceptions import ResourceNotFound
-
-import swiftclient
-from swiftclient.service import SwiftService, SwiftUploadObject
-from swiftclient.utils import generate_temp_url
-
 ONE_GIG = 1048576000  # in bytes
 ONE_GIG = 1048576000  # in bytes
 FIVE_GIG = ONE_GIG * 5  # in bytes
 FIVE_GIG = ONE_GIG * 5  # in bytes
 
 

+ 7 - 7
cloudbridge/cloud/providers/openstack/services.py

@@ -7,6 +7,13 @@ import re
 
 
 from cinderclient.exceptions import NotFound as CinderNotFound
 from cinderclient.exceptions import NotFound as CinderNotFound
 
 
+from neutronclient.common.exceptions import NeutronClientException
+
+from novaclient.exceptions import NotFound as NovaNotFound
+
+from openstack.exceptions import NotFoundException
+from openstack.exceptions import ResourceNotFound
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseLaunchConfig
 from cloudbridge.cloud.base.resources import BaseLaunchConfig
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ClientPagedResultList
@@ -38,13 +45,6 @@ from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import Volume
 from cloudbridge.cloud.interfaces.resources import Volume
 from cloudbridge.cloud.providers.openstack import helpers as oshelpers
 from cloudbridge.cloud.providers.openstack import helpers as oshelpers
 
 
-from neutronclient.common.exceptions import NeutronClientException
-
-from novaclient.exceptions import NotFound as NovaNotFound
-
-from openstack.exceptions import NotFoundException
-from openstack.exceptions import ResourceNotFound
-
 from .resources import OpenStackBucket
 from .resources import OpenStackBucket
 from .resources import OpenStackInstance
 from .resources import OpenStackInstance
 from .resources import OpenStackInternetGateway
 from .resources import OpenStackInternetGateway

+ 3 - 0
setup.cfg

@@ -19,3 +19,6 @@ logging-filter=cloudbridge
 
 
 [bdist_wheel]
 [bdist_wheel]
 universal = 1
 universal = 1
+
+[flake8]
+application_import_names = cloudbridge, test

+ 2 - 2
test/helpers/__init__.py

@@ -6,12 +6,12 @@ import unittest
 import uuid
 import uuid
 from contextlib import contextmanager
 from contextlib import contextmanager
 
 
+from six import reraise
+
 from cloudbridge.cloud.factory import CloudProviderFactory
 from cloudbridge.cloud.factory import CloudProviderFactory
 from cloudbridge.cloud.interfaces import InstanceState
 from cloudbridge.cloud.interfaces import InstanceState
 from cloudbridge.cloud.interfaces import TestMockHelperMixin
 from cloudbridge.cloud.interfaces import TestMockHelperMixin
 
 
-from six import reraise
-
 
 
 def parse_bool(val):
 def parse_bool(val):
     if val:
     if val:

+ 2 - 1
test/helpers/standard_interface_tests.py

@@ -5,7 +5,6 @@ This includes:
    2. Checking for object equality and repr
    2. Checking for object equality and repr
    3. Checking standard behaviour for list, iter, find, get, delete
    3. Checking standard behaviour for list, iter, find, get, delete
 """
 """
-import test.helpers as helpers
 import uuid
 import uuid
 
 
 from cloudbridge.cloud.interfaces.exceptions \
 from cloudbridge.cloud.interfaces.exceptions \
@@ -13,6 +12,8 @@ from cloudbridge.cloud.interfaces.exceptions \
 from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
 from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
 from cloudbridge.cloud.interfaces.resources import ResultList
 from cloudbridge.cloud.interfaces.resources import ResultList
 
 
+import test.helpers as helpers
+
 
 
 def check_repr(test, obj):
 def check_repr(test, obj):
     test.assertTrue(
     test.assertTrue(

+ 5 - 4
test/test_block_store_service.py

@@ -1,8 +1,7 @@
 import time
 import time
 import uuid
 import uuid
-from test import helpers
-from test.helpers import ProviderTestBase
-from test.helpers import standard_interface_tests as sit
+
+import six
 
 
 from cloudbridge.cloud.factory import ProviderList
 from cloudbridge.cloud.factory import ProviderList
 from cloudbridge.cloud.interfaces import SnapshotState
 from cloudbridge.cloud.interfaces import SnapshotState
@@ -12,7 +11,9 @@ from cloudbridge.cloud.interfaces.resources import AttachmentInfo
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import Volume
 from cloudbridge.cloud.interfaces.resources import Volume
 
 
-import six
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
 
 
 
 
 class CloudBlockStoreServiceTestCase(ProviderTestBase):
 class CloudBlockStoreServiceTestCase(ProviderTestBase):

+ 2 - 1
test/test_cloud_factory.py

@@ -1,5 +1,4 @@
 import unittest
 import unittest
-from test import helpers
 
 
 from cloudbridge.cloud import factory
 from cloudbridge.cloud import factory
 from cloudbridge.cloud import interfaces
 from cloudbridge.cloud import interfaces
@@ -9,6 +8,8 @@ from cloudbridge.cloud.interfaces.provider import CloudProvider
 from cloudbridge.cloud.providers.aws import AWSCloudProvider
 from cloudbridge.cloud.providers.aws import AWSCloudProvider
 from cloudbridge.cloud.providers.aws.provider import MockAWSCloudProvider
 from cloudbridge.cloud.providers.aws.provider import MockAWSCloudProvider
 
 
+from test import helpers
+
 
 
 class CloudFactoryTestCase(unittest.TestCase):
 class CloudFactoryTestCase(unittest.TestCase):
 
 

+ 2 - 1
test/test_cloud_helpers.py

@@ -1,9 +1,10 @@
 import itertools
 import itertools
-from test.helpers import ProviderTestBase
 
 
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.base.resources import ServerPagedResultList
 
 
+from test.helpers import ProviderTestBase
+
 
 
 class DummyResult(object):
 class DummyResult(object):
 
 

+ 5 - 4
test/test_compute_service.py

@@ -1,7 +1,6 @@
 import ipaddress
 import ipaddress
-from test import helpers
-from test.helpers import ProviderTestBase
-from test.helpers import standard_interface_tests as sit
+
+import six
 
 
 from cloudbridge.cloud.factory import ProviderList
 from cloudbridge.cloud.factory import ProviderList
 from cloudbridge.cloud.interfaces import InstanceState
 from cloudbridge.cloud.interfaces import InstanceState
@@ -11,7 +10,9 @@ from cloudbridge.cloud.interfaces.resources import Instance
 from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import VMType
 
 
-import six
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
 
 
 
 
 class CloudComputeServiceTestCase(ProviderTestBase):
 class CloudComputeServiceTestCase(ProviderTestBase):

+ 3 - 3
test/test_image_service.py

@@ -1,10 +1,10 @@
+from cloudbridge.cloud.interfaces import MachineImageState
+from cloudbridge.cloud.interfaces.resources import MachineImage
+
 from test import helpers
 from test import helpers
 from test.helpers import ProviderTestBase
 from test.helpers import ProviderTestBase
 from test.helpers import standard_interface_tests as sit
 from test.helpers import standard_interface_tests as sit
 
 
-from cloudbridge.cloud.interfaces import MachineImageState
-from cloudbridge.cloud.interfaces.resources import MachineImage
-
 
 
 class CloudImageServiceTestCase(ProviderTestBase):
 class CloudImageServiceTestCase(ProviderTestBase):
 
 

+ 2 - 1
test/test_interface.py

@@ -1,5 +1,4 @@
 import unittest
 import unittest
-from test.helpers import ProviderTestBase
 
 
 import cloudbridge
 import cloudbridge
 from cloudbridge.cloud import interfaces
 from cloudbridge.cloud import interfaces
@@ -7,6 +6,8 @@ from cloudbridge.cloud.factory import CloudProviderFactory
 from cloudbridge.cloud.interfaces import TestMockHelperMixin
 from cloudbridge.cloud.interfaces import TestMockHelperMixin
 from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
 from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
 
 
+from test.helpers import ProviderTestBase
+
 
 
 class CloudInterfaceTestCase(ProviderTestBase):
 class CloudInterfaceTestCase(ProviderTestBase):
 
 

+ 5 - 5
test/test_network_service.py

@@ -1,13 +1,13 @@
-import test.helpers as helpers
-from test.helpers import ProviderTestBase
-from test.helpers import get_provider_test_data
-from test.helpers import standard_interface_tests as sit
-
 from cloudbridge.cloud.interfaces.resources import FloatingIP
 from cloudbridge.cloud.interfaces.resources import FloatingIP
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import RouterState
 from cloudbridge.cloud.interfaces.resources import RouterState
 from cloudbridge.cloud.interfaces.resources import Subnet
 from cloudbridge.cloud.interfaces.resources import Subnet
 
 
+import test.helpers as helpers
+from test.helpers import ProviderTestBase
+from test.helpers import get_provider_test_data
+from test.helpers import standard_interface_tests as sit
+
 
 
 class CloudNetworkServiceTestCase(ProviderTestBase):
 class CloudNetworkServiceTestCase(ProviderTestBase):
 
 

+ 3 - 3
test/test_object_life_cycle.py

@@ -1,9 +1,9 @@
-from test import helpers
-from test.helpers import ProviderTestBase
-
 from cloudbridge.cloud.interfaces import VolumeState
 from cloudbridge.cloud.interfaces import VolumeState
 from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 
 
+from test import helpers
+from test.helpers import ProviderTestBase
+
 
 
 class CloudObjectLifeCycleTestCase(ProviderTestBase):
 class CloudObjectLifeCycleTestCase(ProviderTestBase):
 
 

+ 5 - 4
test/test_object_store_service.py

@@ -4,17 +4,18 @@ import tempfile
 import uuid
 import uuid
 from datetime import datetime
 from datetime import datetime
 from io import BytesIO
 from io import BytesIO
-from test import helpers
-from test.helpers import ProviderTestBase
-from test.helpers import standard_interface_tests as sit
 from unittest import skip
 from unittest import skip
 
 
+import requests
+
 from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
 from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
 from cloudbridge.cloud.interfaces.provider import TestMockHelperMixin
 from cloudbridge.cloud.interfaces.provider import TestMockHelperMixin
 from cloudbridge.cloud.interfaces.resources import Bucket
 from cloudbridge.cloud.interfaces.resources import Bucket
 from cloudbridge.cloud.interfaces.resources import BucketObject
 from cloudbridge.cloud.interfaces.resources import BucketObject
 
 
-import requests
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
 
 
 
 
 class CloudObjectStoreServiceTestCase(ProviderTestBase):
 class CloudObjectStoreServiceTestCase(ProviderTestBase):

+ 4 - 4
test/test_region_service.py

@@ -1,10 +1,10 @@
-from test import helpers
-from test.helpers import ProviderTestBase
-from test.helpers import standard_interface_tests as sit
+import six
 
 
 from cloudbridge.cloud.interfaces import Region
 from cloudbridge.cloud.interfaces import Region
 
 
-import six
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
 
 
 
 
 class CloudRegionServiceTestCase(ProviderTestBase):
 class CloudRegionServiceTestCase(ProviderTestBase):

+ 4 - 4
test/test_security_service.py

@@ -1,8 +1,4 @@
 """Test cloudbridge.security modules."""
 """Test cloudbridge.security modules."""
-from test import helpers
-from test.helpers import ProviderTestBase
-from test.helpers import standard_interface_tests as sit
-
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import KeyPair
@@ -10,6 +6,10 @@ from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMFirewallRule
 from cloudbridge.cloud.interfaces.resources import VMFirewallRule
 
 
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
+
 
 
 class CloudSecurityServiceTestCase(ProviderTestBase):
 class CloudSecurityServiceTestCase(ProviderTestBase):
 
 

+ 2 - 2
test/test_vm_types_service.py

@@ -1,9 +1,9 @@
+import six
+
 from test import helpers
 from test import helpers
 from test.helpers import ProviderTestBase
 from test.helpers import ProviderTestBase
 from test.helpers import standard_interface_tests as sit
 from test.helpers import standard_interface_tests as sit
 
 
-import six
-
 
 
 class CloudVMTypeServiceTestCase(ProviderTestBase):
 class CloudVMTypeServiceTestCase(ProviderTestBase):
 
 

+ 1 - 1
tox.ini

@@ -17,7 +17,7 @@ envlist = {py27,py36,pypy}-{aws,azure,openstack}
 [testenv]
 [testenv]
 commands = flake8 cloudbridge test setup.py
 commands = flake8 cloudbridge test setup.py
            # see setup.cfg for options sent to nosetests and coverage
            # see setup.cfg for options sent to nosetests and coverage
-           nosetests --logging-format='%(asctime)s [%(levelname)s] %(name)s: %(message)s' {posargs}
+           nosetests -v --nocapture --nologcapture --logging-format='%(asctime)s [%(levelname)s] %(name)s: %(message)s' {posargs}
 setenv =
 setenv =
     MOTO_AMIS_PATH=./test/fixtures/custom_amis.json
     MOTO_AMIS_PATH=./test/fixtures/custom_amis.json
     aws: CB_TEST_PROVIDER=aws
     aws: CB_TEST_PROVIDER=aws