services.py 40 KB

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