services.py 39 KB

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