resources.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. """
  2. DataTypes used by this provider
  3. """
  4. from cloudbridge.cloud.base.resources import BaseInstanceType
  5. from cloudbridge.cloud.base.resources import BaseKeyPair
  6. from cloudbridge.cloud.base.resources import BasePlacementZone
  7. from cloudbridge.cloud.base.resources import BaseRegion
  8. from cloudbridge.cloud.base.resources import BaseSecurityGroup
  9. from cloudbridge.cloud.base.resources import BaseSecurityGroupRule
  10. # Older versions of Python do not have a built-in set data-structure.
  11. try:
  12. set
  13. except NameError:
  14. from sets import Set as set
  15. import hashlib
  16. import inspect
  17. import json
  18. class GCEKeyPair(BaseKeyPair):
  19. def __init__(self, provider, kp_id, kp_name, kp_material=None):
  20. super(GCEKeyPair, self).__init__(provider, None)
  21. self._kp_id = kp_id
  22. self._kp_name = kp_name
  23. self._kp_material = kp_material
  24. @property
  25. def id(self):
  26. return self._kp_id
  27. @property
  28. def name(self):
  29. # use e-mail as keyname if possible, or ID if not
  30. return self._kp_name or self.id
  31. def delete(self):
  32. svc = self._provider.security.key_pairs
  33. def _delete_key(gce_kp_generator):
  34. kp_list = []
  35. for gce_kp in gce_kp_generator:
  36. if svc.gce_kp_to_id(gce_kp) == self.id:
  37. continue
  38. else:
  39. kp_list.append(gce_kp)
  40. return kp_list
  41. svc.gce_metadata_save_op(_delete_key)
  42. @property
  43. def material(self):
  44. return self._kp_material
  45. @material.setter
  46. def material(self, value):
  47. self._kp_material = value
  48. class GCEInstanceType(BaseInstanceType):
  49. def __init__(self, provider, instance_dict):
  50. super(GCEInstanceType, self).__init__(provider)
  51. self._inst_dict = instance_dict
  52. @property
  53. def id(self):
  54. return str(self._inst_dict.get('id'))
  55. @property
  56. def name(self):
  57. return self._inst_dict.get('name')
  58. @property
  59. def family(self):
  60. return self._inst_dict.get('kind')
  61. @property
  62. def vcpus(self):
  63. return self._inst_dict.get('guestCpus')
  64. @property
  65. def ram(self):
  66. return self._inst_dict.get('memoryMb')
  67. @property
  68. def size_root_disk(self):
  69. return 0
  70. @property
  71. def size_ephemeral_disks(self):
  72. return int(self._inst_dict.get('maximumPersistentDisksSizeGb'))
  73. @property
  74. def num_ephemeral_disks(self):
  75. return self._inst_dict.get('maximumPersistentDisks')
  76. @property
  77. def extra_data(self):
  78. return {key: val for key, val in self._inst_dict.items()
  79. if key not in ['id', 'name', 'kind', 'guestCpus', 'memoryMb',
  80. 'maximumPersistentDisksSizeGb',
  81. 'maximumPersistentDisks']}
  82. class GCEPlacementZone(BasePlacementZone):
  83. def __init__(self, provider, zone, region):
  84. super(GCEPlacementZone, self).__init__(provider)
  85. if isinstance(zone, GCEPlacementZone):
  86. # pylint:disable=protected-access
  87. self._gce_zone = zone._gce_zone
  88. self._gce_region = zone._gce_region
  89. else:
  90. self._gce_zone = zone
  91. self._gce_region = region
  92. @property
  93. def id(self):
  94. """
  95. Get the zone id
  96. :rtype: ``str``
  97. :return: ID for this zone as returned by the cloud middleware.
  98. """
  99. return self._gce_zone
  100. @property
  101. def name(self):
  102. """
  103. Get the zone name.
  104. :rtype: ``str``
  105. :return: Name for this zone as returned by the cloud middleware.
  106. """
  107. return self._gce_zone
  108. @property
  109. def region_name(self):
  110. """
  111. Get the region that this zone belongs to.
  112. :rtype: ``str``
  113. :return: Name of this zone's region as returned by the cloud middleware
  114. """
  115. return self._gce_region
  116. class GCERegion(BaseRegion):
  117. def __init__(self, provider, gce_region):
  118. super(GCERegion, self).__init__(provider)
  119. self._gce_region = gce_region
  120. @property
  121. def id(self):
  122. # In GCE API, region has an 'id' property, whose values are '1220',
  123. # '1100', '1000', '1230', etc. Here we use 'name' property (such
  124. # as 'asia-east1', 'europe-west1', 'us-central1', 'us-east1') as
  125. # 'id' to represent the region for the consistency with AWS
  126. # implementation and ease of use.
  127. return self._gce_region['name']
  128. @property
  129. def name(self):
  130. return self._gce_region['name']
  131. @property
  132. def zones(self):
  133. """
  134. Accesss information about placement zones within this region.
  135. """
  136. zones_response = self._provider.gce_compute.zones().list(
  137. project=self._provider.project_name).execute()
  138. zones = [zone for zone in zones_response['items']
  139. if zone['region'] == self._gce_region['selfLink']]
  140. return [GCEPlacementZone(self._provider, zone['name'], self.name)
  141. for zone in zones]
  142. class GCEFirewallsDelegate(object):
  143. def __init__(self, provider):
  144. self._provider = provider
  145. self._list_response = None
  146. @staticmethod
  147. def tag_id(tag):
  148. md5 = hashlib.md5()
  149. md5.update(tag.encode('ascii'))
  150. return md5.hexdigest()
  151. @property
  152. def provider(self):
  153. return self._provider
  154. @property
  155. def tags(self):
  156. out = set()
  157. for firewall in self.iter_firewalls():
  158. out.add(firewall['targetTags'][0])
  159. return out
  160. def get_tag_from_id(self, tag_id):
  161. for tag in self.tags:
  162. if GCEFirewallsDelegate.tag_id(tag) == tag_id:
  163. return tag
  164. return None
  165. def has_tag(self, tag):
  166. return tag in self.tags
  167. def delete_tag_with_id(self, tag_id):
  168. tag = self.get_tag_from_id(tag_id)
  169. if tag is None:
  170. return
  171. for firewall in self.iter_firewalls(tag):
  172. self._delete_firewall(firewall)
  173. self._update_list_response()
  174. def add_firewall(self, tag, ip_protocol, port, source_range, source_tag,
  175. description):
  176. if self.find_firewall(tag, ip_protocol, port, source_range,
  177. source_tag) is not None:
  178. return True
  179. # Do not let the user accidentally open traffic from the world by not
  180. # explicitly specifying the source.
  181. if source_tag is None and source_range is None:
  182. return False
  183. firewall_number = 1
  184. suffixes = []
  185. for firewall in self.iter_firewalls(tag):
  186. suffix = firewall['name'].split('-')[-1]
  187. if suffix.isdigit():
  188. suffixes.append(int(suffix))
  189. for suffix in sorted(suffixes):
  190. if firewall_number == suffix:
  191. firewall_number += 1
  192. firewall = {'name': '%s-rule-%d' % (tag, firewall_number),
  193. 'allowed': [{'IPProtocol': str(ip_protocol)}],
  194. 'targetTags': [tag]}
  195. if description is not None:
  196. firewall['description'] = description
  197. if port is not None:
  198. firewall['allowed'][0]['ports'] = [port]
  199. if source_range is not None:
  200. firewall['sourceRanges'] = [source_range]
  201. if source_tag is not None:
  202. firewall['sourceTags'] = [source_tag]
  203. project_name = self._provider.project_name
  204. try:
  205. response = (self._provider.gce_compute
  206. .firewalls()
  207. .insert(project=project_name,
  208. body=firewall)
  209. .execute())
  210. self._provider.wait_for_global_operation(response)
  211. # TODO: process the response and handle errors.
  212. return True
  213. except:
  214. return False
  215. finally:
  216. self._update_list_response()
  217. def find_firewall(self, tag, ip_protocol, port, source_range, source_tag):
  218. if source_range is None and source_tag is None:
  219. source_range = '0.0.0.0/0'
  220. for firewall in self.iter_firewalls(tag):
  221. if firewall['allowed'][0]['IPProtocol'] != ip_protocol:
  222. continue
  223. if not self._check_list_in_dict(firewall['allowed'][0], 'ports',
  224. port):
  225. continue
  226. if not self._check_list_in_dict(firewall, 'sourceRanges',
  227. source_range):
  228. continue
  229. if not self._check_list_in_dict(firewall, 'sourceTags', source_tag):
  230. continue
  231. return firewall['id']
  232. return None
  233. def get_firewall_info(self, firewall_id):
  234. info = {}
  235. for firewall in self.iter_firewalls():
  236. if firewall['id'] != firewall_id:
  237. continue
  238. if ('sourceRanges' in firewall and
  239. len(firewall['sourceRanges']) == 1):
  240. info['source_range'] = firewall['sourceRanges'][0]
  241. if 'sourceTags' in firewall and len(firewall['sourceTags']) == 1:
  242. info['source_tag'] = firewall['sourceTags'][0]
  243. if 'targetTags' in firewall and len(firewall['targetTags']) == 1:
  244. info['target_tag'] = firewall['targetTags'][0]
  245. if 'IPProtocol' in firewall['allowed'][0]:
  246. info['ip_protocol'] = firewall['allowed'][0]['IPProtocol']
  247. if ('ports' in firewall['allowed'][0] and
  248. len(firewall['allowed'][0]['ports']) == 1):
  249. info['port'] = firewall['allowed'][0]['ports'][0]
  250. return info
  251. return info
  252. def delete_firewall_id(self, firewall_id):
  253. for firewall in self.iter_firewalls():
  254. if firewall['id'] == firewall_id:
  255. self._delete_firewall(firewall)
  256. self._update_list_response()
  257. def iter_firewalls(self, tag=None):
  258. if self._list_response is None:
  259. self._update_list_response()
  260. if 'items' not in self._list_response:
  261. return
  262. for firewall in self._list_response['items']:
  263. if 'targetTags' not in firewall or len(firewall['targetTags']) != 1:
  264. continue
  265. if 'allowed' not in firewall or len(firewall['allowed']) != 1:
  266. continue
  267. if tag is None or firewall['targetTags'][0] == tag:
  268. yield firewall
  269. def _delete_firewall(self, firewall):
  270. project_name = self._provider.project_name
  271. try:
  272. response = (self._provider.gce_compute
  273. .firewalls()
  274. .delete(project=project_name,
  275. firewall=firewall['name'])
  276. .execute())
  277. self._provider.wait_for_global_operation(response)
  278. # TODO: process the response and handle errors.
  279. return True
  280. except:
  281. return False
  282. def _update_list_response(self):
  283. self._list_response = (
  284. self._provider.gce_compute
  285. .firewalls()
  286. .list(project=self._provider.project_name)
  287. .execute())
  288. def _check_list_in_dict(self, dictionary, field_name, value):
  289. if field_name not in dictionary:
  290. return value is None
  291. if (value is None or
  292. len(dictionary[field_name]) != 1 or
  293. dictionary[field_name][0] != value):
  294. return False
  295. return True
  296. class GCESecurityGroup(BaseSecurityGroup):
  297. def __init__(self, delegate, tag, description=None):
  298. super(GCESecurityGroup, self).__init__(delegate.provider, tag)
  299. self._description = description
  300. self._delegate = delegate
  301. @property
  302. def id(self):
  303. return GCEFirewallsDelegate.tag_id(self._security_group)
  304. @property
  305. def name(self):
  306. return self._security_group
  307. @property
  308. def description(self):
  309. if self._description is not None:
  310. return self._description
  311. for firewall in self._delegate.iter_firewalls(self._security_group):
  312. if 'description' in firewall:
  313. return firewall['description']
  314. return None
  315. @property
  316. def rules(self):
  317. out = []
  318. for firewall in self._delegate.iter_firewalls(self._security_group):
  319. out.append(GCESecurityGroupRule(self._delegate, firewall['id']))
  320. return out
  321. @staticmethod
  322. def to_port_range(from_port, to_port):
  323. if from_port is not None and to_port is not None:
  324. return '%d-%d' % (from_port, to_port)
  325. elif from_port is not None:
  326. return from_port
  327. else:
  328. return to_port
  329. def add_rule(self, ip_protocol, from_port=None, to_port=None,
  330. cidr_ip=None, src_group=None):
  331. port = GCESecurityGroup.to_port_range(from_port, to_port)
  332. src_tag = src_group.name if src_group is not None else None
  333. self._delegate.add_firewall(self._security_group, ip_protocol, port,
  334. cidr_ip, src_tag, self.description)
  335. return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
  336. src_group)
  337. def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
  338. cidr_ip=None, src_group=None):
  339. port = GCESecurityGroup.to_port_range(from_port, to_port)
  340. src_tag = src_group.name if src_group is not None else None
  341. firewall_id = self._delegate.find_firewall(
  342. self._security_group, ip_protocol, port, cidr_ip, src_tag)
  343. if firewall_id is None:
  344. return None
  345. return GCESecurityGroupRule(self._delegate, firewall_id)
  346. def to_json(self):
  347. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  348. js = {k: v for(k, v) in attr if not k.startswith('_')}
  349. json_rules = [r.to_json() for r in self.rules]
  350. js['rules'] = [json.loads(r) for r in json_rules]
  351. return json.dumps(js, sort_keys=True)
  352. def delete(self):
  353. for rule in self.rules:
  354. rule.delete()
  355. class GCESecurityGroupRule(BaseSecurityGroupRule):
  356. def __init__(self, delegate, firewall_id):
  357. super(GCESecurityGroupRule, self).__init__(
  358. delegate.provider, firewall_id, None)
  359. self._delegate = delegate
  360. @property
  361. def parent(self):
  362. info = self._delegate.get_firewall_info(self._rule)
  363. if info is None or 'target_tag' not in info:
  364. return None
  365. return GCESecurityGroup(self._delegate, info['target_tag'])
  366. @property
  367. def id(self):
  368. return self._rule
  369. @property
  370. def ip_protocol(self):
  371. info = self._delegate.get_firewall_info(self._rule)
  372. if info is None or 'ip_protocol' not in info:
  373. return None
  374. return info['ip_protocol']
  375. @property
  376. def from_port(self):
  377. info = self._delegate.get_firewall_info(self._rule)
  378. if info is None or 'port' not in info:
  379. return 0
  380. port = info['port']
  381. if port.isdigit():
  382. return int(port)
  383. parts = port.split('-')
  384. if len(parts) > 2 or len(parts) < 1:
  385. return 0
  386. if parts[0].isdigit():
  387. return int(parts[0])
  388. return 0
  389. @property
  390. def to_port(self):
  391. info = self._delegate.get_firewall_info(self._rule)
  392. if info is None or 'port' not in info:
  393. return 0
  394. port = info['port']
  395. if port.isdigit():
  396. return int(port)
  397. parts = port.split('-')
  398. if len(parts) > 2 or len(parts) < 1:
  399. return 0
  400. if parts[-1].isdigit():
  401. return int(parts[-1])
  402. return 0
  403. @property
  404. def cidr_ip(self):
  405. info = self._delegate.get_firewall_info(self._rule)
  406. if info is None or 'source_range' not in info:
  407. return None
  408. return info['source_range']
  409. @property
  410. def group(self):
  411. info = self._delegate.get_firewall_info(self._rule)
  412. if info is None or 'source_tag' not in info:
  413. return None
  414. return GCESecurityGroup(self._delegate, info['source_tag'])
  415. def to_json(self):
  416. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  417. js = {k: v for(k, v) in attr if not k.startswith('_')}
  418. js['group'] = self.group.id if self.group else ''
  419. js['parent'] = self.parent.id if self.parent else ''
  420. return json.dumps(js, sort_keys=True)
  421. def delete(self):
  422. self._delegate.delete_firewall_id(self._rule)