|
@@ -12,13 +12,14 @@ from azure.mgmt.resource.subscriptions import SubscriptionClient
|
|
|
from azure.mgmt.storage import StorageManagementClient
|
|
from azure.mgmt.storage import StorageManagementClient
|
|
|
from azure.storage.blob import BlobPermissions
|
|
from azure.storage.blob import BlobPermissions
|
|
|
from azure.storage.blob import BlockBlobService
|
|
from azure.storage.blob import BlockBlobService
|
|
|
-
|
|
|
|
|
-from cloudbridge.cloud.interfaces.exceptions import WaitStateException
|
|
|
|
|
|
|
+from azure.storage.common import TokenCredential
|
|
|
|
|
|
|
|
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__)
|
|
@@ -157,6 +158,7 @@ class AzureClient(object):
|
|
|
tenant=config.get('azure_tenant')
|
|
tenant=config.get('azure_tenant')
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ self._access_token = config.get('azure_access_token')
|
|
|
self._resource_client = None
|
|
self._resource_client = None
|
|
|
self._storage_client = None
|
|
self._storage_client = None
|
|
|
self._network_management_client = None
|
|
self._network_management_client = None
|
|
@@ -165,6 +167,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)
|
|
|
|
|
|
|
@@ -245,14 +248,22 @@ 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:
|
|
|
- self._block_blob_service = BlockBlobService(
|
|
|
|
|
- self.storage_account,
|
|
|
|
|
- self.access_key_result.keys[0].value)
|
|
|
|
|
|
|
+ if self._access_token:
|
|
|
|
|
+ token_credential = TokenCredential(self._access_token)
|
|
|
|
|
+ self._block_blob_service = BlockBlobService(
|
|
|
|
|
+ account_name=self.storage_account,
|
|
|
|
|
+ token_credential=token_credential)
|
|
|
|
|
+ else:
|
|
|
|
|
+ self._block_blob_service = BlockBlobService(
|
|
|
|
|
+ account_name=self.storage_account,
|
|
|
|
|
+ account_key=self.access_key_result.keys[0].value)
|
|
|
return self._block_blob_service
|
|
return self._block_blob_service
|
|
|
|
|
|
|
|
@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,
|
|
@@ -278,6 +289,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)
|
|
@@ -576,9 +646,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
|
|
|
|
|
|