resources.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. """
  2. DataTypes used by this provider
  3. """
  4. import inspect
  5. import json
  6. from azure.common import AzureException
  7. from cloudbridge.cloud.base.resources import BaseAttachmentInfo, \
  8. BaseBucket, BaseBucketObject, BaseMachineImage, \
  9. BaseSecurityGroup, BaseSecurityGroupRule, BaseSnapshot, BaseVolume, \
  10. ClientPagedResultList
  11. from cloudbridge.cloud.interfaces import VolumeState
  12. from cloudbridge.cloud.interfaces.resources import Instance, \
  13. MachineImageState, SnapshotState
  14. from cloudbridge.cloud.providers.azure import helpers as azure_helpers
  15. from msrestazure.azure_exceptions import CloudError
  16. NETWORK_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
  17. '{resourceGroupName}/providers/Microsoft.Network/' \
  18. 'virtualNetworks/{virtualNetworkName}'
  19. IMAGE_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
  20. '{resourceGroupName}/providers/Microsoft.Compute/' \
  21. 'images/{imageName}'
  22. INSTANCE_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
  23. '{resourceGroupName}/providers/Microsoft.Compute/' \
  24. 'virtualMachines/{vmName}'
  25. VOLUME_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
  26. '{resourceGroupName}/providers/Microsoft.Compute/' \
  27. 'disks/{diskName}'
  28. SNAPSHOT_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
  29. '{resourceGroupName}/providers/Microsoft.Compute/' \
  30. 'snapshots/{snapshotName}'
  31. NETWORK_SECURITY_GROUP_RESOURCE_ID = '/subscriptions/{subscriptionId}/' \
  32. 'resourceGroups/{resourceGroupName}/' \
  33. 'providers/Microsoft.Network/' \
  34. 'networkSecurityGroups/' \
  35. '{networkSecurityGroupName}'
  36. NETWORK_SECURITY_RULE_RESOURCE_ID = '/subscriptions/{subscriptionId}/' \
  37. 'resourceGroups/{resourceGroupName}/' \
  38. 'providers/Microsoft.Network/' \
  39. 'networkSecurityGroups/' \
  40. '{networkSecurityGroupName}' \
  41. '/securityRules/{securityRuleName}'
  42. SUBNET_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
  43. '{resourceGroupName}/providers/Microsoft.Network' \
  44. '/virtualNetworks/{virtualNetworkName}/subnets' \
  45. '/{subnetName}'
  46. PUBLIC_IP_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups' \
  47. '/{resourceGroupName}/providers/Microsoft.Network' \
  48. '/publicIPAddresses/{publicIpAddressName}'
  49. ROUTE_TABLE_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups' \
  50. '/{resourceGroupName}/providers/Microsoft.Network' \
  51. '/routeTables/{routeTableName}'
  52. ROUTE_RESOURCE_ID = '/subscriptions/{subscriptionId}/resourceGroups/' \
  53. '{resourceGroupName}/providers/Microsoft.Network' \
  54. '/routeTables/{routeTableName}/routes/{routeName}'
  55. NETWORK_INTERFACE_RESOURCE_ID = '/subscriptions/{subscriptionId}/' \
  56. 'resourceGroups/{resourceGroupName}' \
  57. '/providers/Microsoft.Network/' \
  58. 'networkInterfaces/{networkInterfaceName}'
  59. RESOURCE_GROUP_NAME = 'resourceGroupName'
  60. SUBSCRIPTION_ID = 'subscriptionId'
  61. NETWORK_NAME = 'virtualNetworkName'
  62. IMAGE_NAME = 'imageName'
  63. VM_NAME = 'vmName'
  64. VOLUME_NAME = 'diskName'
  65. SNAPSHOT_NAME = 'snapshotName'
  66. SECURITY_GROUP_NAME = 'networkSecurityGroupName'
  67. SECURITY_GROUP_RULE_NAME = 'securityRuleName'
  68. SUBNET_NAME = 'subnetName'
  69. PUBLIC_IP_NAME = 'publicIpAddressName'
  70. ROUTE_TABLE_NAME = 'routeTableName'
  71. ROUTE_NAME = 'routeName'
  72. NETWORK_INTERFACE_NAME = 'networkInterfaceName'
  73. class AzureSecurityGroup(BaseSecurityGroup):
  74. def __init__(self, provider, security_group):
  75. super(AzureSecurityGroup, self).__init__(provider, security_group)
  76. self._security_group = security_group
  77. if not self._security_group.tags:
  78. self._security_group.tags = {}
  79. @property
  80. def network_id(self):
  81. return self._security_group.resource_guid
  82. @property
  83. def resource_name(self):
  84. return self._security_group.name
  85. @property
  86. def name(self):
  87. return self._security_group.tags.get('Name', self._security_group.name)
  88. @name.setter
  89. def name(self, value):
  90. self._security_group.tags.update(Name=value)
  91. self._provider.azure_client. \
  92. update_security_group_tags(self.resource_name,
  93. self._security_group.tags)
  94. @property
  95. def description(self):
  96. return self._security_group.tags.get('Description', None)
  97. @description.setter
  98. def description(self, value):
  99. self._security_group.tags.update(Description=value)
  100. self._provider.azure_client.\
  101. update_security_group_tags(self.resource_name,
  102. self._security_group.tags)
  103. @property
  104. def rules(self):
  105. security_group_rules = []
  106. for custom_rule in self._security_group.security_rules:
  107. sg_custom_rule = AzureSecurityGroupRule(self._provider,
  108. custom_rule, self)
  109. security_group_rules.append(sg_custom_rule)
  110. return security_group_rules
  111. def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
  112. cidr_ip=None, src_group=None):
  113. """
  114. Create a security group rule.
  115. You need to pass in either ``src_group`` OR ``ip_protocol``,
  116. ``from_port``, ``to_port``, and ``cidr_ip``. In other words, either
  117. you are authorizing another group or you are authorizing some
  118. ip-based rule.
  119. :type ip_protocol: str
  120. :param ip_protocol: Either ``tcp`` | ``udp`` | ``icmp``
  121. :type from_port: int
  122. :param from_port: The beginning port number you are enabling
  123. :type to_port: int
  124. :param to_port: The ending port number you are enabling
  125. :type cidr_ip: str or list of strings
  126. :param cidr_ip: The CIDR block you are providing access to.
  127. :type src_group: ``object`` of :class:`.SecurityGroup`
  128. :param src_group: The Security Group you are granting access to.
  129. :rtype: :class:``.SecurityGroupRule``
  130. :return: Rule object if successful or ``None``.
  131. """
  132. if not cidr_ip:
  133. cidr_ip = '0.0.0.0/0'
  134. rule = self.get_rule(ip_protocol, from_port,
  135. to_port, cidr_ip, src_group)
  136. if not rule:
  137. # resource_group = self._provider.resource_group
  138. count = len(self.rules) + 1
  139. rule_name = "Rule - " + str(count)
  140. priority = count * 100
  141. destination_port_range = "*"
  142. destination_address_prefix = "*"
  143. access = "Allow"
  144. direction = "Inbound"
  145. parameters = {"protocol": ip_protocol,
  146. "source_port_range":
  147. str(from_port) + "-" + str(to_port),
  148. "destination_port_range": destination_port_range,
  149. "priority": priority,
  150. "source_address_prefix": cidr_ip,
  151. "destination_address_prefix":
  152. destination_address_prefix,
  153. "access": access, "direction": direction}
  154. result = self._provider.azure_client. \
  155. create_security_group_rule(self.resource_name,
  156. rule_name, parameters)
  157. self._security_group.security_rules.append(result)
  158. return AzureSecurityGroupRule(self._provider, result, self)
  159. return rule
  160. def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
  161. cidr_ip=None, src_group=None):
  162. for rule in self.rules:
  163. if (rule.ip_protocol == ip_protocol and
  164. rule.from_port == str(from_port) and
  165. rule.to_port == str(to_port) and
  166. rule.cidr_ip == cidr_ip):
  167. return rule
  168. return None
  169. def to_json(self):
  170. attr = inspect.getmembers(self, lambda a: not (inspect.isroutine(a)))
  171. js = {k: v for (k, v) in attr if not k.startswith('_')}
  172. json_rules = [r.to_json() for r in self.rules]
  173. js['rules'] = [json.loads(r) for r in json_rules]
  174. if js.get('network_id'):
  175. js.pop('network_id') # Omit for consistency across cloud providers
  176. return json.dumps(js, sort_keys=True)
  177. class AzureSecurityGroupRule(BaseSecurityGroupRule):
  178. def __init__(self, provider, rule, parent):
  179. super(AzureSecurityGroupRule, self).__init__(provider, rule, parent)
  180. @property
  181. def name(self):
  182. return self._rule.name
  183. @property
  184. def id(self):
  185. return self._rule.id
  186. @property
  187. def ip_protocol(self):
  188. return self._rule.protocol
  189. @property
  190. def from_port(self):
  191. if self._rule.source_port_range == '*':
  192. return self._rule.source_port_range
  193. source_port_range = self._rule.source_port_range
  194. port_range_split = source_port_range.split('-', 1)
  195. return port_range_split[0]
  196. @property
  197. def to_port(self):
  198. if self._rule.source_port_range == '*':
  199. return self._rule.source_port_range
  200. source_port_range = self._rule.source_port_range
  201. port_range_split = source_port_range.split('-', 1)
  202. return port_range_split[1]
  203. @property
  204. def cidr_ip(self):
  205. return self._rule.source_address_prefix
  206. @property
  207. def group(self):
  208. return self.parent
  209. def to_json(self):
  210. attr = inspect.getmembers(self, lambda a: not (inspect.isroutine(a)))
  211. js = {k: v for (k, v) in attr if not k.startswith('_')}
  212. js['group'] = self.group.id if self.group else ''
  213. js['parent'] = self.parent.id if self.parent else ''
  214. return json.dumps(js, sort_keys=True)
  215. def delete(self):
  216. security_group = self.parent.name
  217. self._provider.azure_client. \
  218. delete_security_group_rule(self.name, security_group)
  219. for i, o in enumerate(self.parent._security_group.security_rules):
  220. if o.name == self.name:
  221. del self.parent._security_group.security_rules[i]
  222. break
  223. class AzureBucketObject(BaseBucketObject):
  224. def __init__(self, provider, container, key):
  225. super(AzureBucketObject, self).__init__(provider)
  226. self._container = container
  227. self._key = key
  228. @property
  229. def id(self):
  230. return self._key.name
  231. @property
  232. def name(self):
  233. """
  234. Get this object's name.
  235. """
  236. return self._key.name
  237. @property
  238. def size(self):
  239. """
  240. Get this object's size.
  241. """
  242. return self._key.properties.content_length
  243. @property
  244. def last_modified(self):
  245. """
  246. Get the date and time this object was last modified.
  247. """
  248. return self._key.properties.last_modified. \
  249. strftime("%Y-%m-%dT%H:%M:%S.%f")
  250. def iter_content(self):
  251. """
  252. Returns this object's content as an
  253. iterable.
  254. """
  255. content_stream = self._provider.azure_client. \
  256. get_blob_content(self._container.name, self._key.name)
  257. if content_stream:
  258. content_stream.seek(0)
  259. return content_stream
  260. def upload(self, data):
  261. """
  262. Set the contents of this object to the data read from the source
  263. string.
  264. """
  265. try:
  266. self._provider.azure_client.create_blob_from_text(
  267. self._container.name, self.name, data)
  268. return True
  269. except AzureException:
  270. return False
  271. def upload_from_file(self, path):
  272. """
  273. Store the contents of the file pointed by the "path" variable.
  274. """
  275. try:
  276. self._provider.azure_client.create_blob_from_file(
  277. self._container.name, self.name, path)
  278. return True
  279. except AzureException:
  280. return False
  281. def delete(self):
  282. """
  283. Delete this object.
  284. :rtype: bool
  285. :return: True if successful
  286. """
  287. try:
  288. self._provider.azure_client.delete_blob(
  289. self._container.name, self.name)
  290. return True
  291. except AzureException:
  292. return False
  293. def generate_url(self, expires_in=0):
  294. """
  295. Generate a URL to this object.
  296. """
  297. return self._provider.azure_client.get_blob_url(
  298. self._container.name, self.name)
  299. class AzureBucket(BaseBucket):
  300. def __init__(self, provider, bucket):
  301. super(AzureBucket, self).__init__(provider)
  302. self._bucket = bucket
  303. @property
  304. def id(self):
  305. return self._bucket.name
  306. @property
  307. def name(self):
  308. """
  309. Get this bucket's name.
  310. """
  311. return self._bucket.name
  312. def get(self, key):
  313. """
  314. Retrieve a given object from this bucket.
  315. """
  316. try:
  317. obj = self._provider.azure_client.get_blob(self.name, key)
  318. return AzureBucketObject(self._provider, self, obj)
  319. except AzureException:
  320. return None
  321. def list(self, limit=None, marker=None, prefix=None):
  322. """
  323. List all objects within this bucket.
  324. :rtype: BucketObject
  325. :return: List of all available BucketObjects within this bucket.
  326. """
  327. objects = [AzureBucketObject(self._provider, self, obj)
  328. for obj in
  329. self._provider.azure_client.list_blobs(
  330. self.name, prefix=prefix)]
  331. return ClientPagedResultList(self._provider, objects,
  332. limit=limit, marker=marker)
  333. def delete(self, delete_contents=True):
  334. """
  335. Delete this bucket.
  336. """
  337. try:
  338. self._provider.azure_client.delete_container(self.name)
  339. return True
  340. except AzureException:
  341. return False
  342. def create_object(self, name):
  343. self._provider.azure_client.create_blob_from_text(
  344. self.name, name, '')
  345. return self.get(name)
  346. def exists(self, name):
  347. """
  348. Determine if an object with given name exists in this bucket.
  349. """
  350. return True if self.get(name) else False
  351. class AzureVolume(BaseVolume):
  352. VOLUME_STATE_MAP = {
  353. 'InProgress': VolumeState.CREATING,
  354. 'Creating': VolumeState.CREATING,
  355. 'Unattached': VolumeState.AVAILABLE,
  356. 'Attached': VolumeState.IN_USE,
  357. 'Deleting': VolumeState.CONFIGURING,
  358. 'Updating': VolumeState.CONFIGURING,
  359. 'Deleted': VolumeState.DELETED,
  360. 'Failed': VolumeState.ERROR
  361. }
  362. def __init__(self, provider, volume):
  363. super(AzureVolume, self).__init__(provider)
  364. self._volume = volume
  365. self._url_params = azure_helpers.\
  366. parse_url(VOLUME_RESOURCE_ID, volume.id)
  367. self._description = None
  368. self._status = 'unknown'
  369. self.update_status()
  370. if not self._volume.tags:
  371. self._volume.tags = {}
  372. def update_status(self):
  373. if not self._volume.provisioning_state == 'Succeeded':
  374. self._status = self._volume.provisioning_state
  375. elif self._volume.owner_id:
  376. self._status = 'Attached'
  377. else:
  378. self._status = 'Unattached'
  379. @property
  380. def id(self):
  381. return self._volume.id
  382. @property
  383. def resource_name(self):
  384. return self._volume.name
  385. @property
  386. def name(self):
  387. """
  388. Get the volume name.
  389. .. note:: an instance must have a (case sensitive) tag ``Name``
  390. """
  391. return self._volume.tags.get('Name', self._volume.name)
  392. @name.setter
  393. # pylint:disable=arguments-differ
  394. def name(self, value):
  395. """
  396. Set the volume name.
  397. """
  398. # self._volume.name = value
  399. self._volume.tags.update(Name=value)
  400. self._provider.azure_client.\
  401. update_disk_tags(self.resource_name,
  402. self._volume.tags)
  403. @property
  404. def description(self):
  405. return self._volume.tags.get('Description', None)
  406. @description.setter
  407. def description(self, value):
  408. self._volume.tags.update(Description=value)
  409. self._provider.azure_client.\
  410. update_disk_tags(self.resource_name,
  411. self._volume.tags)
  412. @property
  413. def size(self):
  414. return self._volume.disk_size_gb
  415. @property
  416. def create_time(self):
  417. return self._volume.time_created.strftime("%Y-%m-%dT%H:%M:%S.%f")
  418. @property
  419. def zone_id(self):
  420. return self._volume.location
  421. @property
  422. def source(self):
  423. if self._volume.creation_data.source_uri:
  424. return self._provider.block_store.snapshots. \
  425. get(self._volume.creation_data.source_uri)
  426. return None
  427. @property
  428. def attachments(self):
  429. if self._volume.owner_id:
  430. return BaseAttachmentInfo(self,
  431. self._volume.owner_id,
  432. None)
  433. else:
  434. return None
  435. def attach(self, instance, device=None):
  436. """
  437. Attach this volume to an instance.
  438. """
  439. try:
  440. instance_id = instance.id if isinstance(
  441. instance,
  442. Instance) else instance
  443. params = azure_helpers.parse_url(INSTANCE_RESOURCE_ID,
  444. instance_id)
  445. vm = self._provider.azure_client.get_vm(params.get(VM_NAME))
  446. vm.storage_profile.data_disks.append({
  447. 'lun': len(vm.storage_profile.data_disks),
  448. 'name': self.resource_name,
  449. 'create_option': 'attach',
  450. 'managed_disk': {
  451. 'id': self.id
  452. }
  453. })
  454. self._provider.azure_client\
  455. .create_or_update_vm(params.get(VM_NAME), vm)
  456. return True
  457. except CloudError:
  458. return False
  459. def detach(self, force=False):
  460. """
  461. Detach this volume from an instance.
  462. """
  463. virtual_machine = None
  464. for index, vm in enumerate(
  465. self._provider.azure_client.list_vm()):
  466. for item in vm.storage_profile.data_disks:
  467. if item.managed_disk and item.managed_disk.id == self.id:
  468. vm.storage_profile.data_disks.remove(item)
  469. virtual_machine = vm
  470. break
  471. if virtual_machine:
  472. break
  473. if virtual_machine:
  474. self._provider.azure_client.create_or_update_vm(
  475. virtual_machine.name,
  476. virtual_machine
  477. )
  478. return True
  479. return False
  480. def create_snapshot(self, name, description=None):
  481. """
  482. Create a snapshot of this Volume.
  483. """
  484. return self._provider.block_store.snapshots.create(name, self.id)
  485. def delete(self):
  486. """
  487. Delete this volume.
  488. """
  489. try:
  490. self._provider.azure_client.\
  491. delete_disk(self.resource_name)
  492. return True
  493. except CloudError:
  494. return False
  495. @property
  496. def state(self):
  497. return AzureVolume.VOLUME_STATE_MAP.get(
  498. self._status, VolumeState.UNKNOWN)
  499. def refresh(self):
  500. """
  501. Refreshes the state of this volume by re-querying the cloud provider
  502. for its latest state.
  503. """
  504. try:
  505. self._volume = self._provider.azure_client.\
  506. get_disk(self.resource_name)
  507. self.update_status()
  508. except (CloudError, ValueError):
  509. # The volume no longer exists and cannot be refreshed.
  510. # set the status to unknown
  511. self._status = 'unknown'
  512. class AzureSnapshot(BaseSnapshot):
  513. SNAPSHOT_STATE_MAP = {
  514. 'InProgress': SnapshotState.PENDING,
  515. 'Succeeded': SnapshotState.AVAILABLE,
  516. 'Failed': SnapshotState.ERROR,
  517. 'Updating': SnapshotState.CONFIGURING,
  518. 'Deleting': SnapshotState.CONFIGURING,
  519. 'Deleted': SnapshotState.UNKNOWN
  520. }
  521. def __init__(self, provider, snapshot):
  522. super(AzureSnapshot, self).__init__(provider)
  523. self._snapshot = snapshot
  524. self._description = None
  525. self._url_params = azure_helpers.\
  526. parse_url(SNAPSHOT_RESOURCE_ID, snapshot.id)
  527. self._status = self._snapshot.provisioning_state
  528. if not self._snapshot.tags:
  529. self._snapshot.tags = {}
  530. @property
  531. def id(self):
  532. return self._snapshot.id
  533. @property
  534. def resource_name(self):
  535. return self._snapshot.name
  536. @property
  537. def name(self):
  538. """
  539. Get the snapshot name.
  540. .. note:: an instance must have a (case sensitive) tag ``Name``
  541. """
  542. return self._snapshot.tags.get('Name', self._snapshot.name)
  543. @name.setter
  544. # pylint:disable=arguments-differ
  545. def name(self, value):
  546. """
  547. Set the snapshot name.
  548. """
  549. # self._snapshot.name = value
  550. self._snapshot.tags.update(Name=value)
  551. self._provider.azure_client. \
  552. update_snapshot_tags(self.resource_name,
  553. self._snapshot.tags)
  554. @property
  555. def description(self):
  556. return self._snapshot.tags.get('Description', None)
  557. @description.setter
  558. def description(self, value):
  559. self._snapshot.tags.update(Description=value)
  560. self._provider.azure_client.\
  561. update_snapshot_tags(self.resource_name,
  562. self._snapshot.tags)
  563. @property
  564. def size(self):
  565. return self._snapshot.disk_size_gb
  566. @property
  567. def volume_id(self):
  568. return self._snapshot.creation_data.source_uri
  569. @property
  570. def create_time(self):
  571. return self._snapshot.time_created.strftime("%Y-%m-%dT%H:%M:%S.%f")
  572. @property
  573. def state(self):
  574. return AzureSnapshot.SNAPSHOT_STATE_MAP.get(
  575. self._status, SnapshotState.UNKNOWN)
  576. def refresh(self):
  577. """
  578. Refreshes the state of this snapshot by re-querying the cloud provider
  579. for its latest state.
  580. """
  581. try:
  582. self._snapshot = self._provider.azure_client.\
  583. get_snapshot(self.resource_name)
  584. self._status = self._snapshot.provisioning_state
  585. except (CloudError, ValueError):
  586. # The snapshot no longer exists and cannot be refreshed.
  587. # set the status to unknown
  588. self._status = 'unknown'
  589. def delete(self):
  590. """
  591. Delete this snapshot.
  592. """
  593. try:
  594. self._provider.azure_client.delete_snapshot(self.resource_name)
  595. return True
  596. except CloudError:
  597. return False
  598. def create_volume(self, placement=None,
  599. size=None, volume_type=None, iops=None):
  600. """
  601. Create a new Volume from this Snapshot.
  602. """
  603. return self._provider.block_store.volumes.\
  604. create(self.resource_name, self.size,
  605. zone=placement, snapshot=self)
  606. class AzureMachineImage(BaseMachineImage):
  607. IMAGE_STATE_MAP = {
  608. 'InProgress': MachineImageState.PENDING,
  609. 'Succeeded': MachineImageState.AVAILABLE,
  610. 'Failed': MachineImageState.ERROR
  611. }
  612. def __init__(self, provider, image):
  613. super(AzureMachineImage, self).__init__(provider)
  614. self._image = image
  615. self._url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID, image.id)
  616. self._state = self._image.provisioning_state
  617. @property
  618. def id(self):
  619. """
  620. Get the image identifier.
  621. :rtype: ``str``
  622. :return: ID for this instance as returned by the cloud middleware.
  623. """
  624. return self._image.id
  625. @property
  626. def name(self):
  627. """
  628. Get the image name.
  629. :rtype: ``str``
  630. :return: Name for this image as returned by the cloud middleware.
  631. """
  632. return self._image.name
  633. @property
  634. def description(self):
  635. """
  636. Get the image description.
  637. :rtype: ``str``
  638. :return: Description for this image as returned by the cloud middleware
  639. """
  640. return None
  641. @property
  642. def min_disk(self):
  643. """
  644. Returns the minimum size of the disk that's required to
  645. boot this image (in GB)
  646. :rtype: ``int``
  647. :return: The minimum disk size needed by this image
  648. """
  649. return self._image.storage_profile.os_disk.disk_size_gb
  650. def delete(self):
  651. """
  652. Delete this image
  653. """
  654. self._provider.azure_client.delete_image(self.name)
  655. @property
  656. def state(self):
  657. return AzureMachineImage.IMAGE_STATE_MAP.get(
  658. self._state, MachineImageState.UNKNOWN)
  659. def refresh(self):
  660. """
  661. Refreshes the state of this instance by re-querying the cloud provider
  662. for its latest state.
  663. """
  664. try:
  665. self._image = self._provider.azure_client.get_image(self.name)
  666. self._state = self._image.provisioning_state
  667. except CloudError:
  668. # image no longer exists
  669. self._state = "unknown"