resources.py 54 KB

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