provider.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import logging
  2. import uuid
  3. from deprecation import deprecated
  4. from msrestazure.azure_exceptions import CloudError
  5. import tenacity
  6. import cloudbridge
  7. from cloudbridge.cloud.base import BaseCloudProvider
  8. from cloudbridge.cloud.base.helpers import get_env
  9. from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
  10. from cloudbridge.cloud.providers.azure.azure_client import AzureClient
  11. from cloudbridge.cloud.providers.azure.services \
  12. import AzureComputeService, AzureNetworkingService, \
  13. AzureSecurityService, AzureStorageService
  14. log = logging.getLogger(__name__)
  15. class AzureCloudProvider(BaseCloudProvider):
  16. PROVIDER_ID = 'azure'
  17. def __init__(self, config):
  18. super(AzureCloudProvider, self).__init__(config)
  19. # mandatory config values
  20. self.subscription_id = self._get_config_value(
  21. 'azure_subscription_id', get_env('AZURE_SUBSCRIPTION_ID'))
  22. self.client_id = self._get_config_value(
  23. 'azure_client_id', get_env('AZURE_CLIENT_ID', None))
  24. self.secret = self._get_config_value(
  25. 'azure_secret', get_env('AZURE_SECRET', None))
  26. self.tenant = self._get_config_value(
  27. 'azure_tenant', get_env('AZURE_TENANT', None))
  28. # optional config values
  29. self.access_token = self._get_config_value(
  30. 'azure_access_token', get_env('AZURE_ACCESS_TOKEN', None))
  31. self.region_name = self._get_config_value(
  32. 'azure_region_name', get_env('AZURE_REGION_NAME', 'eastus'))
  33. self.resource_group = self._get_config_value(
  34. 'azure_resource_group', get_env('AZURE_RESOURCE_GROUP',
  35. 'cloudbridge'))
  36. # Storage account name is limited to a max length of 24 alphanum chars
  37. # and unique across all of Azure. Thus, a uuid is used to generate a
  38. # unique name for the Storage Account based on the resource group,
  39. # while also using the subscription ID to ensure that different users
  40. # having the same resource group name do not have the same SA name.
  41. self.storage_account = self._get_config_value(
  42. 'azure_storage_account',
  43. get_env(
  44. 'AZURE_STORAGE_ACCOUNT',
  45. 'storacc' + self.subscription_id[-6:] +
  46. str(uuid.uuid5(uuid.NAMESPACE_OID,
  47. str(self.resource_group)))[-6:]))
  48. self.vm_default_user_name = self._get_config_value(
  49. 'azure_vm_default_username', get_env(
  50. 'AZURE_VM_DEFAULT_USERNAME', None)) \
  51. or self.__get_deprecated_username('cbuser')
  52. self.public_key_storage_table_name = self._get_config_value(
  53. 'azure_public_key_storage_table_name', get_env(
  54. 'AZURE_PUBLIC_KEY_STORAGE_TABLE_NAME', 'cbcerts'))
  55. self._azure_client = None
  56. self._security = AzureSecurityService(self)
  57. self._storage = AzureStorageService(self)
  58. self._compute = AzureComputeService(self)
  59. self._networking = AzureNetworkingService(self)
  60. def __get_deprecated_username(self, default):
  61. username = self._get_config_value(
  62. 'azure_vm_default_user_name', get_env(
  63. 'AZURE_VM_DEFAULT_USER_NAME', None))
  64. if username:
  65. return self.__wrap_deprecated_username(username)
  66. else:
  67. return default
  68. @deprecated(deprecated_in='1.1', current_version=cloudbridge.__version__,
  69. details='AZURE_VM_DEFAULT_USER_NAME was deprecated in favor '
  70. 'of AZURE_VM_DEFAULT_USERNAME')
  71. def __wrap_deprecated_username(self, username):
  72. return username
  73. @property
  74. def compute(self):
  75. return self._compute
  76. @property
  77. def networking(self):
  78. return self._networking
  79. @property
  80. def security(self):
  81. return self._security
  82. @property
  83. def storage(self):
  84. return self._storage
  85. @property
  86. def azure_client(self):
  87. if not self._azure_client:
  88. # create a dict with both optional and mandatory configuration
  89. # values to pass to the azureclient class, rather
  90. # than passing the provider object and taking a dependency.
  91. provider_config = {
  92. 'azure_subscription_id': self.subscription_id,
  93. 'azure_client_id': self.client_id,
  94. 'azure_secret': self.secret,
  95. 'azure_tenant': self.tenant,
  96. 'azure_region_name': self.region_name,
  97. 'azure_resource_group': self.resource_group,
  98. 'azure_storage_account': self.storage_account,
  99. 'azure_public_key_storage_table_name':
  100. self.public_key_storage_table_name,
  101. 'azure_access_token': self.access_token
  102. }
  103. self._azure_client = AzureClient(provider_config)
  104. self._initialize()
  105. return self._azure_client
  106. @tenacity.retry(stop=tenacity.stop_after_attempt(2),
  107. retry=tenacity.retry_if_exception_type(CloudError),
  108. reraise=True)
  109. def _initialize(self):
  110. """
  111. Verifying that resource group and storage account exists
  112. if not create one with the name provided in the
  113. configuration
  114. """
  115. try:
  116. self._azure_client.get_resource_group(self.resource_group)
  117. except CloudError as cloud_error:
  118. if cloud_error.error.error == "ResourceGroupNotFound":
  119. resource_group_params = {'location': self.region_name}
  120. try:
  121. self._azure_client.\
  122. create_resource_group(self.resource_group,
  123. resource_group_params)
  124. except CloudError as cloud_error2: # pragma: no cover
  125. if cloud_error2.error.error == "AuthorizationFailed":
  126. mess = 'The following error was returned by Azure:\n' \
  127. '%s\n\nThis is likely because the Role' \
  128. 'associated with the given credentials does ' \
  129. 'not allow for Resource Group creation.\nA ' \
  130. 'Resource Group is necessary to manage ' \
  131. 'resources in Azure. You must either ' \
  132. 'provide an existing Resource Group as part ' \
  133. 'of the configuration, or elevate the ' \
  134. 'associated role.\nFor more information on ' \
  135. 'roles, see: https://docs.microsoft.com/' \
  136. 'en-us/azure/role-based-access-control/' \
  137. 'overview\n' % cloud_error2
  138. raise ProviderConnectionException(mess)
  139. else:
  140. raise cloud_error2
  141. else:
  142. raise cloud_error