resources.py 53 KB

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