services.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293
  1. import hashlib
  2. import uuid
  3. from collections import namedtuple
  4. import cloudbridge as cb
  5. from cloudbridge.cloud.base.resources import ClientPagedResultList
  6. from cloudbridge.cloud.base.resources import ServerPagedResultList
  7. from cloudbridge.cloud.base.services import BaseBucketService
  8. from cloudbridge.cloud.base.services import BaseComputeService
  9. from cloudbridge.cloud.base.services import BaseFloatingIPService
  10. from cloudbridge.cloud.base.services import BaseGatewayService
  11. from cloudbridge.cloud.base.services import BaseImageService
  12. from cloudbridge.cloud.base.services import BaseInstanceService
  13. from cloudbridge.cloud.base.services import BaseKeyPairService
  14. from cloudbridge.cloud.base.services import BaseNetworkService
  15. from cloudbridge.cloud.base.services import BaseNetworkingService
  16. from cloudbridge.cloud.base.services import BaseRegionService
  17. from cloudbridge.cloud.base.services import BaseRouterService
  18. from cloudbridge.cloud.base.services import BaseSecurityService
  19. from cloudbridge.cloud.base.services import BaseSnapshotService
  20. from cloudbridge.cloud.base.services import BaseStorageService
  21. from cloudbridge.cloud.base.services import BaseSubnetService
  22. from cloudbridge.cloud.base.services import BaseVMFirewallService
  23. from cloudbridge.cloud.base.services import BaseVMTypeService
  24. from cloudbridge.cloud.base.services import BaseVolumeService
  25. from cloudbridge.cloud.interfaces.resources import PlacementZone
  26. from cloudbridge.cloud.interfaces.resources import VMFirewall
  27. from cloudbridge.cloud.providers.gce import helpers
  28. import googleapiclient
  29. from retrying import retry
  30. from .resources import GCEFirewallsDelegate
  31. from .resources import GCEFloatingIP
  32. from .resources import GCEInstance
  33. from .resources import GCEInternetGateway
  34. from .resources import GCEKeyPair
  35. from .resources import GCEMachineImage
  36. from .resources import GCENetwork
  37. from .resources import GCEPlacementZone
  38. from .resources import GCERegion
  39. from .resources import GCERouter
  40. from .resources import GCESnapshot
  41. from .resources import GCESubnet
  42. from .resources import GCEVMFirewall
  43. from .resources import GCEVMType
  44. from .resources import GCEVolume
  45. from .resources import GCSBucket
  46. class GCESecurityService(BaseSecurityService):
  47. def __init__(self, provider):
  48. super(GCESecurityService, self).__init__(provider)
  49. # Initialize provider services
  50. self._key_pairs = GCEKeyPairService(provider)
  51. self._vm_firewalls = GCEVMFirewallService(provider)
  52. @property
  53. def key_pairs(self):
  54. return self._key_pairs
  55. @property
  56. def vm_firewalls(self):
  57. return self._vm_firewalls
  58. class GCEKeyPairService(BaseKeyPairService):
  59. GCEKeyInfo = namedtuple('GCEKeyInfo', 'format public_key email')
  60. def __init__(self, provider):
  61. super(GCEKeyPairService, self).__init__(provider)
  62. self._gce_projects = None
  63. @property
  64. def gce_projects(self):
  65. if not self._gce_projects:
  66. self._gce_projects = self.provider.gce_compute.projects()
  67. return self._gce_projects
  68. def get(self, key_pair_id):
  69. """
  70. Returns a KeyPair given its ID.
  71. """
  72. for kp in self.list():
  73. if kp.id == key_pair_id:
  74. return kp
  75. else:
  76. return None
  77. def _iter_gce_key_pairs(self):
  78. """
  79. Iterates through the project's metadata, yielding a GCEKeyInfo object
  80. for each entry in commonInstanceMetaData/items
  81. """
  82. metadata = self._get_common_metadata()
  83. for kpinfo in self._iter_gce_ssh_keys(metadata):
  84. yield kpinfo
  85. def _get_common_metadata(self):
  86. """
  87. Get a project's commonInstanceMetadata entry
  88. """
  89. metadata = self.gce_projects.get(
  90. project=self.provider.project_name).execute()
  91. return metadata["commonInstanceMetadata"]
  92. def _get_or_add_sshkey_entry(self, metadata):
  93. """
  94. Get the sshKeys entry from commonInstanceMetadata/items.
  95. If an entry does not exist, adds a new empty entry
  96. """
  97. sshkey_entry = None
  98. entries = [item for item in metadata["items"]
  99. if item["key"] == "sshKeys"]
  100. if entries:
  101. sshkey_entry = entries[0]
  102. else: # add a new entry
  103. sshkey_entry = {"key": "sshKeys", "value": ""}
  104. metadata["items"].append(sshkey_entry)
  105. return sshkey_entry
  106. def _iter_gce_ssh_keys(self, metadata):
  107. """
  108. Iterates through the ssh keys given a commonInstanceMetadata dict,
  109. yielding a GCEKeyInfo object for each entry in
  110. commonInstanceMetaData/items
  111. """
  112. sshkeys = self._get_or_add_sshkey_entry(metadata)["value"]
  113. for key in sshkeys.split("\n"):
  114. # elems should be "ssh-rsa <public_key> <email>"
  115. elems = key.split(" ")
  116. if elems and elems[0]: # ignore blank lines
  117. yield GCEKeyPairService.GCEKeyInfo(
  118. elems[0], elems[1].encode('ascii'), elems[2])
  119. def gce_metadata_save_op(self, callback):
  120. """
  121. Carries out a metadata save operation. In GCE, a fingerprint based
  122. locking mechanism is used to prevent lost updates. A new fingerprint
  123. is returned each time metadata is retrieved. Therefore, this method
  124. retrieves the metadata, invokes the provided callback with that
  125. metadata, and saves the metadata using the original fingerprint
  126. immediately afterwards, ensuring that update conflicts can be detected.
  127. """
  128. def _save_common_metadata():
  129. metadata = self._get_common_metadata()
  130. # add a new entry if one doesn'te xist
  131. sshkey_entry = self._get_or_add_sshkey_entry(metadata)
  132. gce_kp_list = callback(self._iter_gce_ssh_keys(metadata))
  133. entry = ""
  134. for gce_kp in gce_kp_list:
  135. entry = entry + u"{0} {1} {2}\n".format(gce_kp.format,
  136. gce_kp.public_key,
  137. gce_kp.email)
  138. sshkey_entry["value"] = entry.rstrip()
  139. # common_metadata will have the current fingerprint at this point
  140. operation = self.gce_projects.setCommonInstanceMetadata(
  141. project=self.provider.project_name, body=metadata).execute()
  142. self.provider.wait_for_operation(operation)
  143. # Retry a few times if the fingerprints conflict
  144. retry_decorator = retry(stop_max_attempt_number=5)
  145. retry_decorator(_save_common_metadata)()
  146. def gce_kp_to_id(self, gce_kp):
  147. """
  148. Accept a GCEKeyInfo object and return a unique
  149. ID for it
  150. """
  151. md5 = hashlib.md5()
  152. md5.update(gce_kp.public_key)
  153. return md5.hexdigest()
  154. def list(self, limit=None, marker=None):
  155. key_pairs = []
  156. for gce_kp in self._iter_gce_key_pairs():
  157. kp_id = self.gce_kp_to_id(gce_kp)
  158. kp_name = gce_kp.email
  159. key_pairs.append(GCEKeyPair(self.provider, kp_id, kp_name))
  160. return ClientPagedResultList(self.provider, key_pairs,
  161. limit=limit, marker=marker)
  162. def find(self, name, limit=None, marker=None):
  163. """
  164. Searches for a key pair by a given list of attributes.
  165. """
  166. found_kps = []
  167. for kp in self.list():
  168. if kp.name == name:
  169. found_kps.append(kp)
  170. return ClientPagedResultList(self.provider, found_kps,
  171. limit=limit, marker=marker)
  172. def create(self, name):
  173. kp = self.find(name=name)
  174. if kp:
  175. return kp
  176. private_key, public_key = helpers.generate_key_pair()
  177. kp_info = GCEKeyPairService.GCEKeyInfo(name + u":ssh-rsa",
  178. public_key, name)
  179. def _add_kp(gce_kp_generator):
  180. kp_list = []
  181. # Add the new key pair
  182. kp_list.append(kp_info)
  183. for gce_kp in gce_kp_generator:
  184. kp_list.append(gce_kp)
  185. return kp_list
  186. self.gce_metadata_save_op(_add_kp)
  187. return GCEKeyPair(self.provider, self.gce_kp_to_id(kp_info), name,
  188. kp_material=private_key)
  189. class GCEVMFirewallService(BaseVMFirewallService):
  190. def __init__(self, provider):
  191. super(GCEVMFirewallService, self).__init__(provider)
  192. self._delegate = GCEFirewallsDelegate(provider)
  193. def get(self, group_id):
  194. tag, network_name = self._delegate.get_tag_network_from_id(group_id)
  195. if tag is None:
  196. return None
  197. network = self.provider.network.get_by_name(network_name)
  198. return GCEVMFirewall(self._delegate, tag, network)
  199. def list(self, limit=None, marker=None):
  200. vm_firewalls = []
  201. for tag, network_name in self._delegate.tag_networks:
  202. network = self.provider.network.get_by_name(network_name)
  203. vm_firewall = GCEVMFirewall(self._delegate, tag, network)
  204. vm_firewalls.append(vm_firewall)
  205. return ClientPagedResultList(self.provider, vm_firewalls,
  206. limit=limit, marker=marker)
  207. def create(self, name, description, network_id=None):
  208. network = self.provider.network.get(network_id)
  209. return GCEVMFirewall(self._delegate, name, network, description)
  210. def find(self, name, limit=None, marker=None):
  211. """
  212. Finds a non-empty VM firewall. If a VM firewall with the given name
  213. does not exist, or if it does not contain any rules, an empty list is
  214. returned.
  215. """
  216. out = []
  217. for tag, network_name in self._delegate.tag_networks:
  218. if tag == name:
  219. network = self.provider.network.get_by_name(network_name)
  220. out.append(GCEVMFirewall(self._delegate, name, network))
  221. return out
  222. def delete(self, group_id):
  223. return self._delegate.delete_tag_network_with_id(group_id)
  224. def find_by_network_and_tags(self, network_name, tags):
  225. """
  226. Finds non-empty VM firewalls by network name and VM firewall names
  227. (tags). If no matching VM firewall is found, an empty list is returned.
  228. """
  229. vm_firewalls = []
  230. for tag, net_name in self._delegate.tag_networks:
  231. if network_name != net_name:
  232. continue
  233. if tag not in tags:
  234. continue
  235. network = self.provider.network.get_by_name(net_name)
  236. vm_firewalls.append(
  237. GCEVMFirewall(self._delegate, tag, network))
  238. return vm_firewalls
  239. class GCEVMTypeService(BaseVMTypeService):
  240. def __init__(self, provider):
  241. super(GCEVMTypeService, self).__init__(provider)
  242. @property
  243. def instance_data(self):
  244. response = (self.provider
  245. .gce_compute
  246. .machineTypes()
  247. .list(project=self.provider.project_name,
  248. zone=self.provider.default_zone)
  249. .execute())
  250. return response['items']
  251. def get(self, vm_type_id):
  252. for inst_type in self.instance_data:
  253. if inst_type.get('id') == vm_type_id:
  254. return GCEVMType(self.provider, inst_type)
  255. return None
  256. def find(self, **kwargs):
  257. matched_inst_types = []
  258. for inst_type in self.instance_data:
  259. is_match = True
  260. for key, value in kwargs.iteritems():
  261. if key not in inst_type:
  262. raise TypeError("The attribute key is not valid.")
  263. if inst_type.get(key) != value:
  264. is_match = False
  265. break
  266. if is_match:
  267. matched_inst_types.append(
  268. GCEVMType(self.provider, inst_type))
  269. return matched_inst_types
  270. def list(self, limit=None, marker=None):
  271. inst_types = [GCEVMType(self.provider, inst_type)
  272. for inst_type in self.instance_data]
  273. return ClientPagedResultList(self.provider, inst_types,
  274. limit=limit, marker=marker)
  275. class GCERegionService(BaseRegionService):
  276. def __init__(self, provider):
  277. super(GCERegionService, self).__init__(provider)
  278. def get(self, region_id):
  279. try:
  280. region = (self.provider
  281. .gce_compute
  282. .regions()
  283. .get(project=self.provider.project_name,
  284. region=region_id)
  285. .execute())
  286. # Handle the case when region_id is not valid
  287. except googleapiclient.errors.HttpError as http_error:
  288. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  289. return None
  290. if region:
  291. return GCERegion(self.provider, region)
  292. else:
  293. return None
  294. def list(self, limit=None, marker=None):
  295. max_result = limit if limit is not None and limit < 500 else 500
  296. regions_response = (self.provider
  297. .gce_compute
  298. .regions()
  299. .list(project=self.provider.project_name,
  300. maxResults=max_result,
  301. pageToken=marker)
  302. .execute())
  303. regions = [GCERegion(self.provider, region)
  304. for region in regions_response['items']]
  305. if len(regions) > max_result:
  306. cb.log.warning('Expected at most %d results; got %d',
  307. max_result, len(regions))
  308. return ServerPagedResultList('nextPageToken' in regions_response,
  309. regions_response.get('nextPageToken'),
  310. False, data=regions)
  311. @property
  312. def current(self):
  313. return self.get(self.provider.region_name)
  314. class GCEImageService(BaseImageService):
  315. def __init__(self, provider):
  316. super(GCEImageService, self).__init__(provider)
  317. self._public_images = None
  318. _PUBLIC_IMAGE_PROJECTS = ['centos-cloud', 'coreos-cloud', 'debian-cloud',
  319. 'opensuse-cloud', 'ubuntu-os-cloud']
  320. def _retrieve_public_images(self):
  321. if self._public_images is not None:
  322. return
  323. self._public_images = []
  324. try:
  325. for project in GCEImageService._PUBLIC_IMAGE_PROJECTS:
  326. for image in helpers.iter_all(
  327. self.provider.gce_compute.images(), project=project):
  328. self._public_images.append(
  329. GCEMachineImage(self.provider, image))
  330. except googleapiclient.errors.HttpError as http_error:
  331. cb.log.warning("googleapiclient.errors.HttpError: {0}".format(
  332. http_error))
  333. def get(self, image_id):
  334. """
  335. Returns an Image given its id
  336. """
  337. try:
  338. image = (self.provider
  339. .gce_compute
  340. .images()
  341. .get(project=self.provider.project_name,
  342. image=image_id)
  343. .execute())
  344. if image:
  345. return GCEMachineImage(self.provider, image)
  346. except TypeError as type_error:
  347. # The API will throw an TypeError, if parameter `image` does not
  348. # match the pattern "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?".
  349. cb.log.warning("TypeError: {0}".format(type_error))
  350. except googleapiclient.errors.HttpError as http_error:
  351. # If the image is not found in project-specific private images,
  352. # look for this image in public images.
  353. self._retrieve_public_images()
  354. for public_image in self._public_images:
  355. if public_image.id == image_id:
  356. return public_image
  357. cb.log.warning(
  358. "googleapiclient.errors.HttpError: {0}".format(http_error))
  359. return None
  360. def find(self, name, limit=None, marker=None):
  361. """
  362. Searches for an image by a given list of attributes
  363. """
  364. filters = {'name': name}
  365. # Retrieve all available images by setting limit to sys.maxsize
  366. images = [image for image in self if image.name == filters['name']]
  367. return ClientPagedResultList(self.provider, images,
  368. limit=limit, marker=marker)
  369. def list(self, limit=None, marker=None):
  370. """
  371. List all images.
  372. """
  373. self._retrieve_public_images()
  374. images = []
  375. if (self.provider.project_name not in
  376. GCEImageService._PUBLIC_IMAGE_PROJECTS):
  377. try:
  378. for image in helpers.iter_all(
  379. self.provider.gce_compute.images(),
  380. project=self.provider.project_name):
  381. images.append(GCEMachineImage(self.provider, image))
  382. except googleapiclient.errors.HttpError as http_error:
  383. cb.log.warning(
  384. "googleapiclient.errors.HttpError: {0}".format(http_error))
  385. images.extend(self._public_images)
  386. return ClientPagedResultList(self.provider, images,
  387. limit=limit, marker=marker)
  388. class GCEInstanceService(BaseInstanceService):
  389. def __init__(self, provider):
  390. super(GCEInstanceService, self).__init__(provider)
  391. def create(self, name, image, vm_type, subnet, zone=None,
  392. key_pair=None, vm_firewalls=None, user_data=None,
  393. launch_config=None, **kwargs):
  394. """
  395. Creates a new virtual machine instance.
  396. """
  397. if not zone:
  398. zone = self.provider.default_zone
  399. if not launch_config:
  400. if subnet:
  401. network = self.provider.network.get(subnet.network_id)
  402. network_url = (network.resource_url
  403. if isinstance(network, GCENetwork) else network)
  404. else:
  405. network_url = 'global/networks/default'
  406. config = {
  407. 'name': name,
  408. 'machineType': vm_type.resource_url,
  409. 'disks': [{'boot': True,
  410. 'autoDelete': True,
  411. 'initializeParams': {
  412. 'sourceImage': image.resource_url,
  413. }
  414. }],
  415. 'networkInterfaces': [
  416. {
  417. # TODO: Should replace network below with subnetwork
  418. 'network': network_url,
  419. 'accessConfigs': [{'type': 'ONE_TO_ONE_NAT',
  420. 'name': 'External NAT'}]
  421. }],
  422. }
  423. if vm_firewalls and isinstance(vm_firewalls, list):
  424. vm_firewall_names = []
  425. if isinstance(vm_firewalls[0], VMFirewall):
  426. vm_firewall_names = [f.name for f in vm_firewalls]
  427. elif isinstance(vm_firewalls[0], str):
  428. vm_firewall_names = vm_firewalls
  429. if len(vm_firewall_names) > 0:
  430. config['tags'] = {}
  431. config['tags']['items'] = vm_firewall_names
  432. else:
  433. config = launch_config
  434. operation = (self.provider
  435. .gce_compute.instances()
  436. .insert(project=self.provider.project_name,
  437. zone=self.provider.default_zone,
  438. body=config)
  439. .execute())
  440. if 'zone' not in operation:
  441. return None
  442. zone_url = self.provider.parse_url(operation['zone'])
  443. instance_id = operation.get('targetLink')
  444. self.provider.wait_for_operation(operation,
  445. zone=zone_url.parameters['zone'])
  446. return self.get(instance_id)
  447. def get(self, instance_id):
  448. """
  449. Returns an instance given its name. Returns None
  450. if the object does not exist.
  451. A GCE instance is uniquely identified by its selfLink, which is used
  452. as its id.
  453. """
  454. try:
  455. return GCEInstance(
  456. self.provider,
  457. self.provider.parse_url(instance_id).get_resource())
  458. except googleapiclient.errors.HttpError as http_error:
  459. # If the instance is not found, the API will raise
  460. # googleapiclient.errors.HttpError.
  461. cb.log.warning(
  462. "googleapiclient.errors.HttpError: {0}".format(http_error))
  463. return None
  464. def find(self, name, limit=None, marker=None):
  465. """
  466. Searches for instances by instance name.
  467. :return: a list of Instance objects
  468. """
  469. instances = [instance for instance in self.list()
  470. if instance.name == name]
  471. if limit and len(instances) > limit:
  472. instances = instances[:limit]
  473. return instances
  474. def list(self, limit=None, marker=None):
  475. """
  476. List all instances.
  477. """
  478. # For GCE API, Acceptable values are 0 to 500, inclusive.
  479. # (Default: 500).
  480. max_result = limit if limit is not None and limit < 500 else 500
  481. response = (self.provider
  482. .gce_compute
  483. .instances()
  484. .list(project=self.provider.project_name,
  485. zone=self.provider.default_zone,
  486. maxResults=max_result,
  487. pageToken=marker)
  488. .execute())
  489. instances = []
  490. if 'items' in response:
  491. instances = [GCEInstance(self.provider, inst)
  492. for inst in response['items']]
  493. if len(instances) > max_result:
  494. cb.log.warning('Expected at most %d results; got %d',
  495. max_result, len(instances))
  496. return ServerPagedResultList('nextPageToken' in response,
  497. response.get('nextPageToken'),
  498. False, data=instances)
  499. class GCEComputeService(BaseComputeService):
  500. # TODO: implement GCEComputeService
  501. def __init__(self, provider):
  502. super(GCEComputeService, self).__init__(provider)
  503. self._instance_svc = GCEInstanceService(self.provider)
  504. self._vm_type_svc = GCEVMTypeService(self.provider)
  505. self._region_svc = GCERegionService(self.provider)
  506. self._images_svc = GCEImageService(self.provider)
  507. @property
  508. def images(self):
  509. return self._images_svc
  510. @property
  511. def vm_types(self):
  512. return self._vm_type_svc
  513. @property
  514. def instances(self):
  515. return self._instance_svc
  516. @property
  517. def regions(self):
  518. return self._region_svc
  519. class GCENetworkingService(BaseNetworkingService):
  520. def __init__(self, provider):
  521. super(GCENetworkingService, self).__init__(provider)
  522. self._network_service = GCENetworkService(self.provider)
  523. self._subnet_service = GCESubnetService(self.provider)
  524. self._floating_ip_service = GCEFloatingIPService(self.provider)
  525. self._router_service = GCERouterService(self.provider)
  526. self._gateway_service = GCEGatewayService(self.provider)
  527. @property
  528. def networks(self):
  529. return self._network_service
  530. @property
  531. def subnets(self):
  532. return self._subnet_service
  533. @property
  534. def floating_ips(self):
  535. return self._floating_ip_service
  536. @property
  537. def routers(self):
  538. return self._router_service
  539. @property
  540. def gateways(self):
  541. return self._gateway_servcie
  542. class GCENetworkService(BaseNetworkService):
  543. def __init__(self, provider):
  544. super(GCENetworkService, self).__init__(provider)
  545. def get(self, network_id):
  546. if network_id is None:
  547. return None
  548. # Note: networks = self.list(filter='id eq %s' % network_id) does not
  549. # work due to a GCE API bug that causes an error if the network_id has
  550. # has more than 19 digits.
  551. networks = self.list()
  552. for network in networks:
  553. if network.id == network_id:
  554. return network
  555. return None
  556. def find(self, name, limit=None, marker=None):
  557. """
  558. GCE networks are global. There is at most one network with a given
  559. name.
  560. """
  561. return [self.get(name)]
  562. def get_by_name(self, network_name):
  563. if network_name is None:
  564. return None
  565. networks = self.list(filter='name eq %s' % network_name)
  566. return None if len(networks) == 0 else networks[0]
  567. def list(self, limit=None, marker=None, filter=None):
  568. try:
  569. response = (self.provider
  570. .gce_compute
  571. .networks()
  572. .list(project=self.provider.project_name,
  573. filter=filter)
  574. .execute())
  575. networks = []
  576. if 'items' in response:
  577. for network in response['items']:
  578. networks.append(GCENetwork(self.provider, network))
  579. return networks
  580. except googleapiclient.errors.HttpError as http_error:
  581. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  582. return []
  583. def _create(self, name, create_subnetworks):
  584. """
  585. Possible values for 'create_subnetworks' are:
  586. None: For creating a legacy (non-subnetted) network.
  587. True: For creating an auto mode VPC network. This also creates a
  588. subnetwork in every region.
  589. False: For creating a custom mode VPC network. Subnetworks should be
  590. created manually.
  591. """
  592. networks = self.list(filter='name eq %s' % name)
  593. if len(networks) > 0:
  594. return networks[0]
  595. body = {'name': name,
  596. 'autoCreateSubnetworks': create_subnetworks}
  597. try:
  598. response = (self.provider
  599. .gce_compute
  600. .networks()
  601. .insert(project=self.provider.project_name,
  602. body=body)
  603. .execute())
  604. if 'error' in response:
  605. return None
  606. self.provider.wait_for_operation(response)
  607. networks = self.list(filter='name eq %s' % name)
  608. return None if len(networks) == 0 else networks[0]
  609. except googleapiclient.errors.HttpError as http_error:
  610. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  611. return None
  612. def create(self, name):
  613. """
  614. Creates a custom mode VPC network.
  615. """
  616. return self._create(name, False)
  617. def get_or_create_default(self):
  618. return self._create(GCEFirewallsDelegate.DEFAULT_NETWORK, True)
  619. class GCEFloatingIPService(BaseFloatingIPService):
  620. def __init__(self, provider):
  621. super(GCEFloatingIPService, self).__init__(provider)
  622. def get(self, floating_ip_id):
  623. try:
  624. response = (self.provider
  625. .gce_compute
  626. .addresses()
  627. .get(project=self.provider.project_name,
  628. region=self.provider.region_name)
  629. .execute())
  630. return GCEFloatingIP(self.provider, response)
  631. except googleapiclient.errors.HttpError as http_error:
  632. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  633. return None
  634. def list(self, limit=None, marker=None):
  635. max_result = limit if limit is not None and limit < 500 else 500
  636. try:
  637. response = (self.provider
  638. .gce_compute
  639. .addresses()
  640. .list(project=self.provider.project_name,
  641. region=self.provider.region_name,
  642. maxResults=max_result,
  643. pageToken=marker)
  644. .execute())
  645. ips = []
  646. if 'items' in response:
  647. ips = [GCEFloatingIP(self.provider, ip)
  648. for ip in response['items']]
  649. if len(ips) > max_result:
  650. cb.log.warning('Expected at most %d results; got %d',
  651. max_result, len(ips))
  652. return ServerPagedResultList('nextPageToken' in response,
  653. response.get('nextPageToken'),
  654. False, data=ips)
  655. except googleapiclient.errors.HttpError as http_error:
  656. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  657. return None
  658. def create(self):
  659. region = self.provider.region_name
  660. ip_name = 'ip-{0}'.format(uuid.uuid4())
  661. try:
  662. response = (self.provider
  663. .gce_compute
  664. .addresses()
  665. .insert(project=self.provider.project_name,
  666. region=region,
  667. body={'name': ip_name})
  668. .execute())
  669. if 'error' in response:
  670. return None
  671. self.provider.wait_for_operation(response, region=region)
  672. return self.get(ip_name)
  673. except googleapiclient.errors.HttpError as http_error:
  674. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  675. return None
  676. class GCERouterService(BaseRouterService):
  677. def __init__(self, provider):
  678. super(GCERouterService, self).__init__(provider)
  679. def get(self, router_id):
  680. return self._get_in_region(router_id)
  681. def find(self, name, limit=None, marker=None):
  682. routers = []
  683. for region in self.provider.compute.regions.list():
  684. routers.append(self._get_in_region(name, region.name))
  685. return ClientPagedResultList(self.provider, routers, limit=limit,
  686. marker=marker)
  687. def list(self, limit=None, marker=None):
  688. region = self.provider.region_name
  689. max_result = limit if limit is not None and limit < 500 else 500
  690. response = (self.provider
  691. .gce_compute
  692. .routers()
  693. .list(project=self.provider.project_name,
  694. region=region,
  695. maxResults=max_result,
  696. pageToken=marker)
  697. .execute())
  698. routers = []
  699. for router in response.get('items', []):
  700. routers.append(GCERouter(self.provider, router))
  701. if len(routers) > max_result:
  702. cb.log.warning('Expected at most %d results; go %d',
  703. max_result, len(routers))
  704. return ServerPagedResultList('nextPageToken' in response,
  705. response.get('nextPageToken'),
  706. False, data=routers)
  707. def create(self, network, name=None):
  708. if not isinstance(network, GCENetwork):
  709. network = self.provider.networking.networks.get(network)
  710. network_url = network.resource_url
  711. name = name if name else 'router-{0}'.format(uuid.uuid4())
  712. region = self.provider.region_name
  713. try:
  714. response = (self.provider
  715. .gce_compute
  716. .routers()
  717. .insert(project=self.provider.project_name,
  718. region=region,
  719. body={'name': name,
  720. 'network': network_url})
  721. .execute())
  722. if 'error' in response:
  723. return None
  724. self.provider.wait_for_operation(response, region=region)
  725. return self._get_in_region(name, region)
  726. except googleapiclient.errors.HttpError as http_error:
  727. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  728. return None
  729. def delete(self, router):
  730. region = self.provider.region_name
  731. name = router.name if isinstance(router, GCERouter) else router
  732. response = (self.provider
  733. .gce_compute
  734. .routers()
  735. .delete(project=self.provider.project_name,
  736. region=region,
  737. router=name)
  738. .execute())
  739. self._provider.wait_for_operation(response, region=region)
  740. def _get_in_region(self, router_id, region=None):
  741. region = region if region else self.provider.region_name
  742. try:
  743. response = (self.provider
  744. .gce_compute
  745. .routers()
  746. .get(project=self.provider.project_name,
  747. region=region,
  748. router=router_id)
  749. .execute())
  750. return GCERouter(self.provider, response)
  751. except googleapiclient.errors.HttpError as http_error:
  752. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  753. return None
  754. class GCEGatewayService(BaseGatewayService):
  755. _DEFAULT_GATEWAY_NAME = 'default-internet-gateway'
  756. _GATEWAY_URL_PREFIX = 'global/gateways/'
  757. def __init__(self, provider):
  758. super(GCEGatewayService, self).__init__(provider)
  759. self._default_internet_gateway = GCEInternetGateway(
  760. provider,
  761. {'id': (GCEGatewayService._GATEWAY_URL_PREFIX +
  762. GCEGatewayService._DEFAULT_GATEWAY_NAME),
  763. 'name': GCEGatewayService._DEFAULT_GATEWAY_NAME})
  764. def get_or_create_inet_gateway(self, name):
  765. return self._default_internet_gateway
  766. def delete(self, gateway):
  767. pass
  768. class GCESubnetService(BaseSubnetService):
  769. def __init__(self, provider):
  770. super(GCESubnetService, self).__init__(provider)
  771. def get(self, subnet_id):
  772. for subnet in self.list():
  773. if subnet.id == subnet_id:
  774. return subnet
  775. return None
  776. def list(self, network=None, zone=None, limit=None, marker=None):
  777. region = zone.region_name if zone else self.provider.region_name
  778. filter = None
  779. if network is not None:
  780. filter = 'network eq %s' % network.resource_url
  781. max_result = limit if limit is not None and limit < 500 else 500
  782. response = (self.provider
  783. .gce_compute
  784. .subnetworks()
  785. .list(project=self.provider.project_name,
  786. region=region,
  787. filter=filter,
  788. maxResults=max_result,
  789. pageToken=marker)
  790. .execute())
  791. subnets = []
  792. for subnet in response.get('items', []):
  793. subnets.append(GCESubnet(self.provider, subnet))
  794. if len(subnets) > max_result:
  795. cb.log.warning('Expected at most %d results; got %d',
  796. max_result, len(subnets))
  797. return ServerPagedResultList('nextPageToken' in response,
  798. response.get('nextPageToken'),
  799. False, data=subnets)
  800. def create(self, network, cidr_block, name=None, zone=None):
  801. """
  802. GCE subnets are regional. The region is inferred from the zone if a
  803. zone is provided; otherwise, the default region, as set in the
  804. provider, is used.
  805. """
  806. if not name:
  807. name = 'subnet-{0}'.format(uuid.uuid4())
  808. region = self.provider.region_name
  809. if isinstance(zone, GCEPlacementZone):
  810. region = zone.region_name
  811. body = {'ipCidrRange': cidr_block,
  812. 'name': name,
  813. 'network': network.resource_url,
  814. 'region': region}
  815. try:
  816. response = (self.provider
  817. .gce_compute
  818. .subnetworks()
  819. .insert(project=self.provider.project_name,
  820. region=region,
  821. body=body)
  822. .execute())
  823. self.provider.wait_for_operation(response, region=region)
  824. if 'error' in response:
  825. return None
  826. subnets = self.list(network, region)
  827. for subnet in subnets:
  828. if subnet.id == response['targetId']:
  829. return subnet
  830. except googleapiclient.errors.HttpError as http_error:
  831. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  832. return None
  833. def get_or_create_default(self, zone=None):
  834. """
  835. Every GCP project comes with a default auto mode VPC network. An auto
  836. mode VPC network has exactly one subnetwork per region. This method
  837. returns the subnetwork of the default network that spans the given
  838. zone.
  839. """
  840. network = self.provider.network.get_or_create_default()
  841. subnets = self.list(network, zone)
  842. if len(subnets) > 1:
  843. cb.log.warning('The default network has more than one subnetwork '
  844. 'in a region')
  845. if len(subnets) > 0:
  846. return subnets[0]
  847. cb.log.warning('The default network has no subnetwork in a region')
  848. return None
  849. def delete(self, subnet):
  850. response = (self.provider
  851. .gce_compute
  852. .subnetworks()
  853. .delete(project=self.provider.project_name,
  854. region=subnet.region,
  855. subnetwork=subnet.name)
  856. .execute())
  857. self._provider.wait_for_operation(response, region=subnet.region)
  858. class GCPStorageService(BaseStorageService):
  859. def __init__(self, provider):
  860. super(GCPStorageService, self).__init__(provider)
  861. # Initialize provider services
  862. self._volume_svc = GCEVolumeService(self.provider)
  863. self._snapshot_svc = GCESnapshotService(self.provider)
  864. self._bucket_svc = GCSBucketService(self.provider)
  865. @property
  866. def volumes(self):
  867. return self._volume_svc
  868. @property
  869. def snapshots(self):
  870. return self._snapshot_svc
  871. @property
  872. def buckets(self):
  873. return self._bucket_svc
  874. class GCEVolumeService(BaseVolumeService):
  875. def __init__(self, provider):
  876. super(GCEVolumeService, self).__init__(provider)
  877. def get(self, volume_id):
  878. """
  879. Returns a volume given its id.
  880. """
  881. try:
  882. return GCEVolume(self.provider,
  883. self.provider.parse_url(volume_id).get_resource())
  884. except googleapiclient.errors.HttpError as http_error:
  885. # If the volume is not found, the API will raise
  886. # googleapiclient.errors.HttpError.
  887. cb.log.warning(
  888. "googleapiclient.errors.HttpError: {0}".format(http_error))
  889. return None
  890. def find(self, name, limit=None, marker=None):
  891. """
  892. Searches for a volume by a given list of attributes.
  893. """
  894. filtr = 'name eq ' + name
  895. max_result = limit if limit is not None and limit < 500 else 500
  896. response = (self.provider
  897. .gce_compute
  898. .disks()
  899. .list(project=self.provider.project_name,
  900. zone=self.provider.default_zone,
  901. filter=filtr,
  902. maxResults=max_result,
  903. pageToken=marker)
  904. .execute())
  905. if 'items' not in response:
  906. return []
  907. gce_vols = [GCEVolume(self.provider, vol)
  908. for vol in response['items']]
  909. if len(gce_vols) > max_result:
  910. cb.log.warning('Expected at most %d results; got %d',
  911. max_result, len(gce_vols))
  912. return ServerPagedResultList('nextPageToken' in response,
  913. response.get('nextPageToken'),
  914. False, data=gce_vols)
  915. def list(self, limit=None, marker=None):
  916. """
  917. List all volumes.
  918. limit: The maximum number of volumes to return. The returned
  919. ResultList's is_truncated property can be used to determine
  920. whether more records are available.
  921. """
  922. # For GCE API, Acceptable values are 0 to 500, inclusive.
  923. # (Default: 500).
  924. max_result = limit if limit is not None and limit < 500 else 500
  925. response = (self.provider
  926. .gce_compute
  927. .disks()
  928. .list(project=self.provider.project_name,
  929. zone=self.provider.default_zone,
  930. maxResults=max_result,
  931. pageToken=marker)
  932. .execute())
  933. if 'items' not in response:
  934. return []
  935. gce_vols = [GCEVolume(self.provider, vol)
  936. for vol in response['items']]
  937. if len(gce_vols) > max_result:
  938. cb.log.warning('Expected at most %d results; got %d',
  939. max_result, len(gce_vols))
  940. return ServerPagedResultList('nextPageToken' in response,
  941. response.get('nextPageToken'),
  942. False, data=gce_vols)
  943. def create(self, name, size, zone, snapshot=None, description=None):
  944. """
  945. Creates a new volume.
  946. Argument `name` must be 1-63 characters long, and comply with RFC1035.
  947. Specifically, the name must be 1-63 characters long and match the
  948. regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first
  949. character must be a lowercase letter, and all following characters must
  950. be a dash, lowercase letter, or digit, except the last character, which
  951. cannot be a dash.
  952. """
  953. zone_name = zone.name if isinstance(zone, PlacementZone) else zone
  954. snapshot_id = snapshot.id if isinstance(
  955. snapshot, GCESnapshot) and snapshot else snapshot
  956. disk_body = {
  957. 'name': name,
  958. 'sizeGb': size,
  959. 'type': 'zones/{0}/diskTypes/{1}'.format(zone_name, 'pd-standard'),
  960. 'sourceSnapshot': snapshot_id,
  961. 'description': description,
  962. }
  963. operation = (self.provider
  964. .gce_compute
  965. .disks()
  966. .insert(
  967. project=self._provider.project_name,
  968. zone=zone_name,
  969. body=disk_body)
  970. .execute())
  971. return self.get(operation.get('targetLink'))
  972. class GCESnapshotService(BaseSnapshotService):
  973. def __init__(self, provider):
  974. super(GCESnapshotService, self).__init__(provider)
  975. def get(self, snapshot_id):
  976. """
  977. Returns a snapshot given its id.
  978. """
  979. try:
  980. return GCESnapshot(
  981. self.provider,
  982. self.provider.parse_url(snapshot_id).get_resource())
  983. except googleapiclient.errors.HttpError as http_error:
  984. # If the volume is not found, the API will raise
  985. # googleapiclient.errors.HttpError.
  986. cb.log.warning(
  987. "googleapiclient.errors.HttpError: {0}".format(http_error))
  988. return None
  989. def find(self, name, limit=None, marker=None):
  990. """
  991. Searches for a snapshot by a given list of attributes.
  992. """
  993. filtr = 'name eq ' + name
  994. max_result = limit if limit is not None and limit < 500 else 500
  995. response = (self.provider
  996. .gce_compute
  997. .snapshots()
  998. .list(project=self.provider.project_name,
  999. filter=filtr,
  1000. maxResults=max_result,
  1001. pageToken=marker)
  1002. .execute())
  1003. if 'items' not in response:
  1004. return []
  1005. snapshots = [GCESnapshot(self.provider, snapshot)
  1006. for snapshot in response['items']]
  1007. if len(snapshots) > max_result:
  1008. cb.log.warning('Expected at most %d results; got %d',
  1009. max_result, len(snapshots))
  1010. return ServerPagedResultList('nextPageToken' in response,
  1011. response.get('nextPageToken'),
  1012. False, data=snapshots)
  1013. def list(self, limit=None, marker=None):
  1014. """
  1015. List all snapshots.
  1016. """
  1017. max_result = limit if limit is not None and limit < 500 else 500
  1018. response = (self.provider
  1019. .gce_compute
  1020. .snapshots()
  1021. .list(project=self.provider.project_name,
  1022. maxResults=max_result,
  1023. pageToken=marker)
  1024. .execute())
  1025. if 'items' not in response:
  1026. return []
  1027. snapshots = [GCESnapshot(self.provider, snapshot)
  1028. for snapshot in response['items']]
  1029. if len(snapshots) > max_result:
  1030. cb.log.warning('Expected at most %d results; got %d',
  1031. max_result, len(snapshots))
  1032. return ServerPagedResultList('nextPageToken' in response,
  1033. response.get('nextPageToken'),
  1034. False, data=snapshots)
  1035. def create(self, name, volume, description=None):
  1036. """
  1037. Creates a new snapshot of a given volume.
  1038. """
  1039. volume_name = volume.name if isinstance(volume, GCEVolume) else volume
  1040. snapshot_body = {
  1041. "name": name,
  1042. "description": description
  1043. }
  1044. operation = (self.provider
  1045. .gce_compute
  1046. .disks()
  1047. .createSnapshot(
  1048. project=self.provider.project_name,
  1049. zone=self.provider.default_zone,
  1050. disk=volume_name, body=snapshot_body)
  1051. .execute())
  1052. if 'zone' not in operation:
  1053. return None
  1054. zone_url = self.provider.parse_url(operation['zone'])
  1055. self.provider.wait_for_operation(operation,
  1056. zone=zone_url.parameters['zone'])
  1057. snapshots = self.provider.block_store.snapshots.find(name=name)
  1058. if snapshots:
  1059. return snapshots[0]
  1060. else:
  1061. return None
  1062. class GCSBucketService(BaseBucketService):
  1063. def __init__(self, provider):
  1064. super(GCSBucketService, self).__init__(provider)
  1065. def get(self, bucket_id):
  1066. """
  1067. Returns a bucket given its ID. Returns ``None`` if the bucket
  1068. does not exist or if the user does not have permission to access the
  1069. bucket.
  1070. """
  1071. try:
  1072. response = (self.provider
  1073. .gcp_storage
  1074. .buckets()
  1075. .get(bucket=bucket_id)
  1076. .execute())
  1077. if 'error' in response:
  1078. # response['error']['code'] is 404 if the bucket does not exist
  1079. # and 403 if the user does not have permission to access it.
  1080. if response['error']['code'] not in (403, 404):
  1081. cb.log.warning('Unexpected error code (%d) when accessing '
  1082. 'bucket %s', response['error']['code'],
  1083. bucket_id)
  1084. return None
  1085. return GCSBucket(self.provider, response)
  1086. except googleapiclient.errors.HttpError as http_error:
  1087. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  1088. return None
  1089. def find(self, name, limit=None, marker=None):
  1090. """
  1091. Searches in bucket names for a substring.
  1092. """
  1093. buckets = [bucket for bucket in self if name in bucket.name]
  1094. return ClientPagedResultList(self.provider, buckets, limit=limit,
  1095. marker=marker)
  1096. def list(self, limit=None, marker=None):
  1097. """
  1098. List all containers.
  1099. """
  1100. max_result = limit if limit is not None and limit < 500 else 500
  1101. try:
  1102. response = (self.provider
  1103. .gcp_storage
  1104. .buckets()
  1105. .list(project=self.provider.project_name,
  1106. maxResults=max_result,
  1107. pageToken=marker)
  1108. .execute())
  1109. if 'error' in response:
  1110. return ServerPagedResultList(False, None, False, data=[])
  1111. buckets = []
  1112. for bucket in response.get('items', []):
  1113. buckets.append(GCSBucket(self.provider, bucket))
  1114. if len(buckets) > max_result:
  1115. cb.log.warning('Expected at most %d results; got %d',
  1116. max_result, len(buckets))
  1117. return ServerPagedResultList('nextPageToken' in response,
  1118. response.get('nextPageToken'),
  1119. False, data=buckets)
  1120. except googleapiclient.errors.HttpError as http_error:
  1121. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  1122. return ServerPagedResultList(False, None, False, data=[])
  1123. def create(self, name, location=None):
  1124. """
  1125. Create a new bucket and returns it. Returns None if creation fails.
  1126. """
  1127. body = {'name': name}
  1128. if location:
  1129. body['location'] = location
  1130. try:
  1131. response = (self.provider
  1132. .gcp_storage
  1133. .buckets()
  1134. .insert(project=self.provider.project_name,
  1135. body=body)
  1136. .execute())
  1137. if 'error' in response:
  1138. return None
  1139. return GCSBucket(self.provider, response)
  1140. except googleapiclient.errors.HttpError as http_error:
  1141. cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
  1142. return None