resources.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678
  1. """
  2. DataTypes used by this provider
  3. """
  4. import collections
  5. import io
  6. import logging
  7. import paramiko
  8. from cloudbridge.base.resources import (BaseAttachmentInfo, BaseBucket,
  9. BaseBucketObject, BaseDnsRecord,
  10. BaseDnsZone, BaseFloatingIP,
  11. BaseInstance, BaseInternetGateway,
  12. BaseKeyPair, BaseLaunchConfig,
  13. BaseMachineImage, BaseNetwork,
  14. BasePlacementZone, BaseRegion,
  15. BaseRouter, BaseSnapshot, BaseSubnet,
  16. BaseVMFirewall, BaseVMFirewallRule,
  17. BaseVMType, BaseVolume)
  18. from cloudbridge.interfaces import InstanceState, VolumeState
  19. from cloudbridge.interfaces.resources import (Instance, MachineImageState,
  20. NetworkState, RouterState,
  21. SnapshotState, SubnetState,
  22. TrafficDirection)
  23. from azure.common import AzureException
  24. from azure.core.exceptions import ResourceNotFoundError
  25. from azure.mgmt.compute.models import (DataDisk, ManagedDiskParameters,
  26. SubResource as ComputeSubResource)
  27. from azure.mgmt.devtestlabs.models import GalleryImageReference
  28. from azure.mgmt.network.models import NetworkSecurityGroup
  29. from . import helpers as azure_helpers
  30. from .subservices import (AzureBucketObjectSubService,
  31. AzureDnsRecordSubService,
  32. AzureFloatingIPSubService, AzureGatewaySubService,
  33. AzureSubnetSubService, AzureVMFirewallRuleSubService)
  34. log = logging.getLogger(__name__)
  35. class AzureVMFirewall(BaseVMFirewall):
  36. def __init__(self, provider, vm_firewall):
  37. super(AzureVMFirewall, self).__init__(provider, vm_firewall)
  38. self._vm_firewall = vm_firewall
  39. self._vm_firewall.tags = self._vm_firewall.tags or {}
  40. self._rule_container = AzureVMFirewallRuleSubService(provider, self)
  41. @property
  42. def network_id(self):
  43. return self._vm_firewall.tags.get('network_id', None)
  44. @property
  45. def resource_id(self):
  46. return self._vm_firewall.id
  47. @property
  48. def id(self):
  49. return self._vm_firewall.id
  50. @property
  51. def name(self):
  52. return self._vm_firewall.name
  53. @property
  54. def label(self):
  55. return self._vm_firewall.tags.get('Label', None)
  56. @label.setter
  57. def label(self, value):
  58. self.assert_valid_resource_label(value)
  59. self._vm_firewall.tags.update(Label=value or "")
  60. self._provider.azure_client.update_vm_firewall_tags(
  61. self.id, self._vm_firewall.tags)
  62. @property
  63. def description(self):
  64. return self._vm_firewall.tags.get('Description')
  65. @description.setter
  66. def description(self, value):
  67. self._vm_firewall.tags.update(Description=value or "")
  68. self._provider.azure_client.\
  69. update_vm_firewall_tags(self.id,
  70. self._vm_firewall.tags)
  71. @property
  72. def rules(self):
  73. return self._rule_container
  74. def refresh(self):
  75. """
  76. Refreshes the security group with tags if required.
  77. """
  78. try:
  79. self._vm_firewall = self._provider.azure_client. \
  80. get_vm_firewall(self.id)
  81. if not self._vm_firewall.tags:
  82. self._vm_firewall.tags = {}
  83. except (ResourceNotFoundError, ValueError) as cloud_error:
  84. log.exception(cloud_error.message)
  85. # The security group no longer exists and cannot be refreshed.
  86. def to_json(self):
  87. js = super(AzureVMFirewall, self).to_json()
  88. json_rules = [r.to_json() for r in self.rules]
  89. js['rules'] = json_rules
  90. if js.get('network_id'):
  91. js.pop('network_id') # Omit for consistency across cloud providers
  92. return js
  93. # Tuple for port range
  94. PortRange = collections.namedtuple('PortRange', ['from_port', 'to_port'])
  95. class AzureVMFirewallRule(BaseVMFirewallRule):
  96. def __init__(self, parent_fw, rule):
  97. super(AzureVMFirewallRule, self).__init__(parent_fw, rule)
  98. @property
  99. def id(self):
  100. return self._rule.id
  101. @property
  102. def name(self):
  103. return self._rule.name
  104. @property
  105. def direction(self):
  106. return (TrafficDirection.INBOUND if self._rule.direction == "Inbound"
  107. else TrafficDirection.OUTBOUND)
  108. @property
  109. def protocol(self):
  110. return self._rule.protocol
  111. @property
  112. def from_port(self):
  113. return self._port_range_tuple.from_port
  114. @property
  115. def to_port(self):
  116. return self._port_range_tuple.to_port
  117. @property
  118. def _port_range_tuple(self):
  119. if self._rule.destination_port_range == '*':
  120. return PortRange(1, 65535)
  121. destination_port_range = self._rule.destination_port_range
  122. port_range_split = destination_port_range.split('-', 1)
  123. return PortRange(int(port_range_split[0]), int(port_range_split[1]))
  124. @property
  125. def cidr(self):
  126. return self._rule.source_address_prefix
  127. @property
  128. def src_dest_fw_id(self):
  129. return self.firewall.id
  130. @property
  131. def src_dest_fw(self):
  132. return self.firewall
  133. class AzureBucketObject(BaseBucketObject):
  134. def __init__(self, provider, container, blob_properties):
  135. super(AzureBucketObject, self).__init__(provider)
  136. self._container = container
  137. self._blob_properties = blob_properties
  138. @property
  139. def _blob_client(self):
  140. return self._container._bucket.get_blob_client(self.name)
  141. @property
  142. def id(self):
  143. return self._blob_properties.name
  144. @property
  145. def name(self):
  146. return self._blob_properties.name
  147. @property
  148. def size(self):
  149. """
  150. Get this object's size.
  151. """
  152. return self._blob_properties.size
  153. @property
  154. def last_modified(self):
  155. """
  156. Get the date and time this object was last modified.
  157. """
  158. return self._blob_properties.last_modified.strftime("%Y-%m-%dT%H:%M:%S.%f")
  159. def iter_content(self):
  160. """
  161. Returns this object's content as an
  162. iterable stream.
  163. """
  164. def iterable_to_stream(iterable):
  165. class IterStream(io.RawIOBase):
  166. def __init__(self):
  167. self.leftover = None
  168. def readable(self):
  169. return True
  170. def readinto(self, b):
  171. try:
  172. buffer_length = len(b) # We're supposed to return at most this much
  173. chunk = self.leftover or next(iterable)
  174. output, self.leftover = chunk[:buffer_length], chunk[buffer_length:]
  175. b[:len(output)] = output
  176. return len(output)
  177. except StopIteration:
  178. return 0 # indicate EOF
  179. return IterStream()
  180. def blob_iterator():
  181. for chunk in self._blob_client.download_blob().chunks():
  182. yield chunk
  183. return iterable_to_stream(blob_iterator())
  184. @property
  185. def bucket(self):
  186. return self._container
  187. def _upload_single_shot(self, data):
  188. """
  189. Upload the object in a single request. ``data`` may be text, bytes or
  190. a file-like object; the Azure SDK streams file-like data rather than
  191. buffering it all in memory. Larger uploads are handled transparently
  192. by the base class via the multipart path.
  193. """
  194. try:
  195. self._provider.azure_client.upload_blob(
  196. self._container.id, self.id, data)
  197. return True
  198. except AzureException as azureEx:
  199. log.exception(azureEx)
  200. return False
  201. def _upload_multipart(self, stream, config=None):
  202. # The Azure SDK's upload_blob stages blocks concurrently (max_concurrency
  203. # workers) over a thread-safe client, so the transparent multipart path
  204. # delegates to it rather than CloudBridge's generic clone-pool driver.
  205. try:
  206. self._provider.azure_client.upload_blob(
  207. self._container.id, self.id, stream,
  208. max_concurrency=self._multipart_max_concurrency(config))
  209. return True
  210. except AzureException as azureEx:
  211. log.exception(azureEx)
  212. return False
  213. def delete(self):
  214. """
  215. Delete this object.
  216. :rtype: bool
  217. :return: True if successful
  218. """
  219. self._blob_client.delete_blob()
  220. def generate_url(self, expires_in, writable=False):
  221. """
  222. Generate a URL to this object.
  223. """
  224. return self._provider.azure_client.get_blob_url(
  225. self._container, self.name, expires_in, writable)
  226. def refresh(self):
  227. pass
  228. class AzureBucket(BaseBucket):
  229. def __init__(self, provider, bucket):
  230. super(AzureBucket, self).__init__(provider)
  231. self._bucket = bucket
  232. self._object_container = AzureBucketObjectSubService(provider, self)
  233. @property
  234. def id(self):
  235. try:
  236. name = self._bucket.name
  237. except AttributeError:
  238. name = self._bucket.container_name
  239. return name
  240. @property
  241. def name(self):
  242. """
  243. Get this bucket's name.
  244. Due to changes in the Azure API, we can either received a
  245. Container or a ContainerClient, Container has a name, but
  246. the ContainerClient has a container_name
  247. """
  248. try:
  249. name = self._bucket.name
  250. except AttributeError:
  251. name = self._bucket.container_name
  252. return name
  253. def exists(self, name):
  254. """
  255. Determine if an object with given name exists in this bucket.
  256. """
  257. return True if self.get(name) else False
  258. @property
  259. def objects(self):
  260. return self._object_container
  261. class AzureVolume(BaseVolume):
  262. VOLUME_STATE_MAP = {
  263. 'InProgress': VolumeState.CREATING,
  264. 'Creating': VolumeState.CREATING,
  265. 'Unattached': VolumeState.AVAILABLE,
  266. 'Attached': VolumeState.IN_USE,
  267. 'Deleting': VolumeState.CONFIGURING,
  268. 'Updating': VolumeState.CONFIGURING,
  269. 'Deleted': VolumeState.DELETED,
  270. 'Failed': VolumeState.ERROR,
  271. 'Canceled': VolumeState.ERROR
  272. }
  273. def __init__(self, provider, volume):
  274. super(AzureVolume, self).__init__(provider)
  275. self._volume = volume
  276. self._description = None
  277. self._state = 'unknown'
  278. self._update_state()
  279. if not self._volume.tags:
  280. self._volume.tags = {}
  281. def _update_state(self):
  282. if not self._volume.provisioning_state == 'Succeeded':
  283. self._state = self._volume.provisioning_state
  284. elif self._volume.managed_by:
  285. self._state = 'Attached'
  286. else:
  287. self._state = 'Unattached'
  288. @property
  289. def id(self):
  290. return self._volume.id
  291. @property
  292. def resource_id(self):
  293. return self._volume.id
  294. @property
  295. def name(self):
  296. return self._volume.name
  297. @property
  298. def tags(self):
  299. return self._volume.tags
  300. @property
  301. def label(self):
  302. """
  303. Get the volume label.
  304. .. note:: an instance must have a (case sensitive) tag ``Label``
  305. """
  306. return self._volume.tags.get('Label', None)
  307. @label.setter
  308. # pylint:disable=arguments-differ
  309. def label(self, value):
  310. """
  311. Set the volume label.
  312. """
  313. self.assert_valid_resource_label(value)
  314. self._volume.tags.update(Label=value or "")
  315. self._provider.azure_client. \
  316. update_disk_tags(self.id,
  317. self._volume.tags)
  318. @property
  319. def description(self):
  320. return self._volume.tags.get('Description', None)
  321. @description.setter
  322. def description(self, value):
  323. self._volume.tags.update(Description=value or "")
  324. self._provider.azure_client. \
  325. update_disk_tags(self.id,
  326. self._volume.tags)
  327. @property
  328. def size(self):
  329. return self._volume.disk_size_gb
  330. @property
  331. def create_time(self):
  332. return self._volume.time_created.strftime("%Y-%m-%dT%H:%M:%S.%f")
  333. @property
  334. def zone_id(self):
  335. return self._volume.location
  336. @property
  337. def source(self):
  338. return self._volume.creation_data.source_uri
  339. @property
  340. def attachments(self):
  341. """
  342. Azure does not have option to specify the device name
  343. while attaching disk to VM. It is automatically populated
  344. and is not returned. As a result this method ignores
  345. the device name parameter and passes None
  346. to the BaseAttachmentInfo
  347. :return:
  348. """
  349. if self._volume.managed_by:
  350. return BaseAttachmentInfo(self, self._volume.managed_by, None)
  351. else:
  352. return None
  353. def attach(self, instance, device=None):
  354. """
  355. Attach this volume to an instance.
  356. """
  357. instance_id = instance.id if isinstance(
  358. instance,
  359. Instance) else instance
  360. vm = self._provider.azure_client.get_vm(instance_id)
  361. vm.storage_profile.data_disks.append(DataDisk(
  362. lun=len(vm.storage_profile.data_disks),
  363. name=self._volume.name,
  364. create_option='attach',
  365. managed_disk=ManagedDiskParameters(id=self.resource_id)
  366. ))
  367. self._provider.azure_client.update_vm(instance_id, vm)
  368. def detach(self, force=False):
  369. """
  370. Detach this volume from an instance.
  371. """
  372. for vm in self._provider.azure_client.list_vm():
  373. for item in vm.storage_profile.data_disks:
  374. if item.managed_disk and \
  375. item.managed_disk.id == self.resource_id:
  376. vm.storage_profile.data_disks.remove(item)
  377. self._provider.azure_client.update_vm(vm.id, vm)
  378. def create_snapshot(self, label, description=None):
  379. """
  380. Create a snapshot of this Volume.
  381. """
  382. return self._provider.storage.snapshots.create(label, self,
  383. description)
  384. @property
  385. def state(self):
  386. return AzureVolume.VOLUME_STATE_MAP.get(
  387. self._state, VolumeState.UNKNOWN)
  388. def refresh(self):
  389. """
  390. Refreshes the state of this volume by re-querying the cloud provider
  391. for its latest state.
  392. """
  393. try:
  394. self._volume = self._provider.azure_client. \
  395. get_disk(self.id)
  396. self._update_state()
  397. except (ResourceNotFoundError, ValueError) as cloud_error:
  398. log.exception(cloud_error.message)
  399. # The volume no longer exists and cannot be refreshed.
  400. # set the state to unknown
  401. self._state = 'unknown'
  402. class AzureSnapshot(BaseSnapshot):
  403. SNAPSHOT_STATE_MAP = {
  404. 'InProgress': SnapshotState.PENDING,
  405. 'Succeeded': SnapshotState.AVAILABLE,
  406. 'Failed': SnapshotState.ERROR,
  407. 'Canceled': SnapshotState.ERROR,
  408. 'Updating': SnapshotState.CONFIGURING,
  409. 'Deleting': SnapshotState.CONFIGURING,
  410. 'Deleted': SnapshotState.UNKNOWN
  411. }
  412. def __init__(self, provider, snapshot):
  413. super(AzureSnapshot, self).__init__(provider)
  414. self._snapshot = snapshot
  415. self._description = None
  416. self._state = self._snapshot.provisioning_state
  417. if not self._snapshot.tags:
  418. self._snapshot.tags = {}
  419. @property
  420. def id(self):
  421. return self._snapshot.id
  422. @property
  423. def name(self):
  424. return self._snapshot.name
  425. @property
  426. def resource_id(self):
  427. return self._snapshot.id
  428. @property
  429. def label(self):
  430. """
  431. Get the snapshot label.
  432. .. note:: an instance must have a (case sensitive) tag ``Label``
  433. """
  434. return self._snapshot.tags.get('Label', None)
  435. @label.setter
  436. # pylint:disable=arguments-differ
  437. def label(self, value):
  438. """
  439. Set the snapshot label.
  440. """
  441. self.assert_valid_resource_label(value)
  442. self._snapshot.tags.update(Label=value or "")
  443. self._provider.azure_client. \
  444. update_snapshot_tags(self.id,
  445. self._snapshot.tags)
  446. @property
  447. def description(self):
  448. return self._snapshot.tags.get('Description', None)
  449. @description.setter
  450. def description(self, value):
  451. self._snapshot.tags.update(Description=value or "")
  452. self._provider.azure_client. \
  453. update_snapshot_tags(self.id,
  454. self._snapshot.tags)
  455. @property
  456. def size(self):
  457. return self._snapshot.disk_size_gb
  458. @property
  459. def volume_id(self):
  460. return self._snapshot.creation_data.source_resource_id
  461. @property
  462. def create_time(self):
  463. return self._snapshot.time_created.strftime("%Y-%m-%dT%H:%M:%S.%f")
  464. @property
  465. def state(self):
  466. return AzureSnapshot.SNAPSHOT_STATE_MAP.get(
  467. self._state, SnapshotState.UNKNOWN)
  468. def refresh(self):
  469. """
  470. Refreshes the state of this snapshot by re-querying the cloud provider
  471. for its latest state.
  472. """
  473. try:
  474. self._snapshot = self._provider.azure_client. \
  475. get_snapshot(self.id)
  476. self._state = self._snapshot.provisioning_state
  477. except (ResourceNotFoundError, ValueError) as cloud_error:
  478. log.exception(cloud_error.message)
  479. # The snapshot no longer exists and cannot be refreshed.
  480. # set the state to unknown
  481. self._state = 'unknown'
  482. def create_volume(self, size=None, volume_type=None, iops=None):
  483. """
  484. Create a new Volume from this Snapshot.
  485. """
  486. return self._provider.storage.volumes. \
  487. create(self.name, self.size, snapshot=self)
  488. class AzureMachineImage(BaseMachineImage):
  489. IMAGE_STATE_MAP = {
  490. 'InProgress': MachineImageState.PENDING,
  491. 'Succeeded': MachineImageState.AVAILABLE,
  492. 'Failed': MachineImageState.ERROR
  493. }
  494. def __init__(self, provider, image):
  495. super(AzureMachineImage, self).__init__(provider)
  496. # Image can be either a dict for public image reference
  497. # or the Azure iamge object
  498. self._image = image
  499. if isinstance(self._image, GalleryImageReference):
  500. self._state = 'Succeeded'
  501. else:
  502. self._state = self._image.provisioning_state
  503. if not self._image.tags:
  504. self._image.tags = {}
  505. @property
  506. def id(self):
  507. """
  508. Get the image identifier.
  509. :rtype: ``str``
  510. :return: ID for this instance as returned by the cloud middleware.
  511. """
  512. if self.is_gallery_image:
  513. return azure_helpers.generate_urn(self._image)
  514. else:
  515. return azure_helpers.normalize_rg_case(self._image.id)
  516. @property
  517. def name(self):
  518. if self.is_gallery_image:
  519. return azure_helpers.generate_urn(self._image)
  520. else:
  521. return self._image.name
  522. @property
  523. def resource_id(self):
  524. if self.is_gallery_image:
  525. return azure_helpers.generate_urn(self._image)
  526. else:
  527. return self._image.id
  528. @property
  529. def label(self):
  530. if self.is_gallery_image:
  531. return azure_helpers.generate_urn(self._image)
  532. else:
  533. return self._image.tags.get('Label', None)
  534. @label.setter
  535. def label(self, value):
  536. """
  537. Set the image label when it is a private image.
  538. """
  539. if not self.is_gallery_image:
  540. self.assert_valid_resource_label(value)
  541. self._image.tags.update(Label=value or "")
  542. self._provider.azure_client. \
  543. update_image_tags(self.id, self._image.tags)
  544. @property
  545. def description(self):
  546. """
  547. Get the image description.
  548. :rtype: ``str``
  549. :return: Description for this image as returned by the cloud middleware
  550. """
  551. if self.is_gallery_image:
  552. return 'Public gallery image from the Azure Marketplace: '\
  553. + self.name
  554. else:
  555. return self._image.tags.get('Description', None)
  556. @description.setter
  557. def description(self, value):
  558. """
  559. Set the image description.
  560. """
  561. if not self.is_gallery_image:
  562. self._image.tags.update(Description=value or "")
  563. self._provider.azure_client. \
  564. update_image_tags(self.id, self._image.tags)
  565. @property
  566. def min_disk(self):
  567. """
  568. Returns the minimum size of the disk that's required to
  569. boot this image (in GB).
  570. This value is not retuned in azure api
  571. as this is a limitation with Azure Compute API
  572. :rtype: ``int``
  573. :return: The minimum disk size needed by this image
  574. """
  575. if self.is_gallery_image:
  576. return 0
  577. else:
  578. return self._image.storage_profile.os_disk.disk_size_gb or 0
  579. def delete(self):
  580. """
  581. Delete this image
  582. """
  583. if not self.is_gallery_image:
  584. self._provider.azure_client.delete_image(self.id)
  585. @property
  586. def state(self):
  587. if self.is_gallery_image:
  588. return MachineImageState.AVAILABLE
  589. else:
  590. return AzureMachineImage.IMAGE_STATE_MAP.get(
  591. self._state, MachineImageState.UNKNOWN)
  592. @property
  593. def is_gallery_image(self):
  594. """
  595. Returns true if the image is a public reference and false if it
  596. is a private image in the resource group.
  597. """
  598. return isinstance(self._image, GalleryImageReference)
  599. def refresh(self):
  600. """
  601. Refreshes the state of this instance by re-querying the cloud provider
  602. for its latest state.
  603. """
  604. if not self.is_gallery_image:
  605. try:
  606. self._image = self._provider.azure_client.get_image(self.id)
  607. self._state = self._image.provisioning_state
  608. except ResourceNotFoundError as cloud_error:
  609. log.exception(cloud_error.message)
  610. # image no longer exists
  611. self._state = "unknown"
  612. class AzureNetwork(BaseNetwork):
  613. NETWORK_STATE_MAP = {
  614. 'InProgress': NetworkState.PENDING,
  615. 'Succeeded': NetworkState.AVAILABLE,
  616. }
  617. def __init__(self, provider, network):
  618. super(AzureNetwork, self).__init__(provider)
  619. self._network = network
  620. self._state = self._network.provisioning_state
  621. if not self._network.tags:
  622. self._network.tags = {}
  623. self._gateway_service = AzureGatewaySubService(provider, self)
  624. self._subnet_svc = AzureSubnetSubService(provider, self)
  625. @property
  626. def id(self):
  627. return self._network.id
  628. @property
  629. def name(self):
  630. return self._network.name
  631. @property
  632. def resource_id(self):
  633. return self._network.id
  634. @property
  635. def label(self):
  636. """
  637. Get the network label.
  638. .. note:: the network must have a (case sensitive) tag ``Label``
  639. """
  640. return self._network.tags.get('Label', None)
  641. @label.setter
  642. # pylint:disable=arguments-differ
  643. def label(self, value):
  644. """
  645. Set the network label.
  646. """
  647. self.assert_valid_resource_label(value)
  648. self._network.tags.update(Label=value or "")
  649. self._provider.azure_client. \
  650. update_network_tags(self.id, self._network.tags)
  651. @property
  652. def external(self):
  653. """
  654. For Azure, all VPC networks can be connected to the Internet so always
  655. return ``True``.
  656. """
  657. return True
  658. @property
  659. def state(self):
  660. return AzureNetwork.NETWORK_STATE_MAP.get(
  661. self._state, NetworkState.UNKNOWN)
  662. def refresh(self):
  663. """
  664. Refreshes the state of this network by re-querying the cloud provider
  665. for its latest state.
  666. """
  667. try:
  668. self._network = self._provider.azure_client.\
  669. get_network(self.id)
  670. self._state = self._network.provisioning_state
  671. except (ResourceNotFoundError, ValueError) as cloud_error:
  672. log.exception(cloud_error.message)
  673. # The network no longer exists and cannot be refreshed.
  674. # set the state to unknown
  675. self._state = 'unknown'
  676. @property
  677. def cidr_block(self):
  678. """
  679. Address space associated with this network
  680. :return:
  681. """
  682. return self._network.address_space.address_prefixes[0]
  683. def delete(self):
  684. """
  685. Delete an existing network.
  686. """
  687. self._provider.azure_client.delete_network(self.id)
  688. @property
  689. def subnets(self):
  690. return self._subnet_svc
  691. @property
  692. def gateways(self):
  693. return self._gateway_service
  694. class AzureFloatingIP(BaseFloatingIP):
  695. def __init__(self, provider, floating_ip):
  696. super(AzureFloatingIP, self).__init__(provider)
  697. self._ip = floating_ip
  698. @property
  699. def id(self):
  700. return self._ip.id
  701. @property
  702. def name(self):
  703. return self._ip.ip_address
  704. @property
  705. def resource_id(self):
  706. return self._ip.id
  707. @property
  708. def public_ip(self):
  709. return self._ip.ip_address
  710. @property
  711. def private_ip(self):
  712. return self._ip.ip_configuration.private_ip_address \
  713. if self._ip.ip_configuration else None
  714. @property
  715. def in_use(self):
  716. return True if self._ip.ip_configuration else False
  717. def refresh(self):
  718. # Gateway is not needed as it doesn't exist in Azure, so just
  719. # getting the Floating IP again from the client
  720. # pylint:disable=protected-access
  721. fip = self._provider.networking._floating_ips.get(None, self.id)
  722. # pylint:disable=protected-access
  723. self._ip = fip._ip
  724. class AzureRegion(BaseRegion):
  725. def __init__(self, provider, azure_region):
  726. super(AzureRegion, self).__init__(provider)
  727. self._azure_region = azure_region
  728. @property
  729. def id(self):
  730. return self._azure_region.name
  731. @property
  732. def name(self):
  733. return self._azure_region.name
  734. @property
  735. def zones(self):
  736. """
  737. Access information about placement zones within this region.
  738. As Azure does not have this feature, mapping the region
  739. name as zone id and name.
  740. """
  741. return [AzurePlacementZone(self._provider,
  742. self._azure_region.name,
  743. self._azure_region.name)]
  744. class AzurePlacementZone(BasePlacementZone):
  745. """
  746. As Azure does not provide zones (limited support), we are mapping the
  747. region information in the zones.
  748. """
  749. def __init__(self, provider, zone, region):
  750. super(AzurePlacementZone, self).__init__(provider)
  751. self._azure_zone = zone
  752. self._azure_region = region
  753. @property
  754. def id(self):
  755. """
  756. Get the zone id
  757. :rtype: ``str``
  758. :return: ID for this zone as returned by the cloud middleware.
  759. """
  760. return self._azure_zone
  761. @property
  762. def name(self):
  763. """
  764. Get the zone name.
  765. :rtype: ``str``
  766. :return: Name for this zone as returned by the cloud middleware.
  767. """
  768. return self._azure_region
  769. @property
  770. def region_name(self):
  771. """
  772. Get the region that this zone belongs to.
  773. :rtype: ``str``
  774. :return: Name of this zone's region as returned by the
  775. cloud middleware
  776. """
  777. return self._azure_region
  778. class AzureSubnet(BaseSubnet):
  779. _SUBNET_STATE_MAP = {
  780. 'InProgress': SubnetState.PENDING,
  781. 'Succeeded': SubnetState.AVAILABLE,
  782. }
  783. def __init__(self, provider, subnet):
  784. super(AzureSubnet, self).__init__(provider)
  785. self._subnet = subnet
  786. self._state = self._subnet.provisioning_state
  787. self._tag_name = None
  788. @property
  789. def id(self):
  790. return self._subnet.id
  791. @property
  792. def name(self):
  793. net_name = self.network_id.split('/')[-1]
  794. sn_name = self._subnet.name
  795. return '{0}/{1}'.format(net_name, sn_name)
  796. @property
  797. def label(self):
  798. # Although Subnet doesn't support labels, we use the parent Network's
  799. # tags to track the subnet's labels
  800. network = self.network
  801. # pylint:disable=protected-access
  802. az_network = network._network
  803. return az_network.tags.get(self.tag_name, None)
  804. @label.setter
  805. # pylint:disable=arguments-differ
  806. def label(self, value):
  807. self.assert_valid_resource_label(value)
  808. network = self.network
  809. # pylint:disable=protected-access
  810. az_network = network._network
  811. kwargs = {self.tag_name: value or ""}
  812. az_network.tags.update(**kwargs)
  813. self._provider.azure_client.update_network_tags(
  814. az_network.id, az_network.tags)
  815. @property
  816. def tag_name(self):
  817. if not self._tag_name:
  818. self._tag_name = 'SubnetLabel_{0}'.format(self._subnet.name)
  819. return self._tag_name
  820. @property
  821. def resource_id(self):
  822. return self._subnet.id
  823. @property
  824. def zone(self):
  825. # pylint:disable=protected-access
  826. region = self._provider.compute.regions.get(
  827. self.network._network.location)
  828. return region.zones[0]
  829. @property
  830. def cidr_block(self):
  831. return self._subnet.address_prefix
  832. @property
  833. def network_id(self):
  834. return self._provider.azure_client.get_network_id_for_subnet(self.id)
  835. @property
  836. def state(self):
  837. return self._SUBNET_STATE_MAP.get(self._state, NetworkState.UNKNOWN)
  838. def refresh(self):
  839. """
  840. Refreshes the state of this network by re-querying the cloud provider
  841. for its latest state.
  842. """
  843. try:
  844. self._subnet = self._provider.azure_client. \
  845. get_subnet(self.id)
  846. self._state = self._subnet.provisioning_state
  847. except (ResourceNotFoundError, ValueError) as cloud_error:
  848. log.exception(cloud_error.message)
  849. # The subnet no longer exists and cannot be refreshed.
  850. # set the state to unknown
  851. self._state = 'unknown'
  852. class AzureInstance(BaseInstance):
  853. INSTANCE_STATE_MAP = {
  854. 'InProgress': InstanceState.PENDING,
  855. 'Creating': InstanceState.PENDING,
  856. 'VM running': InstanceState.RUNNING,
  857. 'Updating': InstanceState.CONFIGURING,
  858. 'Deleted': InstanceState.DELETED,
  859. 'Stopping': InstanceState.CONFIGURING,
  860. 'Deleting': InstanceState.CONFIGURING,
  861. 'Stopped': InstanceState.STOPPED,
  862. 'Canceled': InstanceState.ERROR,
  863. 'Failed': InstanceState.ERROR,
  864. 'VM stopped': InstanceState.STOPPED,
  865. 'VM deallocated': InstanceState.STOPPED,
  866. 'VM deallocating': InstanceState.CONFIGURING,
  867. 'VM stopping': InstanceState.CONFIGURING,
  868. 'VM starting': InstanceState.CONFIGURING
  869. }
  870. def __init__(self, provider, vm_instance):
  871. super(AzureInstance, self).__init__(provider)
  872. self._vm = vm_instance
  873. self._update_state()
  874. if not self._vm.tags:
  875. self._vm.tags = {}
  876. @property
  877. def _nic_ids(self):
  878. return (nic.id for nic in self._vm.network_profile.network_interfaces)
  879. @property
  880. def _nics(self):
  881. return (self._provider.azure_client.get_nic(nic_id)
  882. for nic_id in self._nic_ids)
  883. @property
  884. def _public_ip_ids(self):
  885. return (ip_config.public_ip_address.id
  886. for nic in self._nics
  887. for ip_config in nic.ip_configurations
  888. if nic.ip_configurations and ip_config.public_ip_address)
  889. @property
  890. def id(self):
  891. """
  892. Get the instance identifier.
  893. """
  894. return self._vm.id
  895. @property
  896. def name(self):
  897. """
  898. Get the instance name.
  899. """
  900. return self._vm.name
  901. @property
  902. def resource_id(self):
  903. return self._vm.id
  904. @property
  905. def label(self):
  906. """
  907. Get the instance label.
  908. .. note:: an instance must have a (case sensitive) tag ``Label``
  909. """
  910. return self._vm.tags.get('Label', None)
  911. @label.setter
  912. # pylint:disable=arguments-differ
  913. def label(self, value):
  914. """
  915. Set the instance label.
  916. """
  917. self.assert_valid_resource_label(value)
  918. self._vm.tags.update(Label=value or "")
  919. self._provider.azure_client. \
  920. update_vm_tags(self.id, self._vm.tags)
  921. @property
  922. def public_ips(self):
  923. """
  924. Get all the public IP addresses for this instance.
  925. """
  926. return [self._provider.azure_client.get_floating_ip(pip).ip_address
  927. for pip in self._public_ip_ids]
  928. @property
  929. def private_ips(self):
  930. """
  931. Get all the private IP addresses for this instance.
  932. """
  933. return [ip_config.private_ip_address
  934. for nic in self._nics
  935. for ip_config in nic.ip_configurations
  936. if nic.ip_configurations and ip_config.private_ip_address]
  937. @property
  938. def vm_type_id(self):
  939. """
  940. Get the instance type name.
  941. """
  942. return self._vm.hardware_profile.vm_size
  943. @property
  944. def vm_type(self):
  945. """
  946. Get the instance type.
  947. """
  948. return self._provider.compute.vm_types.find(
  949. name=self.vm_type_id)[0]
  950. @property
  951. def create_time(self):
  952. """
  953. Get the instance creation time
  954. """
  955. return self._vm.time_created
  956. def reboot(self):
  957. """
  958. Reboot this instance (using the cloud middleware API).
  959. """
  960. self._provider.azure_client.restart_vm(self.id)
  961. def stop(self):
  962. """
  963. Stop this instance (using the cloud middleware API).
  964. """
  965. self._provider.azure_client.stop_vm(self.id)
  966. @property
  967. def image_id(self):
  968. """
  969. Get the image ID for this instance.
  970. """
  971. # Not tested for resource group images
  972. reference_dict = self._vm.storage_profile.image_reference.as_dict()
  973. if reference_dict.get('publisher'):
  974. return ':'.join([reference_dict['publisher'],
  975. reference_dict['offer'],
  976. reference_dict['sku'],
  977. reference_dict['version']])
  978. else:
  979. return reference_dict['id']
  980. @property
  981. def zone_id(self):
  982. """
  983. Get the placement zone id where this instance is running.
  984. """
  985. return self._vm.location
  986. @property
  987. def subnet_id(self):
  988. """
  989. Return the first subnet id associated with the first network iface.
  990. An Azure instance can have multiple network interfaces attached with
  991. each interface having at most one subnet. This method will return only
  992. the subnet of the first attached network interface.
  993. """
  994. for nic_id in self._nic_ids:
  995. nic = self._provider.azure_client.get_nic(nic_id)
  996. for ipc in nic.ip_configurations:
  997. return ipc.subnet.id
  998. @property
  999. def vm_firewalls(self):
  1000. return [self._provider.security.vm_firewalls.get(group_id)
  1001. for group_id in self.vm_firewall_ids]
  1002. @property
  1003. def vm_firewall_ids(self):
  1004. return [nic.network_security_group.id
  1005. for nic in self._nics
  1006. if nic.network_security_group]
  1007. @property
  1008. def key_pair_id(self):
  1009. """
  1010. Get the name of the key pair associated with this instance.
  1011. """
  1012. return self._vm.tags.get('Key_Pair')
  1013. def create_image(self, label, private_key_path=None):
  1014. """
  1015. Create a new image based on this instance. Documentation for create
  1016. image available at https://docs.microsoft.com/en-us/azure/virtual-ma
  1017. chines/linux/capture-image. In azure, we need to deprovision the VM
  1018. before capturing.
  1019. To deprovision, login to the VM and execute the `waagent deprovision`
  1020. command. To do this programmatically, use paramiko to ssh into the VM
  1021. and executing deprovision command. To SSH into the VM programmatically
  1022. however, we need to pass private key file path, so we have modified the
  1023. CloudBridge interface to pass the private key file path
  1024. """
  1025. self.assert_valid_resource_label(label)
  1026. name = self._generate_name_from_label(label, 'cb-img')
  1027. if not self._state == 'VM generalized':
  1028. if not self._state == 'VM running':
  1029. self._provider.azure_client.start_vm(self.id)
  1030. # if private_key_path:
  1031. self._deprovision(private_key_path)
  1032. self._provider.azure_client.deallocate_vm(self.id)
  1033. self._provider.azure_client.generalize_vm(self.id)
  1034. create_params = {
  1035. 'location': self._provider.region_name,
  1036. 'source_virtual_machine': ComputeSubResource(id=self.resource_id),
  1037. 'tags': {'Label': label}
  1038. }
  1039. image = self._provider.azure_client.create_image(name,
  1040. create_params)
  1041. return AzureMachineImage(self._provider, image)
  1042. def _deprovision(self, private_key_path):
  1043. if not private_key_path:
  1044. return
  1045. client = paramiko.SSHClient()
  1046. client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  1047. try:
  1048. client.connect(
  1049. hostname=self.public_ips[0],
  1050. username=self._provider.vm_default_user_name,
  1051. key_filename=private_key_path)
  1052. client.exec_command('sudo waagent -deprovision -force')
  1053. finally:
  1054. client.close()
  1055. def add_floating_ip(self, floating_ip):
  1056. """
  1057. Attaches public ip to the instance.
  1058. """
  1059. floating_ip_id = floating_ip.id if isinstance(
  1060. floating_ip, AzureFloatingIP) else floating_ip
  1061. nic = next(self._nics)
  1062. nic.ip_configurations[0].public_ip_address = {
  1063. 'id': floating_ip_id
  1064. }
  1065. self._provider.azure_client.update_nic(nic.id, nic)
  1066. def remove_floating_ip(self, floating_ip):
  1067. """
  1068. Remove a public IP address from this instance.
  1069. """
  1070. floating_ip_id = floating_ip.id if isinstance(
  1071. floating_ip, AzureFloatingIP) else floating_ip
  1072. nic = next(self._nics)
  1073. for ip_config in nic.ip_configurations:
  1074. if ip_config.public_ip_address.id == floating_ip_id:
  1075. nic.ip_configurations[0].public_ip_address = None
  1076. self._provider.azure_client.update_nic(nic.id, nic)
  1077. def add_vm_firewall(self, fw):
  1078. '''
  1079. :param fw:
  1080. :return: None
  1081. This method adds the security group to VM instance.
  1082. In Azure, security group added to Network interface.
  1083. Azure supports to add only one security group to
  1084. network interface, we are adding the provided security group
  1085. if not associated any security group to NIC
  1086. else replacing the existing security group.
  1087. '''
  1088. fw = (self._provider.security.vm_firewalls.get(fw)
  1089. if isinstance(fw, str) else fw)
  1090. nic = next(self._nics)
  1091. if not nic.network_security_group:
  1092. nic.network_security_group = NetworkSecurityGroup()
  1093. nic.network_security_group.id = fw.resource_id
  1094. else:
  1095. existing_fw = self._provider.security.\
  1096. vm_firewalls.get(nic.network_security_group.id)
  1097. new_fw = self._provider.security.vm_firewalls.\
  1098. create('{0}-{1}'.format(fw.name, existing_fw.name),
  1099. 'Merged security groups {0} and {1}'.
  1100. format(fw.name, existing_fw.name))
  1101. new_fw.add_rule(src_dest_fw=fw)
  1102. new_fw.add_rule(src_dest_fw=existing_fw)
  1103. nic.network_security_group.id = new_fw.resource_id
  1104. self._provider.azure_client.update_nic(nic.id, nic)
  1105. def remove_vm_firewall(self, fw):
  1106. '''
  1107. :param fw:
  1108. :return: None
  1109. This method removes the security group to VM instance.
  1110. In Azure, security group added to Network interface.
  1111. Azure supports to add only one security group to
  1112. network interface, we are removing the provided security group
  1113. if it associated to NIC
  1114. else we are ignoring.
  1115. '''
  1116. nic = next(self._nics)
  1117. fw = (self._provider.security.vm_firewalls.get(fw)
  1118. if isinstance(fw, str) else fw)
  1119. if nic.network_security_group and \
  1120. nic.network_security_group.id == fw.resource_id:
  1121. nic.network_security_group = None
  1122. self._provider.azure_client.update_nic(nic.id, nic)
  1123. def _update_state(self):
  1124. """
  1125. Azure python sdk list operation does not return the current
  1126. staus of the instance. We have to explicity call the get method
  1127. for each instance to get the instance status(instance_view).
  1128. This is the limitation with azure rest api
  1129. :return:
  1130. """
  1131. if not self._vm.instance_view:
  1132. self.refresh()
  1133. if self._vm.instance_view and len(
  1134. self._vm.instance_view.statuses) > 1:
  1135. self._state = \
  1136. self._vm.instance_view.statuses[1].display_status
  1137. else:
  1138. self._state = \
  1139. self._vm.provisioning_state
  1140. @property
  1141. def state(self):
  1142. return AzureInstance.INSTANCE_STATE_MAP.get(
  1143. self._state, InstanceState.UNKNOWN)
  1144. def refresh(self):
  1145. """
  1146. Refreshes the state of this instance by re-querying the cloud provider
  1147. for its latest state.
  1148. """
  1149. try:
  1150. self._vm = self._provider.azure_client.get_vm(self.id)
  1151. if not self._vm.tags:
  1152. self._vm.tags = {}
  1153. self._update_state()
  1154. except (ResourceNotFoundError, ValueError) as cloud_error:
  1155. log.exception(cloud_error.message)
  1156. # The volume no longer exists and cannot be refreshed.
  1157. # set the state to unknown
  1158. self._state = 'unknown'
  1159. class AzureLaunchConfig(BaseLaunchConfig):
  1160. def __init__(self, provider):
  1161. super(AzureLaunchConfig, self).__init__(provider)
  1162. class AzureVMType(BaseVMType):
  1163. def __init__(self, provider, vm_type):
  1164. super(AzureVMType, self).__init__(provider)
  1165. self._vm_type = vm_type
  1166. @property
  1167. def id(self):
  1168. return self._vm_type.name
  1169. @property
  1170. def name(self):
  1171. return self._vm_type.name
  1172. @property
  1173. def family(self):
  1174. """
  1175. Python sdk does not return family details.
  1176. So, as of now populating it with 'Unknown'
  1177. """
  1178. return "Unknown"
  1179. @property
  1180. def vcpus(self):
  1181. return self._vm_type.number_of_cores
  1182. @property
  1183. def ram(self):
  1184. return int(self._vm_type.memory_in_mb) / 1024
  1185. @property
  1186. def size_root_disk(self):
  1187. return self._vm_type.os_disk_size_in_mb / 1024
  1188. @property
  1189. def size_ephemeral_disks(self):
  1190. return self._vm_type.resource_disk_size_in_mb / 1024
  1191. @property
  1192. def num_ephemeral_disks(self):
  1193. """
  1194. Azure by default adds one ephemeral disk. We can not add
  1195. more ephemeral disks to VM explicitly
  1196. So, returning it as Zero.
  1197. """
  1198. return 0
  1199. @property
  1200. def extra_data(self):
  1201. return {
  1202. 'max_data_disk_count':
  1203. self._vm_type.max_data_disk_count
  1204. }
  1205. class AzureKeyPair(BaseKeyPair):
  1206. def __init__(self, provider, key_pair):
  1207. super(AzureKeyPair, self).__init__(provider, key_pair)
  1208. @property
  1209. def id(self):
  1210. return self._key_pair['Name']
  1211. @property
  1212. def name(self):
  1213. return self._key_pair['Name']
  1214. class AzureRouter(BaseRouter):
  1215. def __init__(self, provider, route_table):
  1216. super(AzureRouter, self).__init__(provider)
  1217. self._route_table = route_table
  1218. if not self._route_table.tags:
  1219. self._route_table.tags = {}
  1220. @property
  1221. def id(self):
  1222. return self._route_table.id
  1223. @property
  1224. def name(self):
  1225. return self._route_table.name
  1226. @property
  1227. def resource_id(self):
  1228. return self._route_table.id
  1229. @property
  1230. def label(self):
  1231. """
  1232. Get the router label.
  1233. .. note:: the router must have a (case sensitive) tag ``Label``
  1234. """
  1235. return self._route_table.tags.get('Label', None)
  1236. @label.setter
  1237. # pylint:disable=arguments-differ
  1238. def label(self, value):
  1239. """
  1240. Set the router label.
  1241. """
  1242. self.assert_valid_resource_label(value)
  1243. self._route_table.tags.update(Label=value or "")
  1244. self._provider.azure_client. \
  1245. update_route_table_tags(self._route_table.name,
  1246. self._route_table.tags)
  1247. def refresh(self):
  1248. self._route_table = self._provider.azure_client. \
  1249. get_route_table(self._route_table.name)
  1250. @property
  1251. def state(self):
  1252. self.refresh() # Explicitly refresh the local object
  1253. if self._route_table.subnets:
  1254. return RouterState.ATTACHED
  1255. return RouterState.DETACHED
  1256. @property
  1257. def network_id(self):
  1258. return None
  1259. def attach_subnet(self, subnet):
  1260. self._provider.azure_client. \
  1261. attach_subnet_to_route_table(subnet.id,
  1262. self.resource_id)
  1263. self.refresh()
  1264. @property
  1265. def subnets(self):
  1266. if self._route_table.subnets:
  1267. return [AzureSubnet(self._provider, sn)
  1268. for sn in self._route_table.subnets]
  1269. return []
  1270. def detach_subnet(self, subnet):
  1271. self._provider.azure_client. \
  1272. detach_subnet_to_route_table(subnet.id,
  1273. self.resource_id)
  1274. self.refresh()
  1275. def attach_gateway(self, gateway):
  1276. pass
  1277. def detach_gateway(self, gateway):
  1278. pass
  1279. class AzureInternetGateway(BaseInternetGateway):
  1280. def __init__(self, provider, gateway, gateway_net):
  1281. super(AzureInternetGateway, self).__init__(provider)
  1282. self._gateway = gateway
  1283. self._network_id = gateway_net.id if isinstance(
  1284. gateway_net, AzureNetwork) else gateway_net
  1285. self._state = ''
  1286. self._fips_container = AzureFloatingIPSubService(provider, self)
  1287. @property
  1288. def id(self):
  1289. return "cb-gateway-wrapper"
  1290. @property
  1291. def name(self):
  1292. return "cb-gateway-wrapper"
  1293. def refresh(self):
  1294. pass
  1295. @property
  1296. def state(self):
  1297. return self._state
  1298. @property
  1299. def network_id(self):
  1300. return self._network_id
  1301. def delete(self):
  1302. pass
  1303. @property
  1304. def floating_ips(self):
  1305. return self._fips_container
  1306. # Map Azure record-set type suffix (e.g. 'Microsoft.Network/dnszones/A')
  1307. # to cloudbridge DnsRecordType. Used to expose record data in a normalized form.
  1308. _AZURE_RECORD_TYPE_ATTR = {
  1309. 'A': 'a_records',
  1310. 'AAAA': 'aaaa_records',
  1311. 'CNAME': 'cname_record',
  1312. 'MX': 'mx_records',
  1313. 'NS': 'ns_records',
  1314. 'PTR': 'ptr_records',
  1315. 'SRV': 'srv_records',
  1316. 'TXT': 'txt_records',
  1317. }
  1318. def _azure_record_type(raw_record):
  1319. """Return the bare type (e.g. 'A') from an Azure RecordSet."""
  1320. rec_type = raw_record.type or ''
  1321. # Azure formats: 'Microsoft.Network/dnszones/A' or bare 'A'
  1322. return rec_type.split('/')[-1] if '/' in rec_type else rec_type
  1323. def _azure_record_data(raw_record):
  1324. """Extract the data values from an Azure RecordSet as a list of strings."""
  1325. rt = _azure_record_type(raw_record)
  1326. attr = _AZURE_RECORD_TYPE_ATTR.get(rt)
  1327. if not attr:
  1328. return []
  1329. value = getattr(raw_record, attr, None)
  1330. if value is None:
  1331. return []
  1332. if rt == 'A':
  1333. return [r.ipv4_address for r in value]
  1334. if rt == 'AAAA':
  1335. return [r.ipv6_address for r in value]
  1336. if rt == 'CNAME':
  1337. return [value.cname]
  1338. if rt == 'MX':
  1339. return ['{0} {1}'.format(r.preference, r.exchange) for r in value]
  1340. if rt == 'NS':
  1341. return [r.nsdname for r in value]
  1342. if rt == 'PTR':
  1343. return [r.ptrdname for r in value]
  1344. if rt == 'SRV':
  1345. return ['{0} {1} {2} {3}'.format(r.priority, r.weight, r.port,
  1346. r.target) for r in value]
  1347. if rt == 'TXT':
  1348. # Each TXT record carries a list of strings; join with spaces by convention
  1349. return [' '.join(r.value) if isinstance(r.value, list) else r.value
  1350. for r in value]
  1351. return []
  1352. class AzureDnsZone(BaseDnsZone):
  1353. def __init__(self, provider, dns_zone):
  1354. super(AzureDnsZone, self).__init__(provider)
  1355. self._dns_zone = dns_zone
  1356. self._dns_record_container = AzureDnsRecordSubService(provider, self)
  1357. @property
  1358. def id(self):
  1359. return self._dns_zone.name
  1360. @property
  1361. def name(self):
  1362. return self._dns_zone.name
  1363. @property
  1364. def admin_email(self):
  1365. tags = self._dns_zone.tags or {}
  1366. return tags.get('admin_email')
  1367. @property
  1368. def records(self):
  1369. return self._dns_record_container
  1370. class AzureDnsRecord(BaseDnsRecord):
  1371. def __init__(self, provider, dns_zone, dns_record):
  1372. super(AzureDnsRecord, self).__init__(provider)
  1373. self._dns_zone = dns_zone
  1374. self._dns_rec = dns_record
  1375. @property
  1376. def id(self):
  1377. return self.name + ":" + self.type
  1378. @property
  1379. def name(self):
  1380. return self._dns_rec.name
  1381. @property
  1382. def zone_id(self):
  1383. return self._dns_zone.id
  1384. @property
  1385. def type(self):
  1386. return _azure_record_type(self._dns_rec)
  1387. @property
  1388. def data(self):
  1389. return _azure_record_data(self._dns_rec)
  1390. @property
  1391. def ttl(self):
  1392. return self._dns_rec.ttl
  1393. def delete(self):
  1394. # pylint:disable=protected-access
  1395. return self._provider.dns._records.delete(self._dns_zone, self)