1# -*- coding: utf-8 -*- #
2# Copyright 2016 Google LLC. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Backend service."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import unicode_literals
20
21from apitools.base.py import batch
22from apitools.base.py import exceptions as apitools_exceptions
23from googlecloudsdk.api_lib.compute import exceptions
24from googlecloudsdk.api_lib.compute import request_helper
25from googlecloudsdk.api_lib.compute import utils
26from googlecloudsdk.api_lib.util import apis as core_apis
27from googlecloudsdk.api_lib.util import exceptions as api_exceptions
28
29from six.moves.urllib import parse
30
31# Upper bound on batch size
32# https://cloud.google.com/compute/docs/api/how-tos/batch
33_BATCH_SIZE_LIMIT = 1000
34
35
36class Error(exceptions.Error):
37  """Errors raised by this module."""
38
39
40def _GetBatchUrl(endpoint_url, api_version):
41  """Return a batch URL for the given endpoint URL."""
42  parsed_endpoint = parse.urlparse(endpoint_url)
43  return parse.urljoin(
44      '{0}://{1}'.format(parsed_endpoint.scheme, parsed_endpoint.netloc),
45      'batch/compute/' + api_version)
46
47
48class ClientAdapter(object):
49  """Encapsulates compute apitools interactions."""
50  _API_NAME = 'compute'
51
52  def __init__(self, api_version=None, no_http=False, client=None):
53    self._api_version = core_apis.ResolveVersion(
54        self._API_NAME, api_version=api_version)
55    self._client = client or core_apis.GetClientInstance(
56        self._API_NAME, self._api_version, no_http=no_http)
57
58    # Turn the endpoint into just the host.
59    # eg. https://compute.googleapis.com/compute/v1 -> https://compute.googleapis.com
60    endpoint_url = core_apis.GetEffectiveApiEndpoint(self._API_NAME,
61                                                     self._api_version)
62    self._batch_url = _GetBatchUrl(endpoint_url, self._api_version)
63
64  @property
65  def api_version(self):
66    return self._api_version
67
68  @property
69  def apitools_client(self):
70    return self._client
71
72  @property
73  def batch_url(self):
74    return self._batch_url
75
76  @property
77  def messages(self):
78    return self._client.MESSAGES_MODULE
79
80  def MakeRequests(self,
81                   requests,
82                   errors_to_collect=None,
83                   progress_tracker=None,
84                   no_followup=False,
85                   always_return_operation=False,
86                   followup_overrides=None,
87                   log_result=True,
88                   timeout=None):
89    """Sends given request in batch mode."""
90    errors = errors_to_collect if errors_to_collect is not None else []
91    objects = list(
92        request_helper.MakeRequests(
93            requests=requests,
94            http=self._client.http,
95            batch_url=self._batch_url,
96            errors=errors,
97            progress_tracker=progress_tracker,
98            no_followup=no_followup,
99            always_return_operation=always_return_operation,
100            followup_overrides=followup_overrides,
101            log_result=log_result,
102            timeout=timeout))
103    if errors_to_collect is None and errors:
104      utils.RaiseToolException(
105          errors, error_message='Could not fetch resource:')
106    return objects
107
108  def BatchRequests(self, requests, errors_to_collect=None):
109    """Issues batch request for given set of requests.
110
111    Args:
112      requests: list(tuple(service, method, payload)), where service is
113        apitools.base.py.base_api.BaseApiService, method is str, method name,
114        e.g. 'Get', 'CreateInstance', payload is a subclass of
115        apitools.base.protorpclite.messages.Message.
116      errors_to_collect: list, output only, can be None, contains instances of
117        api_exceptions.HttpException for each request with exception.
118
119    Returns:
120      list of responses, matching list of requests. Some responses can be
121        errors.
122    """
123    batch_request = batch.BatchApiRequest(batch_url=self._batch_url)
124    for service, method, request in requests:
125      batch_request.Add(service, method, request)
126
127    payloads = batch_request.Execute(
128        self._client.http, max_batch_size=_BATCH_SIZE_LIMIT)
129
130    responses = []
131    errors = errors_to_collect if errors_to_collect is not None else []
132
133    for payload in payloads:
134      if payload.is_error:
135        if isinstance(payload.exception, apitools_exceptions.HttpError):
136          errors.append(api_exceptions.HttpException(payload.exception))
137        else:
138          errors.append(Error(payload.exception.message))
139
140      responses.append(payload.response)
141
142    return responses
143