Просмотр исходного кода

Merge pull request #139 from CloudVE/better-exceptions

Change Resource Group and Storage Account Handling
Nuwan Goonasekera 7 лет назад
Родитель
Сommit
cd34ce5977

+ 3 - 0
.gitignore

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

+ 1 - 1
.travis.yml

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

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

@@ -9,6 +9,8 @@ import re
 import shutil
 import time
 
+import six
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.interfaces.exceptions \
     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 VolumeState
 
-import six
-
 log = logging.getLogger(__name__)
 
 

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

@@ -4,6 +4,8 @@ import string
 
 from botocore.exceptions import ClientError
 
+import requests
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 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 Volume
 
-import requests
-
 from .helpers import BotoEC2Service
 from .helpers import BotoS3Service
 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.common import TokenCredential
 
-from cloudbridge.cloud.interfaces.exceptions import WaitStateException
-
 from msrestazure.azure_exceptions import CloudError
 
 import tenacity
 
+from cloudbridge.cloud.interfaces.exceptions import \
+    InvalidNameException, ProviderConnectionException, WaitStateException
+
 from . import helpers as azure_helpers
 
 log = logging.getLogger(__name__)
@@ -167,6 +168,7 @@ class AzureClient(object):
         self._access_key_result = None
         self._block_blob_service = None
         self._table_service = None
+        self._storage_account = None
 
         log.debug("azure subscription : %s", self.subscription_id)
 
@@ -247,6 +249,7 @@ class AzureClient(object):
 
     @property
     def blob_service(self):
+        self._get_or_create_storage_account()
         if not self._block_blob_service:
             if self._access_token:
                 token_credential = TokenCredential(self._access_token)
@@ -261,6 +264,7 @@ class AzureClient(object):
 
     @property
     def table_service(self):
+        self._get_or_create_storage_account()
         if not self._table_service:
             self._table_service = TableService(
                 self.storage_account,
@@ -286,6 +290,65 @@ class AzureClient(object):
         return self.storage_client.storage_accounts. \
             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):
         return self.subscription_client.subscriptions. \
             list_locations(self.subscription_id)
@@ -584,9 +647,7 @@ class AzureClient(object):
     def __if_subnet_in_use(e):
         # return True if the CloudError exception is due to subnet being in use
         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 False
 

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

@@ -1,16 +1,18 @@
 import logging
 import os
+import uuid
+
+from msrestazure.azure_exceptions import CloudError
+
+import tenacity
 
 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.services \
     import AzureComputeService, AzureNetworkingService, \
     AzureSecurityService, AzureStorageService
 
-from msrestazure.azure_exceptions import CloudError
-
-import tenacity
-
 log = logging.getLogger(__name__)
 
 
@@ -41,16 +43,17 @@ class AzureCloudProvider(BaseCloudProvider):
             'azure_resource_group', os.environ.get('AZURE_RESOURCE_GROUP',
                                                    'cloudbridge'))
         # 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(
             'azure_storage_account',
             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(
             'azure_vm_default_user_name', os.environ.get
@@ -108,6 +111,9 @@ class AzureCloudProvider(BaseCloudProvider):
             self._initialize()
         return self._azure_client
 
+    @tenacity.retry(stop=tenacity.stop_after_attempt(2),
+                    retry=tenacity.retry_if_exception_type(CloudError),
+                    reraise=True)
     def _initialize(self):
         """
         Verifying that resource group and storage account exists
@@ -116,25 +122,31 @@ class AzureCloudProvider(BaseCloudProvider):
         """
         try:
             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.network.models import NetworkSecurityGroup
 
+from msrestazure.azure_exceptions import CloudError
+
+import pysftp
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
     BaseBucket, BaseBucketContainer, BaseBucketObject, BaseFloatingIP, \
@@ -22,10 +26,6 @@ from cloudbridge.cloud.interfaces.resources import Instance, \
     MachineImageState, NetworkState, RouterState, \
     SnapshotState, SubnetState, TrafficDirection
 
-from msrestazure.azure_exceptions import CloudError
-
-import pysftp
-
 from . import helpers as azure_helpers
 
 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.mgmt.compute.models import DiskCreateOption
 
+from msrestazure.azure_exceptions import CloudError
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import ClientPagedResultList, \
     ServerPagedResultList
@@ -20,8 +22,6 @@ from cloudbridge.cloud.interfaces.exceptions import \
 from cloudbridge.cloud.interfaces.resources import MachineImage, \
     Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
 
-from msrestazure.azure_exceptions import CloudError
-
 from . import helpers as azure_helpers
 from .resources import AzureBucket, \
     AzureInstance, AzureKeyPair, \
@@ -972,8 +972,7 @@ class AzureSubnetService(BaseSubnetService):
                         net.id
                     ))
                 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)
                     else:
                         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 cloudbridge.cloud.base import BaseCloudProvider
