224 lines
8.5 KiB
Python
224 lines
8.5 KiB
Python
# Copyright (c) 2017 Platform9 Systems Inc.
|
|
#
|
|
# 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 expressed or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import time
|
|
|
|
from googleapiclient.discovery import build
|
|
from oauth2client.client import GoogleCredentials
|
|
from oslo_log import log as logging
|
|
|
|
from cinder.i18n import _LI, _
|
|
from oslo_service import loopingcall
|
|
from oslo_utils import reflection
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class GceOperationError(Exception):
|
|
pass
|
|
|
|
|
|
class _FixedIntervalWithTimeoutLoopingCall(loopingcall.LoopingCallBase):
|
|
"""A fixed interval looping call with timeout checking mechanism."""
|
|
|
|
_RUN_ONLY_ONE_MESSAGE = _("A fixed interval looping call with timeout"
|
|
" checking and can only run one function at"
|
|
" at a time")
|
|
|
|
_KIND = _('Fixed interval looping call with timeout checking.')
|
|
|
|
def start(self, interval, initial_delay=None, stop_on_exception=True,
|
|
timeout=0):
|
|
start_time = time.time()
|
|
|
|
def _idle_for(result, elapsed):
|
|
delay = round(elapsed - interval, 2)
|
|
if delay > 0:
|
|
func_name = reflection.get_callable_name(self.f)
|
|
LOG.warning('Function %(func_name)r run outlasted '
|
|
'interval by %(delay).2f sec',
|
|
{'func_name': func_name,
|
|
'delay': delay})
|
|
elapsed_time = time.time() - start_time
|
|
if timeout > 0 and elapsed_time > timeout:
|
|
raise loopingcall.LoopingCallTimeOut(
|
|
_('Looping call timed out after %.02f seconds') %
|
|
elapsed_time)
|
|
return -delay if delay < 0 else 0
|
|
|
|
return self._start(_idle_for, initial_delay=initial_delay,
|
|
stop_on_exception=stop_on_exception)
|
|
|
|
|
|
# Currently, default oslo.service version(newton) is 1.16.0.
|
|
# Once we upgrade oslo.service >= 1.19.0, we can remove temporary
|
|
# definition _FixedIntervalWithTimeoutLoopingCall
|
|
if not hasattr(loopingcall, 'FixedIntervalWithTimeoutLoopingCall'):
|
|
loopingcall.FixedIntervalWithTimeoutLoopingCall = \
|
|
_FixedIntervalWithTimeoutLoopingCall
|
|
|
|
|
|
def wait_for_operation(compute, project, operation, interval=1, timeout=60):
|
|
"""Wait for GCE operation to complete, raise error if operation failure
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param zone: string, GCE Name of zone
|
|
:param operation: object, Operation resource obtained by calling GCE API
|
|
:param interval: int, Time period(seconds) between two GCE operation checks
|
|
:param timeout: int, Absoulte time period(seconds) to monitor GCE operation
|
|
"""
|
|
|
|
def watch_operation(name, request):
|
|
result = request.execute()
|
|
if result['status'] == 'DONE':
|
|
LOG.info(
|
|
_LI("Operation %s status is %s") % (name, result['status']))
|
|
if 'error' in result:
|
|
raise GceOperationError(result['error'])
|
|
raise loopingcall.LoopingCallDone()
|
|
|
|
operation_name = operation['name']
|
|
|
|
if 'zone' in operation:
|
|
zone = operation['zone'].split('/')[-1]
|
|
monitor_request = compute.zoneOperations().get(
|
|
project=project, zone=zone, operation=operation_name)
|
|
elif 'region' in operation:
|
|
region = operation['region'].split('/')[-1]
|
|
monitor_request = compute.regionOperations().get(
|
|
project=project, region=region, operation=operation_name)
|
|
else:
|
|
monitor_request = compute.globalOperations().get(
|
|
project=project, operation=operation_name)
|
|
|
|
timer = loopingcall.FixedIntervalWithTimeoutLoopingCall(
|
|
watch_operation, operation_name, monitor_request)
|
|
timer.start(interval=interval, timeout=timeout).wait()
|
|
|
|
|
|
def get_gce_service(service_key):
|
|
"""Returns GCE compute resource object for interacting with GCE API
|
|
:param service_key: string, Path of service key obtained from
|
|
https://console.cloud.google.com/apis/credentials
|
|
:return: :class:`Resource <Resource>` object
|
|
:rtype: googleapiclient.discovery.Resource
|
|
"""
|
|
credentials = GoogleCredentials.from_stream(service_key)
|
|
service = build('compute', 'v1', credentials=credentials)
|
|
return service
|
|
|
|
|
|
def create_disk(compute, project, zone, name, size):
|
|
"""Create disk in GCE
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param zone: string, GCE Name of zone
|
|
:param name: string, GCE disk name
|
|
:param size: int, size of disk inn Gb
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
body = {
|
|
"name": name,
|
|
"zone": "projects/%s/zones/%s" % (project, zone),
|
|
"type": "projects/%s/zones/%s/diskTypes/pd-standard" % (project, zone),
|
|
"sizeGb": size
|
|
}
|
|
return compute.disks().insert(project=project, zone=zone, body=body,
|
|
sourceImage=None).execute()
|
|
|
|
|
|
def delete_disk(compute, project, zone, name):
|
|
"""Delete disk in GCE
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param zone: string, GCE Name of zone
|
|
:param name: string, GCE disk name
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
return compute.disks().delete(project=project, zone=zone,
|
|
disk=name).execute()
|
|
|
|
|
|
def get_disk(compute, project, zone, name):
|
|
"""Get info of disk in GCE
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param zone: string, GCE Name of zone
|
|
:param name: string, GCE disk name
|
|
:return: GCE disk information
|
|
:rtype: dict
|
|
"""
|
|
return compute.disks().get(project=project, zone=zone, disk=name).execute()
|
|
|
|
|
|
def snapshot_disk(compute, project, zone, name, snapshot_name):
|
|
"""Create snapshot of disk in GCE
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param zone: string, GCE Name of zone
|
|
:param name: string, GCE disk name
|
|
:param snapshot_name: string, GCE snapshot name
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
body = {"name": snapshot_name}
|
|
return compute.disks().createSnapshot(project=project, zone=zone,
|
|
disk=name, body=body).execute()
|
|
|
|
|
|
def get_snapshot(compute, project, name):
|
|
"""Get info of snapshot in GCE
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param name: string, GCE snapshot name
|
|
:return: GCE snapshot information
|
|
:rtype: dict
|
|
"""
|
|
return compute.snapshots().get(project=project, snapshot=name).execute()
|
|
|
|
|
|
def delete_snapshot(compute, project, name):
|
|
"""Delete snapshot in GCE
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param name: string, GCE snapshot name
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
return compute.snapshots().delete(project=project, snapshot=name).execute()
|
|
|
|
|
|
def create_disk_from_snapshot(compute, project, zone, name, snapshot_name):
|
|
"""Create disk from snapshot in GCE
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param zone: string, GCE Name of zone
|
|
:param name: string, GCE disk name
|
|
:param snapshot_name: string, GCE snapshot name
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
gce_snapshot = get_snapshot(compute, project, snapshot_name)
|
|
body = {
|
|
"name": name,
|
|
"zone": "projects/%s/zones/%s" % (project, zone),
|
|
"type": "projects/%s/zones/%s/diskTypes/pd-standard" % (project, zone),
|
|
"sourceSnapshot": gce_snapshot["selfLink"],
|
|
"sizeGb": gce_snapshot["diskSizeGb"]
|
|
}
|
|
return compute.disks().insert(project=project, zone=zone, body=body,
|
|
sourceImage=None).execute()
|