241 lines
8.3 KiB
Python
241 lines
8.3 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2016 Google LLC. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Constructs to poll compute operations."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
from googlecloudsdk.api_lib.compute import exceptions
|
|
from googlecloudsdk.api_lib.compute import utils
|
|
from googlecloudsdk.api_lib.util import waiter
|
|
from googlecloudsdk.core import exceptions as core_exceptions
|
|
from googlecloudsdk.core import resources
|
|
import six
|
|
from six.moves import zip
|
|
|
|
|
|
class Error(exceptions.Error):
|
|
"""Errors raised by this module."""
|
|
|
|
|
|
class OperationErrors(Error):
|
|
"""Encapsulates multiple errors reported about single operation."""
|
|
|
|
def __init__(self, errors):
|
|
use_construct_list = False
|
|
for error in errors:
|
|
if utils.ShouldUseYaml(error):
|
|
use_construct_list = True
|
|
break
|
|
if use_construct_list:
|
|
formatted_errors = utils.ConstructList(
|
|
title='', items=utils.ParseErrors(errors))
|
|
super(OperationErrors, self).__init__(formatted_errors)
|
|
else:
|
|
messages = [error.message for error in errors]
|
|
super(OperationErrors, self).__init__(', '.join(messages))
|
|
|
|
|
|
class Poller(waiter.OperationPoller):
|
|
"""Compute operations poller."""
|
|
|
|
def __init__(self, resource_service, target_ref=None, has_project=False):
|
|
"""Initializes poller for compute operations.
|
|
|
|
Args:
|
|
resource_service: apitools.base.py.base_api.BaseApiService,
|
|
service representing the target of operation.
|
|
target_ref: Resource, optional reference to the expected target of the
|
|
operation. If not provided operation.targetLink will be used instead.
|
|
has_project: If 'projects' token should be in the target link for
|
|
organization operations.
|
|
"""
|
|
self.resource_service = resource_service
|
|
self.client = resource_service.client
|
|
self.messages = self.client.MESSAGES_MODULE
|
|
self.status_enum = self.messages.Operation.StatusValueValuesEnum
|
|
self.target_ref = target_ref
|
|
self.has_project = has_project
|
|
|
|
def IsDone(self, operation):
|
|
"""Overrides."""
|
|
if operation.error:
|
|
raise OperationErrors(operation.error.errors)
|
|
|
|
return operation.status == self.status_enum.DONE
|
|
|
|
def IsGlobalOrganizationOperation(self, operation_ref):
|
|
return six.text_type(operation_ref.GetCollectionInfo()
|
|
) == 'compute.globalOrganizationOperations'
|
|
|
|
def Poll(self, operation_ref):
|
|
"""Overrides."""
|
|
# For organization operations, need to get the organization ID from the
|
|
# operation name prefixed with 'org-'.
|
|
if self.IsGlobalOrganizationOperation(operation_ref) and hasattr(
|
|
operation_ref, 'operation') and 'org-' in operation_ref.operation:
|
|
service = self.client.globalOrganizationOperations
|
|
token_list = operation_ref.operation.split('-')
|
|
parent_id = 'organizations/' + token_list[1]
|
|
return service.Get(
|
|
service.GetRequestType('Get')(
|
|
operation=operation_ref.operation, parentId=parent_id))
|
|
|
|
if hasattr(operation_ref, 'zone'):
|
|
service = self.client.zoneOperations
|
|
elif hasattr(operation_ref, 'region'):
|
|
service = self.client.regionOperations
|
|
else:
|
|
service = self.client.globalOperations
|
|
return service.Wait(
|
|
service.GetRequestType('Wait')(**operation_ref.AsDict()))
|
|
|
|
def GetResult(self, operation):
|
|
"""Overrides."""
|
|
request_type = self.resource_service.GetRequestType('Get')
|
|
# Organization operation and target URLs have 'locations' and do not
|
|
# have 'projects' token. This is not supported in REGISTRY.Parse(). Simply
|
|
# return None.
|
|
if operation.name and 'org-' in operation.name:
|
|
return None
|
|
else:
|
|
target_ref = (
|
|
self.target_ref or resources.REGISTRY.Parse(operation.targetLink))
|
|
return self.resource_service.Get(request_type(**target_ref.AsDict()))
|
|
|
|
|
|
class DeletePoller(Poller):
|
|
def GetResult(self, operation):
|
|
"""Overrides."""
|
|
# For delete operations, once the operation status is DONE, there is
|
|
# nothing further to fetch.
|
|
return None
|
|
|
|
|
|
class OperationBatch(object):
|
|
"""Wrapper class for a set of batched operations."""
|
|
|
|
def __init__(self, operation_refs):
|
|
self._operation_refs = operation_refs or []
|
|
self._responses = {}
|
|
|
|
def SetResponse(self, operation_ref, response):
|
|
self._responses[operation_ref] = response
|
|
|
|
def GetResponse(self, operation_ref):
|
|
return self._responses.get(operation_ref)
|
|
|
|
def GetWithResponse(self, response_func):
|
|
for op in self._operation_refs:
|
|
if response_func(self._responses.get(op)):
|
|
yield op
|
|
|
|
def __iter__(self):
|
|
return iter(self._operation_refs)
|
|
|
|
def __str__(self):
|
|
return '[{0}]'.format(', '.join(
|
|
six.text_type(r) for r in self._operation_refs))
|
|
|
|
|
|
class BatchPoller(waiter.OperationPoller):
|
|
"""Compute operations batch poller."""
|
|
|
|
def __init__(self, compute_adapter, resource_service, target_refs=None):
|
|
"""Initializes poller for compute operations.
|
|
|
|
Args:
|
|
compute_adapter: googlecloudsdk.api_lib.compute.client_adapter
|
|
.ClientAdapter.
|
|
resource_service: apitools.base.py.base_api.BaseApiService,
|
|
service representing the target of operation.
|
|
target_refs: Resources, optional references to the expected targets of the
|
|
operations. If not provided operation.targetLink will be used instead.
|
|
"""
|
|
self._compute_adapter = compute_adapter
|
|
self._resource_service = resource_service
|
|
self._client = compute_adapter.apitools_client
|
|
self._messages = compute_adapter.messages
|
|
self._status_enum = self._messages.Operation.StatusValueValuesEnum
|
|
self._target_refs = target_refs
|
|
|
|
def IsDone(self, operation_batch):
|
|
"""Overrides."""
|
|
is_done = True
|
|
for operation_ref in operation_batch:
|
|
response = operation_batch.GetResponse(operation_ref)
|
|
is_done = is_done and response.status == self._status_enum.DONE
|
|
return is_done
|
|
|
|
def Poll(self, operation_batch):
|
|
"""Overrides."""
|
|
requests = []
|
|
|
|
not_done = list(operation_batch.GetWithResponse(
|
|
lambda r: r is None or r.status != self._status_enum.DONE))
|
|
for operation_ref in not_done:
|
|
if hasattr(operation_ref, 'zone'):
|
|
service = self._client.zoneOperations
|
|
elif hasattr(operation_ref, 'region'):
|
|
service = self._client.regionOperations
|
|
else:
|
|
service = self._client.globalOperations
|
|
|
|
request_type = service.GetRequestType('Wait')
|
|
requests.append((service, 'Wait', request_type(**operation_ref.AsDict())))
|
|
|
|
errors_to_collect = []
|
|
responses = self._compute_adapter.AsyncRequests(requests, errors_to_collect)
|
|
for response, operation_ref in zip(responses, not_done):
|
|
operation_batch.SetResponse(operation_ref, response)
|
|
if response is not None and response.error:
|
|
errors_to_collect.append(OperationErrors(response.error.errors))
|
|
|
|
if errors_to_collect:
|
|
raise core_exceptions.MultiError(errors_to_collect)
|
|
|
|
return operation_batch
|
|
|
|
def GetResult(self, operation_batch):
|
|
"""Overrides."""
|
|
requests = []
|
|
request_type = self._resource_service.GetRequestType('Get')
|
|
target_refs = (
|
|
self._target_refs or
|
|
[resources.REGISTRY.Parse(
|
|
operation_batch.GetResponse(operation_ref).targetLink)
|
|
for operation_ref in operation_batch])
|
|
|
|
for target_ref in target_refs:
|
|
requests.append((
|
|
self._resource_service,
|
|
'Get',
|
|
request_type(**target_ref.AsDict())))
|
|
|
|
errors_to_collect = []
|
|
responses = self._compute_adapter.AsyncRequests(requests, errors_to_collect)
|
|
if errors_to_collect:
|
|
raise core_exceptions.MultiError(errors_to_collect)
|
|
return responses
|
|
|
|
|
|
class DeleteBatchPoller(BatchPoller):
|
|
|
|
def GetResult(self, operation_batch):
|
|
# For delete operations, once the operation status is DONE, there is
|
|
# nothing further to fetch.
|
|
return
|