services.py 41 KB

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