services.py 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138
  1. import base64
  2. import logging
  3. import uuid
  4. from azure.common import AzureException
  5. from azure.mgmt.compute.models import DiskCreateOption
  6. from msrestazure.azure_exceptions import CloudError
  7. import cloudbridge.cloud.base.helpers as cb_helpers
  8. from cloudbridge.cloud.base.resources import ClientPagedResultList, \
  9. ServerPagedResultList
  10. from cloudbridge.cloud.base.services import BaseBucketService, \
  11. BaseComputeService, \
  12. BaseImageService, BaseInstanceService, BaseKeyPairService, \
  13. BaseNetworkService, BaseNetworkingService, BaseRegionService, \
  14. BaseRouterService, BaseSecurityService, BaseSnapshotService, \
  15. BaseStorageService, BaseSubnetService, BaseVMFirewallService, \
  16. BaseVMTypeService, BaseVolumeService
  17. from cloudbridge.cloud.interfaces.exceptions import \
  18. DuplicateResourceException, InvalidValueException
  19. from cloudbridge.cloud.interfaces.resources import MachineImage, \
  20. Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
  21. from . import helpers as azure_helpers
  22. from .resources import AzureBucket, \
  23. AzureInstance, AzureKeyPair, \
  24. AzureLaunchConfig, AzureMachineImage, AzureNetwork, \
  25. AzureRegion, AzureRouter, AzureSnapshot, AzureSubnet, \
  26. AzureVMFirewall, AzureVMType, AzureVolume
  27. log = logging.getLogger(__name__)
  28. class AzureSecurityService(BaseSecurityService):
  29. def __init__(self, provider):
  30. super(AzureSecurityService, self).__init__(provider)
  31. # Initialize provider services
  32. self._key_pairs = AzureKeyPairService(provider)
  33. self._vm_firewalls = AzureVMFirewallService(provider)
  34. @property
  35. def key_pairs(self):
  36. return self._key_pairs
  37. @property
  38. def vm_firewalls(self):
  39. return self._vm_firewalls
  40. class AzureVMFirewallService(BaseVMFirewallService):
  41. def __init__(self, provider):
  42. super(AzureVMFirewallService, self).__init__(provider)
  43. def get(self, fw_id):
  44. try:
  45. fws = self.provider.azure_client.get_vm_firewall(fw_id)
  46. return AzureVMFirewall(self.provider, fws)
  47. except (CloudError, InvalidValueException) as cloud_error:
  48. # Azure raises the cloud error if the resource not available
  49. log.exception(cloud_error)
  50. return None
  51. def list(self, limit=None, marker=None):
  52. fws = [AzureVMFirewall(self.provider, fw)
  53. for fw in self.provider.azure_client.list_vm_firewall()]
  54. return ClientPagedResultList(self.provider, fws, limit, marker)
  55. def create(self, label=None, description=None, network_id=None):
  56. parameters = {"location": self.provider.region_name}
  57. if label:
  58. AzureVMFirewall.assert_valid_resource_name(label)
  59. parameters.update({'tags': {'Label': label}})
  60. else:
  61. label = "cb-fw"
  62. if description:
  63. tags = parameters.get('tags')
  64. if tags:
  65. tags.update(Description=description)
  66. else:
  67. parameters.update({'tags': {'Description': description}})
  68. name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
  69. fw = self.provider.azure_client.create_vm_firewall(name,
  70. parameters)
  71. # Add default rules to negate azure default rules.
  72. # See: https://github.com/CloudVE/cloudbridge/issues/106
  73. # pylint:disable=protected-access
  74. for rule in fw.default_security_rules:
  75. rule_name = "cb-override-" + rule.name
  76. # Transpose rules to priority 4001 onwards, because
  77. # only 0-4096 are allowed for custom rules
  78. rule.priority = rule.priority - 61440
  79. rule.access = "Deny"
  80. self.provider.azure_client.create_vm_firewall_rule(
  81. fw.id, rule_name, rule)
  82. # Add a new custom rule allowing all outbound traffic to the internet
  83. parameters = {"priority": 3000,
  84. "protocol": "*",
  85. "source_port_range": "*",
  86. "source_address_prefix": "*",
  87. "destination_port_range": "*",
  88. "destination_address_prefix": "Internet",
  89. "access": "Allow",
  90. "direction": "Outbound"}
  91. result = self.provider.azure_client.create_vm_firewall_rule(
  92. fw.id, "cb-default-internet-outbound", parameters)
  93. fw.security_rules.append(result)
  94. cb_fw = AzureVMFirewall(self.provider, fw)
  95. return cb_fw
  96. def find(self, **kwargs):
  97. obj_list = self
  98. filters = ['label', 'name']
  99. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  100. # All kwargs should have been popped at this time.
  101. if len(kwargs) > 0:
  102. raise TypeError("Unrecognised parameters for search: %s."
  103. " Supported attributes: %s" % (kwargs,
  104. ", ".join(filters)))
  105. return ClientPagedResultList(self.provider,
  106. matches if matches else [])
  107. def delete(self, group_id):
  108. self.provider.azure_client.delete_vm_firewall(group_id)
  109. class AzureKeyPairService(BaseKeyPairService):
  110. PARTITION_KEY = '00000000-0000-0000-0000-000000000000'
  111. def __init__(self, provider):
  112. super(AzureKeyPairService, self).__init__(provider)
  113. def get(self, key_pair_id):
  114. try:
  115. key_pair = self.provider.azure_client.\
  116. get_public_key(key_pair_id)
  117. if key_pair:
  118. return AzureKeyPair(self.provider, key_pair)
  119. return None
  120. except AzureException as error:
  121. log.exception(error)
  122. return None
  123. def list(self, limit=None, marker=None):
  124. key_pairs, resume_marker = self.provider.azure_client.list_public_keys(
  125. AzureKeyPairService.PARTITION_KEY, marker=marker,
  126. limit=limit or self.provider.config.default_result_limit)
  127. results = [AzureKeyPair(self.provider, key_pair)
  128. for key_pair in key_pairs]
  129. return ServerPagedResultList(is_truncated=resume_marker,
  130. marker=resume_marker,
  131. supports_total=False,
  132. data=results)
  133. def find(self, **kwargs):
  134. obj_list = self
  135. filters = ['name']
  136. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  137. # All kwargs should have been popped at this time.
  138. if len(kwargs) > 0:
  139. raise TypeError("Unrecognised parameters for search: %s."
  140. " Supported attributes: %s" % (kwargs,
  141. ", ".join(filters)))
  142. return ClientPagedResultList(self.provider,
  143. matches if matches else [])
  144. def create(self, name, public_key_material=None):
  145. AzureKeyPair.assert_valid_resource_name(name)
  146. key_pair = self.get(name)
  147. if key_pair:
  148. raise DuplicateResourceException(
  149. 'Keypair already exists with name {0}'.format(name))
  150. private_key = None
  151. if not public_key_material:
  152. public_key_material, private_key = cb_helpers.generate_key_pair()
  153. entity = {
  154. 'PartitionKey': AzureKeyPairService.PARTITION_KEY,
  155. 'RowKey': str(uuid.uuid4()),
  156. 'Name': name,
  157. 'Key': public_key_material
  158. }
  159. self.provider.azure_client.create_public_key(entity)
  160. key_pair = self.get(name)
  161. key_pair.material = private_key
  162. return key_pair
  163. class AzureBucketService(BaseBucketService):
  164. def __init__(self, provider):
  165. super(AzureBucketService, self).__init__(provider)
  166. def get(self, bucket_id):
  167. """
  168. Returns a bucket given its ID. Returns ``None`` if the bucket
  169. does not exist.
  170. """
  171. try:
  172. bucket = self.provider.azure_client.get_container(bucket_id)
  173. return AzureBucket(self.provider, bucket)
  174. except AzureException as error:
  175. log.exception(error)
  176. return None
  177. def find(self, **kwargs):
  178. obj_list = self
  179. filters = ['name']
  180. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  181. # All kwargs should have been popped at this time.
  182. if len(kwargs) > 0:
  183. raise TypeError("Unrecognised parameters for search: %s."
  184. " Supported attributes: %s" % (kwargs,
  185. ", ".join(filters)))
  186. return ClientPagedResultList(self.provider,
  187. matches if matches else [])
  188. def list(self, limit=None, marker=None):
  189. """
  190. List all containers.
  191. """
  192. buckets = [AzureBucket(self.provider, bucket)
  193. for bucket in self.provider.azure_client.list_containers()]
  194. return ClientPagedResultList(self.provider, buckets,
  195. limit=limit, marker=marker)
  196. def create(self, name, location=None):
  197. """
  198. Create a new bucket.
  199. """
  200. AzureBucket.assert_valid_resource_name(name)
  201. bucket = self.provider.azure_client.create_container(name.lower())
  202. return AzureBucket(self.provider, bucket)
  203. class AzureStorageService(BaseStorageService):
  204. def __init__(self, provider):
  205. super(AzureStorageService, self).__init__(provider)
  206. # Initialize provider services
  207. self._volume_svc = AzureVolumeService(self.provider)
  208. self._snapshot_svc = AzureSnapshotService(self.provider)
  209. self._bucket_svc = AzureBucketService(self.provider)
  210. @property
  211. def volumes(self):
  212. return self._volume_svc
  213. @property
  214. def snapshots(self):
  215. return self._snapshot_svc
  216. @property
  217. def buckets(self):
  218. return self._bucket_svc
  219. class AzureVolumeService(BaseVolumeService):
  220. def __init__(self, provider):
  221. super(AzureVolumeService, self).__init__(provider)
  222. def get(self, volume_id):
  223. """
  224. Returns a volume given its id.
  225. """
  226. try:
  227. volume = self.provider.azure_client.get_disk(volume_id)
  228. return AzureVolume(self.provider, volume)
  229. except (CloudError, InvalidValueException) as cloud_error:
  230. # Azure raises the cloud error if the resource not available
  231. log.exception(cloud_error)
  232. return None
  233. def find(self, **kwargs):
  234. obj_list = self
  235. filters = ['name', 'label']
  236. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  237. # All kwargs should have been popped at this time.
  238. if len(kwargs) > 0:
  239. raise TypeError("Unrecognised parameters for search: %s."
  240. " Supported attributes: %s" % (kwargs,
  241. ", ".join(filters)))
  242. return ClientPagedResultList(self.provider,
  243. matches if matches else [])
  244. def list(self, limit=None, marker=None):
  245. """
  246. List all volumes.
  247. """
  248. azure_vols = self.provider.azure_client.list_disks()
  249. cb_vols = [AzureVolume(self.provider, vol) for vol in azure_vols]
  250. return ClientPagedResultList(self.provider, cb_vols,
  251. limit=limit, marker=marker)
  252. def create(self, size, label=None, description=None,
  253. zone=None, snapshot=None):
  254. """
  255. Creates a new volume.
  256. """
  257. AzureVolume.assert_valid_resource_name(label)
  258. if label:
  259. AzureVolume.assert_valid_resource_name(label)
  260. tags = {'Label': label}
  261. else:
  262. label = "cb-vol"
  263. disk_name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
  264. zone_id = zone.id if isinstance(zone, PlacementZone) else zone
  265. snapshot = (self.provider.storage.snapshots.get(snapshot)
  266. if snapshot and isinstance(snapshot, str) else snapshot)
  267. if description:
  268. if not tags:
  269. tags = {}
  270. tags.update(Description=description)
  271. if snapshot:
  272. params = {
  273. 'location':
  274. zone_id or self.provider.azure_client.region_name,
  275. 'creation_data': {
  276. 'create_option': DiskCreateOption.copy,
  277. 'source_uri': snapshot.resource_id
  278. }
  279. }
  280. if tags:
  281. params.update(tags=tags)
  282. disk = self.provider.azure_client.create_snapshot_disk(disk_name,
  283. params)
  284. else:
  285. params = {
  286. 'location':
  287. zone_id or self.provider.region_name,
  288. 'disk_size_gb': size,
  289. 'creation_data': {
  290. 'create_option': DiskCreateOption.empty
  291. }
  292. }
  293. if tags:
  294. params.update(tags=tags)
  295. disk = self.provider.azure_client.create_empty_disk(disk_name,
  296. params)
  297. azure_vol = self.provider.azure_client.get_disk(disk.id)
  298. cb_vol = AzureVolume(self.provider, azure_vol)
  299. return cb_vol
  300. class AzureSnapshotService(BaseSnapshotService):
  301. def __init__(self, provider):
  302. super(AzureSnapshotService, self).__init__(provider)
  303. def get(self, ss_id):
  304. """
  305. Returns a snapshot given its id.
  306. """
  307. try:
  308. snapshot = self.provider.azure_client.get_snapshot(ss_id)
  309. return AzureSnapshot(self.provider, snapshot)
  310. except (CloudError, InvalidValueException) as cloud_error:
  311. # Azure raises the cloud error if the resource not available
  312. log.exception(cloud_error)
  313. return None
  314. def find(self, **kwargs):
  315. obj_list = self
  316. filters = ['name', 'label']
  317. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  318. # All kwargs should have been popped at this time.
  319. if len(kwargs) > 0:
  320. raise TypeError("Unrecognised parameters for search: %s."
  321. " Supported attributes: %s" % (kwargs,
  322. ", ".join(filters)))
  323. return ClientPagedResultList(self.provider,
  324. matches if matches else [])
  325. def list(self, limit=None, marker=None):
  326. """
  327. List all snapshots.
  328. """
  329. snaps = [AzureSnapshot(self.provider, obj)
  330. for obj in
  331. self.provider.azure_client.list_snapshots()]
  332. return ClientPagedResultList(self.provider, snaps, limit, marker)
  333. def create(self, volume, label=None, description=None):
  334. """
  335. Creates a new snapshot of a given volume.
  336. """
  337. volume = (self.provider.storage.volumes.get(volume)
  338. if isinstance(volume, str) else volume)
  339. if label:
  340. AzureSnapshot.assert_valid_resource_name(label)
  341. tags = {'Label': label}
  342. else:
  343. label = "cb-snap"
  344. snapshot_name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
  345. if description:
  346. if not tags:
  347. tags = {}
  348. tags.update(Description=description)
  349. params = {
  350. 'location': self.provider.azure_client.region_name,
  351. 'creation_data': {
  352. 'create_option': DiskCreateOption.copy,
  353. 'source_uri': volume.resource_id
  354. },
  355. 'disk_size_gb': volume.size
  356. }
  357. if tags:
  358. params.update(tags=tags)
  359. azure_snap = self.provider.azure_client.create_snapshot(snapshot_name,
  360. params)
  361. return AzureSnapshot(self.provider, azure_snap)
  362. class AzureComputeService(BaseComputeService):
  363. def __init__(self, provider):
  364. super(AzureComputeService, self).__init__(provider)
  365. self._vm_type_svc = AzureVMTypeService(self.provider)
  366. self._instance_svc = AzureInstanceService(self.provider)
  367. self._region_svc = AzureRegionService(self.provider)
  368. self._images_svc = AzureImageService(self.provider)
  369. @property
  370. def images(self):
  371. return self._images_svc
  372. @property
  373. def vm_types(self):
  374. return self._vm_type_svc
  375. @property
  376. def instances(self):
  377. return self._instance_svc
  378. @property
  379. def regions(self):
  380. return self._region_svc
  381. class AzureInstanceService(BaseInstanceService):
  382. def __init__(self, provider):
  383. super(AzureInstanceService, self).__init__(provider)
  384. def create(self, image, vm_type, label=None, subnet=None, zone=None,
  385. key_pair=None, vm_firewalls=None, user_data=None,
  386. launch_config=None, **kwargs):
  387. if label:
  388. prefix = label
  389. else:
  390. prefix = "cb-ins"
  391. instance_name = "{0}-{1}".format(prefix, uuid.uuid4().hex[:6])
  392. AzureInstance.assert_valid_resource_name(instance_name)
  393. image = (image if isinstance(image, AzureMachineImage) else
  394. self.provider.compute.images.get(image))
  395. if not isinstance(image, AzureMachineImage):
  396. raise Exception("Provided image %s is not a valid azure image"
  397. % image)
  398. instance_size = vm_type.id if \
  399. isinstance(vm_type, VMType) else vm_type
  400. if not subnet:
  401. # Azure has only a single zone per region; use the current one
  402. zone = self.provider.compute.regions.get(
  403. self.provider.region_name).zones[0]
  404. subnet = self.provider.networking.subnets.get_or_create_default(
  405. zone)
  406. else:
  407. subnet = (self.provider.networking.subnets.get(subnet)
  408. if isinstance(subnet, str) else subnet)
  409. zone_id = zone.id if isinstance(zone, PlacementZone) else zone
  410. subnet_id, zone_id, vm_firewall_id = \
  411. self._resolve_launch_options(instance_name,
  412. subnet, zone_id, vm_firewalls)
  413. storage_profile = self._create_storage_profile(image, launch_config,
  414. instance_name, zone_id)
  415. nic_params = {
  416. 'location': self.provider.region_name,
  417. 'ip_configurations': [{
  418. 'name': instance_name + '_ip_config',
  419. 'private_ip_allocation_method': 'Dynamic',
  420. 'subnet': {
  421. 'id': subnet_id
  422. }
  423. }]
  424. }
  425. if vm_firewall_id:
  426. nic_params['network_security_group'] = {
  427. 'id': vm_firewall_id
  428. }
  429. nic_info = self.provider.azure_client.create_nic(
  430. instance_name + '_nic',
  431. nic_params
  432. )
  433. # #! indicates shell script
  434. ud = '#cloud-config\n' + user_data \
  435. if user_data and not user_data.startswith('#!')\
  436. and not user_data.startswith('#cloud-config') else user_data
  437. # Key_pair is mandatory in azure and it should not be None.
  438. temp_key_pair = None
  439. if key_pair:
  440. key_pair = (self.provider.security.key_pairs.get(key_pair)
  441. if isinstance(key_pair, str) else key_pair)
  442. else:
  443. # Create a temporary keypair if none is provided to keep Azure
  444. # happy, but the private key will be discarded, so it'll be all
  445. # but useless. However, this will allow an instance to be launched
  446. # without specifying a keypair, so users may still be able to login
  447. # if they have a preinstalled keypair/password baked into the image
  448. temp_kp_name = "".join(["cb_default_kp_",
  449. str(uuid.uuid5(uuid.NAMESPACE_OID,
  450. instance_name))[-6:]])
  451. key_pair = self.provider.security.key_pairs.create(
  452. name=temp_kp_name)
  453. temp_key_pair = key_pair
  454. params = {
  455. 'location': zone_id or self.provider.region_name,
  456. 'os_profile': {
  457. 'admin_username': self.provider.vm_default_user_name,
  458. 'computer_name': instance_name,
  459. 'linux_configuration': {
  460. "disable_password_authentication": True,
  461. "ssh": {
  462. "public_keys": [{
  463. "path":
  464. "/home/{}/.ssh/authorized_keys".format(
  465. self.provider.vm_default_user_name),
  466. "key_data": key_pair._key_pair.Key
  467. }]
  468. }
  469. }
  470. },
  471. 'hardware_profile': {
  472. 'vm_size': instance_size
  473. },
  474. 'network_profile': {
  475. 'network_interfaces': [{
  476. 'id': nic_info.id
  477. }]
  478. },
  479. 'storage_profile': storage_profile,
  480. 'tags': {}
  481. }
  482. if label:
  483. params['tags'].update(Label=label)
  484. for disk_def in storage_profile.get('data_disks', []):
  485. params['tags'] = dict(disk_def.get('tags', {}), **params['tags'])
  486. if user_data:
  487. custom_data = base64.b64encode(bytes(ud, 'utf-8'))
  488. params['os_profile']['custom_data'] = str(custom_data, 'utf-8')
  489. if not temp_key_pair:
  490. params['tags'].update(Key_Pair=key_pair.name)
  491. try:
  492. vm = self.provider.azure_client.create_vm(instance_name, params)
  493. except Exception as e:
  494. # If VM creation fails, attempt to clean up intermediary resources
  495. self.provider.azure_client.delete_nic(nic_info.id)
  496. for disk_def in storage_profile.get('data_disks', []):
  497. if disk_def.get('tags', {}).get('delete_on_terminate'):
  498. disk_id = disk_def.get('managed_disk', {}).get('id')
  499. if disk_id:
  500. vol = self.provider.storage.volumes.get(disk_id)
  501. vol.delete()
  502. raise e
  503. finally:
  504. if temp_key_pair:
  505. temp_key_pair.delete()
  506. return AzureInstance(self.provider, vm)
  507. def _resolve_launch_options(self, inst_name, subnet=None, zone_id=None,
  508. vm_firewalls=None):
  509. if subnet:
  510. # subnet's zone takes precedence
  511. zone_id = subnet.zone.id
  512. vm_firewall_id = None
  513. if isinstance(vm_firewalls, list) and len(vm_firewalls) > 0:
  514. if isinstance(vm_firewalls[0], VMFirewall):
  515. vm_firewalls_ids = [fw.id for fw in vm_firewalls]
  516. vm_firewall_id = vm_firewalls[0].resource_id
  517. else:
  518. vm_firewalls_ids = vm_firewalls
  519. vm_firewall = self.provider.security.\
  520. vm_firewalls.get(vm_firewalls[0])
  521. vm_firewall_id = vm_firewall.resource_id
  522. if len(vm_firewalls) > 1:
  523. new_fw = self.provider.security.vm_firewalls.\
  524. create(label='{0}-fw'.format(inst_name),
  525. description='Merge vm firewall {0}'.
  526. format(','.join(vm_firewalls_ids)))
  527. for fw in vm_firewalls:
  528. new_fw.add_rule(src_dest_fw=fw)
  529. vm_firewall_id = new_fw.resource_id
  530. return subnet.resource_id, zone_id, vm_firewall_id
  531. def _create_storage_profile(self, image, launch_config, instance_name,
  532. zone_id):
  533. if image.is_gallery_image:
  534. reference = image._image.as_dict()
  535. image_ref = {
  536. 'publisher': reference['publisher'],
  537. 'offer': reference['offer'],
  538. 'sku': reference['sku'],
  539. 'version': reference['version']
  540. }
  541. else:
  542. image_ref = {
  543. 'id': image.resource_id
  544. }
  545. storage_profile = {
  546. 'image_reference': image_ref,
  547. "os_disk": {
  548. "name": instance_name + '_os_disk',
  549. "create_option": DiskCreateOption.from_image
  550. },
  551. }
  552. if launch_config:
  553. data_disks, root_disk_size = self._process_block_device_mappings(
  554. launch_config, instance_name, zone_id)
  555. if data_disks:
  556. storage_profile['data_disks'] = data_disks
  557. if root_disk_size:
  558. storage_profile['os_disk']['disk_size_gb'] = root_disk_size
  559. return storage_profile
  560. def _process_block_device_mappings(self, launch_config,
  561. vm_name, zone=None):
  562. """
  563. Processes block device mapping information
  564. and returns a Data disk dictionary list. If new volumes
  565. are requested (source is None and destination is VOLUME), they will be
  566. created and the relevant volume ids included in the mapping.
  567. """
  568. data_disks = []
  569. root_disk_size = None
  570. def append_disk(disk_def, device_no, delete_on_terminate):
  571. # In azure, there is no option to specify terminate disks
  572. # (similar to AWS delete_on_terminate) on VM delete.
  573. # This method uses the azure tags functionality to store
  574. # the delete_on_terminate option when the virtual machine
  575. # is deleted, we parse the tags and delete accordingly
  576. disk_def['lun'] = device_no
  577. disk_def['tags'] = {
  578. 'delete_on_terminate': delete_on_terminate
  579. }
  580. data_disks.append(disk_def)
  581. for device_no, device in enumerate(launch_config.block_devices):
  582. if device.is_volume:
  583. if device.is_root:
  584. root_disk_size = device.size
  585. else:
  586. # In azure, os disk automatically created,
  587. # we are ignoring the root disk, if specified
  588. if isinstance(device.source, Snapshot):
  589. snapshot_vol = device.source.create_volume()
  590. disk_def = {
  591. # pylint:disable=protected-access
  592. 'name': snapshot_vol._volume.name,
  593. 'create_option': DiskCreateOption.attach,
  594. 'managed_disk': {
  595. 'id': snapshot_vol.id
  596. }
  597. }
  598. elif isinstance(device.source, Volume):
  599. disk_def = {
  600. # pylint:disable=protected-access
  601. 'name': device.source._volume.name,
  602. 'create_option': DiskCreateOption.attach,
  603. 'managed_disk': {
  604. 'id': device.source.id
  605. }
  606. }
  607. elif isinstance(device.source, MachineImage):
  608. disk_def = {
  609. # pylint:disable=protected-access
  610. 'name': device.source._volume.name,
  611. 'create_option': DiskCreateOption.from_image,
  612. 'source_resource_id': device.source.id
  613. }
  614. else:
  615. disk_def = {
  616. # pylint:disable=protected-access
  617. 'create_option': DiskCreateOption.empty,
  618. 'disk_size_gb': device.size
  619. }
  620. append_disk(disk_def, device_no,
  621. device.delete_on_terminate)
  622. else: # device is ephemeral
  623. # in azure we cannot add the ephemeral disks explicitly
  624. pass
  625. return data_disks, root_disk_size
  626. def create_launch_config(self):
  627. return AzureLaunchConfig(self.provider)
  628. def list(self, limit=None, marker=None):
  629. """
  630. List all instances.
  631. """
  632. instances = [AzureInstance(self.provider, inst)
  633. for inst in self.provider.azure_client.list_vm()]
  634. return ClientPagedResultList(self.provider, instances,
  635. limit=limit, marker=marker)
  636. def get(self, instance_id):
  637. """
  638. Returns an instance given its id. Returns None
  639. if the object does not exist.
  640. """
  641. try:
  642. vm = self.provider.azure_client.get_vm(instance_id)
  643. return AzureInstance(self.provider, vm)
  644. except (CloudError, InvalidValueException) as cloud_error:
  645. # Azure raises the cloud error if the resource not available
  646. log.exception(cloud_error)
  647. return None
  648. def find(self, **kwargs):
  649. obj_list = self
  650. filters = ['name', 'label']
  651. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  652. # All kwargs should have been popped at this time.
  653. if len(kwargs) > 0:
  654. raise TypeError("Unrecognised parameters for search: %s."
  655. " Supported attributes: %s" % (kwargs,
  656. ", ".join(filters)))
  657. return ClientPagedResultList(self.provider,
  658. matches if matches else [])
  659. class AzureImageService(BaseImageService):
  660. def __init__(self, provider):
  661. super(AzureImageService, self).__init__(provider)
  662. def get(self, image_id):
  663. """
  664. Returns an Image given its id
  665. """
  666. try:
  667. image = self.provider.azure_client.get_image(image_id)
  668. return AzureMachineImage(self.provider, image)
  669. except (CloudError, InvalidValueException) as cloud_error:
  670. # Azure raises the cloud error if the resource not available
  671. log.exception(cloud_error)
  672. return None
  673. def find(self, **kwargs):
  674. obj_list = self
  675. filters = ['name', 'label']
  676. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  677. # All kwargs should have been popped at this time.
  678. if len(kwargs) > 0:
  679. raise TypeError("Unrecognised parameters for search: %s."
  680. " Supported attributes: %s" % (kwargs,
  681. ", ".join(filters)))
  682. return ClientPagedResultList(self.provider,
  683. matches if matches else [])
  684. def list(self, limit=None, marker=None):
  685. """
  686. List all images.
  687. """
  688. azure_images = self.provider.azure_client.list_images()
  689. azure_gallery_refs = self.provider.azure_client.list_gallery_refs()
  690. cb_images = [AzureMachineImage(self.provider, img)
  691. for img in azure_images + azure_gallery_refs]
  692. return ClientPagedResultList(self.provider, cb_images,
  693. limit=limit, marker=marker)
  694. class AzureVMTypeService(BaseVMTypeService):
  695. def __init__(self, provider):
  696. super(AzureVMTypeService, self).__init__(provider)
  697. @property
  698. def instance_data(self):
  699. """
  700. Fetch info about the available instances.
  701. """
  702. r = self.provider.azure_client.list_vm_types()
  703. return r
  704. def list(self, limit=None, marker=None):
  705. vm_types = [AzureVMType(self.provider, vm_type)
  706. for vm_type in self.instance_data]
  707. return ClientPagedResultList(self.provider, vm_types,
  708. limit=limit, marker=marker)
  709. class AzureNetworkingService(BaseNetworkingService):
  710. def __init__(self, provider):
  711. super(AzureNetworkingService, self).__init__(provider)
  712. self._network_service = AzureNetworkService(self.provider)
  713. self._subnet_service = AzureSubnetService(self.provider)
  714. self._router_service = AzureRouterService(self.provider)
  715. @property
  716. def networks(self):
  717. return self._network_service
  718. @property
  719. def subnets(self):
  720. return self._subnet_service
  721. @property
  722. def routers(self):
  723. return self._router_service
  724. class AzureNetworkService(BaseNetworkService):
  725. def __init__(self, provider):
  726. super(AzureNetworkService, self).__init__(provider)
  727. def get(self, network_id):
  728. try:
  729. network = self.provider.azure_client.get_network(network_id)
  730. return AzureNetwork(self.provider, network)
  731. except (CloudError, InvalidValueException) as cloud_error:
  732. # Azure raises the cloud error if the resource not available
  733. log.exception(cloud_error)
  734. return None
  735. def list(self, limit=None, marker=None):
  736. """
  737. List all networks.
  738. """
  739. networks = [AzureNetwork(self.provider, network)
  740. for network in self.provider.azure_client.list_networks()]
  741. return ClientPagedResultList(self.provider, networks,
  742. limit=limit, marker=marker)
  743. def find(self, **kwargs):
  744. obj_list = self
  745. filters = ['name', 'label']
  746. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  747. # All kwargs should have been popped at this time.
  748. if len(kwargs) > 0:
  749. raise TypeError("Unrecognised parameters for search: %s."
  750. " Supported attributes: %s" % (kwargs,
  751. ", ".join(filters)))
  752. return ClientPagedResultList(self.provider,
  753. matches if matches else [])
  754. def create(self, cidr_block, label=None):
  755. # Azure requires CIDR block to be specified when creating a network
  756. # so set a default one and use the largest allowed netmask.
  757. if label:
  758. tags = {'Label': label }
  759. else:
  760. label = 'cb-net'
  761. network_name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
  762. AzureNetwork.assert_valid_resource_name(network_name)
  763. params = {
  764. 'location': self.provider.azure_client.region_name,
  765. 'address_space': {
  766. 'address_prefixes': [cidr_block]
  767. }
  768. }
  769. if tags:
  770. params.update(tags=tags)
  771. az_network = self.provider.azure_client.create_network(network_name,
  772. params)
  773. cb_network = AzureNetwork(self.provider, az_network)
  774. return cb_network
  775. def delete(self, network_id):
  776. """
  777. Delete an existing network.
  778. """
  779. self.provider.azure_client.delete_network(network_id)
  780. class AzureRegionService(BaseRegionService):
  781. def __init__(self, provider):
  782. super(AzureRegionService, self).__init__(provider)
  783. def get(self, region_id):
  784. region = None
  785. for azureRegion in self.provider.azure_client.list_locations():
  786. if azureRegion.name == region_id:
  787. region = AzureRegion(self.provider, azureRegion)
  788. break
  789. return region
  790. def list(self, limit=None, marker=None):
  791. regions = [AzureRegion(self.provider, region)
  792. for region in self.provider.azure_client.list_locations()]
  793. return ClientPagedResultList(self.provider, regions,
  794. limit=limit, marker=marker)
  795. @property
  796. def current(self):
  797. return self.get(self.provider.region_name)
  798. class AzureSubnetService(BaseSubnetService):
  799. def __init__(self, provider):
  800. super(AzureSubnetService, self).__init__(provider)
  801. def get(self, subnet_id):
  802. """
  803. Azure does not provide an api to get the subnet directly by id.
  804. It also requires the network id.
  805. To make it consistent across the providers the following code
  806. gets the specific code from the subnet list.
  807. :param subnet_id:
  808. :return:
  809. """
  810. try:
  811. azure_subnet = self.provider.azure_client.get_subnet(subnet_id)
  812. return AzureSubnet(self.provider,
  813. azure_subnet) if azure_subnet else None
  814. except (CloudError, InvalidValueException) as cloud_error:
  815. # Azure raises the cloud error if the resource not available
  816. log.exception(cloud_error)
  817. return None
  818. def list(self, network=None, limit=None, marker=None):
  819. """
  820. List subnets
  821. """
  822. return ClientPagedResultList(self.provider,
  823. self._list_subnets(network),
  824. limit=limit, marker=marker)
  825. def _list_subnets(self, network=None):
  826. result_list = []
  827. if network:
  828. network_id = network.id \
  829. if isinstance(network, Network) else network
  830. result_list = self.provider.azure_client.list_subnets(network_id)
  831. else:
  832. for net in self.provider.networking.networks:
  833. try:
  834. result_list.extend(self.provider.azure_client.list_subnets(
  835. net.id
  836. ))
  837. except CloudError as cloud_error:
  838. if cloud_error.error.error == "ResourceNotFound":
  839. log.exception(cloud_error)
  840. else:
  841. raise cloud_error
  842. subnets = [AzureSubnet(self.provider, subnet)
  843. for subnet in result_list]
  844. return subnets
  845. def find(self, network=None, **kwargs):
  846. obj_list = self._list_subnets(network)
  847. filters = ['name']
  848. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  849. # All kwargs should have been popped at this time.
  850. if len(kwargs) > 0:
  851. raise TypeError("Unrecognised parameters for search: %s."
  852. " Supported attributes: %s" % (kwargs,
  853. ", ".join(filters)))
  854. return ClientPagedResultList(self.provider,
  855. matches if matches else [])
  856. def create(self, network, cidr_block, prefix=None, **kwargs):
  857. """
  858. Create subnet
  859. """
  860. network_id = network.id \
  861. if isinstance(network, Network) else network
  862. if prefix:
  863. AzureSubnet.assert_valid_resource_name(prefix)
  864. else:
  865. prefix = "cb-sn"
  866. subnet_name = "{0}-{1}".format(prefix, uuid.uuid4().hex[:6])
  867. subnet_info = self.provider.azure_client\
  868. .create_subnet(
  869. network_id,
  870. subnet_name,
  871. {
  872. 'address_prefix': cidr_block
  873. }
  874. )
  875. return AzureSubnet(self.provider, subnet_info)
  876. def get_or_create_default(self, zone):
  877. default_cidr = '10.0.1.0/24'
  878. # No provider-default Subnet exists, look for a library-default one
  879. matches = self.find(label=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
  880. if matches:
  881. return matches[0]
  882. # No provider-default Subnet exists, try to create it (net + subnets)
  883. networks = self.provider.networking.networks.find(
  884. label=AzureNetwork.CB_DEFAULT_NETWORK_NAME)
  885. if networks:
  886. network = networks[0]
  887. else:
  888. network = self.provider.networking.networks.create('10.0.0.0/16',
  889. label=AzureNetwork.CB_DEFAULT_NETWORK_NAME)
  890. subnet = self.create(network, default_cidr,
  891. prefix=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
  892. return subnet
  893. def delete(self, subnet):
  894. subnet_id = subnet.id if isinstance(subnet, Subnet) else subnet
  895. self.provider.azure_client.delete_subnet(subnet_id)
  896. class AzureRouterService(BaseRouterService):
  897. def __init__(self, provider):
  898. super(AzureRouterService, self).__init__(provider)
  899. def get(self, router_id):
  900. try:
  901. route = self.provider.azure_client.get_route_table(router_id)
  902. return AzureRouter(self.provider, route)
  903. except (CloudError, InvalidValueException) as cloud_error:
  904. # Azure raises the cloud error if the resource not available
  905. log.exception(cloud_error)
  906. return None
  907. def find(self, **kwargs):
  908. obj_list = self
  909. filters = ['name', 'label']
  910. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  911. # All kwargs should have been popped at this time.
  912. if len(kwargs) > 0:
  913. raise TypeError("Unrecognised parameters for search: %s."
  914. " Supported attributes: %s" % (kwargs,
  915. ", ".join(filters)))
  916. return ClientPagedResultList(self.provider,
  917. matches if matches else [])
  918. def list(self, limit=None, marker=None):
  919. routes = [AzureRouter(self.provider, route)
  920. for route in
  921. self.provider.azure_client.list_route_tables()]
  922. return ClientPagedResultList(self.provider,
  923. routes,
  924. limit=limit, marker=marker)
  925. def create(self, network, label=None):
  926. AzureRouter.assert_valid_resource_name(label)
  927. parameters = {"location": self.provider.region_name}
  928. if label:
  929. parameters.update(tags={'Label': label})
  930. else:
  931. label = 'cb-router'
  932. router_name = "{0}-{1}".format(label, uuid.uuid4().hex[:6])
  933. route = self.provider.azure_client. \
  934. create_route_table(router_name, parameters)
  935. return AzureRouter(self.provider, route)