-
 from keystoneauth1 import session
 
 from keystoneclient import client as keystone_client
@@ -20,6 +18,8 @@ from openstack import connection
 
 from swiftclient import client as swift_client
 
+from cloudbridge.cloud.base import BaseCloudProvider
+
 from .services import OpenStackComputeService
 from .services import OpenStackNetworkingService
 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 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
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 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.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
 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 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
 from cloudbridge.cloud.base.resources import BaseLaunchConfig
 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.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 OpenStackInstance
 from .resources import OpenStackInternetGateway

+ 3 - 0
setup.cfg

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

+ 2 - 2
test/helpers/__init__.py

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

+ 2 - 1
test/helpers/standard_interface_tests.py

@@ -5,7 +5,6 @@ This includes:
    2. Checking for object equality and repr
    3. Checking standard behaviour for list, iter, find, get, delete
 """
-import test.helpers as helpers
 import uuid
 
 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 ResultList
 
+import test.helpers as helpers
+
 
 def check_repr(test, obj):
     test.assertTrue(

+ 5 - 4
test/test_block_store_service.py

@@ -1,8 +1,7 @@
 import time
 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.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 Volume
 
-import six
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
 
 
 class CloudBlockStoreServiceTestCase(ProviderTestBase):

+ 2 - 1
test/test_cloud_factory.py

@@ -1,5 +1,4 @@
 import unittest
-from test import helpers
 
 from cloudbridge.cloud import factory
 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.provider import MockAWSCloudProvider
 
+from test import helpers
+
 
 class CloudFactoryTestCase(unittest.TestCase):
 

+ 2 - 1
test/test_cloud_helpers.py

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

+ 5 - 4
test/test_compute_service.py

@@ -1,7 +1,6 @@
 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.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 VMType
 
-import six
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
 
 
 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.helpers import ProviderTestBase
 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):
 

+ 2 - 1
test/test_interface.py

@@ -1,5 +1,4 @@
 import unittest
-from test.helpers import ProviderTestBase
 
 import cloudbridge
 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.exceptions import ProviderConnectionException
 
+from test.helpers import 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 Network
 from cloudbridge.cloud.interfaces.resources import RouterState
 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):
 

+ 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.exceptions import WaitStateException
 
+from test import helpers
+from test.helpers import ProviderTestBase
+
 
 class CloudObjectLifeCycleTestCase(ProviderTestBase):
 

+ 5 - 4
test/test_object_store_service.py

@@ -4,17 +4,18 @@ import tempfile
 import uuid
 from datetime import datetime
 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
 
+import requests
+
 from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
 from cloudbridge.cloud.interfaces.provider import TestMockHelperMixin
 from cloudbridge.cloud.interfaces.resources import Bucket
 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):

+ 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
 
-import six
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
 
 
 class CloudRegionServiceTestCase(ProviderTestBase):

+ 4 - 4
test/test_security_service.py

@@ -1,8 +1,4 @@
 """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
 from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 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 VMFirewallRule
 
+from test import helpers
+from test.helpers import ProviderTestBase
+from test.helpers import standard_interface_tests as sit
+
 
 class CloudSecurityServiceTestCase(ProviderTestBase):
 

+ 2 - 2
test/test_vm_types_service.py

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

+ 1 - 1
tox.ini

@@ -17,7 +17,7 @@ envlist = {py27,py36,pypy}-{aws,azure,openstack}
 [testenv]
 commands = flake8 cloudbridge test setup.py
            # 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 =
     MOTO_AMIS_PATH=./test/fixtures/custom_amis.json
     aws: CB_TEST_PROVIDER=aws