Merge "Added Nova Instace Snapshot Operation and security group rules support"
This commit is contained in:
commit
0cf63a5004
|
@ -1,2 +1 @@
|
||||||
google-api-python-client
|
google-api-python-client
|
||||||
oslo.service>=1.19.0
|
|
||||||
|
|
|
@ -12,12 +12,15 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
from oauth2client.client import GoogleCredentials
|
from oauth2client.client import GoogleCredentials
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from cinder.i18n import _LI
|
from cinder.i18n import _LI, _
|
||||||
from oslo_service import loopingcall
|
from oslo_service import loopingcall
|
||||||
|
from oslo_utils import reflection
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -26,6 +29,46 @@ class GceOperationError(Exception):
|
||||||
pass
|
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):
|
def wait_for_operation(compute, project, operation, interval=1, timeout=60):
|
||||||
"""Wait for GCE operation to complete, raise error if operation failure
|
"""Wait for GCE operation to complete, raise error if operation failure
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
|
|
@ -12,8 +12,6 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import six
|
|
||||||
import time
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
|
@ -22,239 +20,6 @@ from oauth2client.client import GoogleCredentials
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def list_instances(compute, project, zone):
|
|
||||||
"""Returns list of GCE instance resources for specified project
|
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
||||||
:param project: string, GCE Project Id
|
|
||||||
:param zone: string, GCE Name of zone
|
|
||||||
"""
|
|
||||||
result = compute.instances().list(project=project, zone=zone).execute()
|
|
||||||
if 'items' not in result:
|
|
||||||
return []
|
|
||||||
return result['items']
|
|
||||||
|
|
||||||
|
|
||||||
def get_instance(compute, project, zone, instance):
|
|
||||||
"""Get GCE instance information
|
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
||||||
:param project: string, GCE Project Id
|
|
||||||
:param zone: string, GCE Name of zone
|
|
||||||
:param instance: string, Name of the GCE instance resource
|
|
||||||
"""
|
|
||||||
result = compute.instances().get(project=project, zone=zone,
|
|
||||||
instance=instance).execute()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def get_instance_metadata(compute, project, zone, instance):
|
|
||||||
"""Returns specified instance's metadata
|
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
||||||
:param project: string, GCE Project Id
|
|
||||||
:param zone: string, GCE Name of zone
|
|
||||||
:param instance: string or instance resource, Name of the GCE instance
|
|
||||||
resource or GCE instance resource
|
|
||||||
"""
|
|
||||||
if isinstance(instance, six.string_types):
|
|
||||||
instance = get_instance(compute, project, zone, instance)
|
|
||||||
return instance['metadata']
|
|
||||||
|
|
||||||
|
|
||||||
def get_instances_metadata_key(compute, project, zone, instance, key):
|
|
||||||
"""Returns particular key information for specified instance
|
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
||||||
:param project: string, GCE Project Id
|
|
||||||
:param zone: string, GCE Name of zone
|
|
||||||
:param instance: string or instance resource, Name of the GCE instance
|
|
||||||
resource or GCE instance resource
|
|
||||||
:param key: string, Key to retrieved from the instance metadata
|
|
||||||
"""
|
|
||||||
metadata = get_instance_metadata(compute, project, zone, instance)
|
|
||||||
if 'items' in metadata:
|
|
||||||
for item in metadata['items']:
|
|
||||||
if item['key'] == key:
|
|
||||||
return item['value']
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_external_ip(compute, project, zone, instance):
|
|
||||||
""" Return external IP of GCE instance return empty string otherwise
|
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
||||||
:param project: string, GCE Project Id
|
|
||||||
:param zone: string, GCE Name of zone
|
|
||||||
:param instance: string or instance resource, Name of the GCE instance
|
|
||||||
resource or GCE instance resource
|
|
||||||
"""
|
|
||||||
if isinstance(instance, six.string_types):
|
|
||||||
instance = get_instance(compute, project, zone, instance)
|
|
||||||
for interface in instance.get('networkInterfaces', []):
|
|
||||||
for config in interface.get('accessConfigs', []):
|
|
||||||
if config['type'] == 'ONE_TO_ONE_NAT' and 'natIP' in config:
|
|
||||||
return config['natIP']
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def set_instance_metadata(compute, project, zone, instance, items,
|
|
||||||
operation='add'):
|
|
||||||
"""Perform specified operation on GCE instance metadata
|
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
||||||
:param project: string, GCE Project Id
|
|
||||||
:param zone: string, GCE Name of zone
|
|
||||||
:param instance: string or instance resource, Name of the GCE instance
|
|
||||||
resource or GCE instance resource
|
|
||||||
:param items: list, List of items where each item is dictionary having
|
|
||||||
'key' and 'value' as its members
|
|
||||||
Refer following sample list,
|
|
||||||
[ {'key': 'openstack_id', 'value': '1224555'}, ]
|
|
||||||
:param operation: string, Operation to perform on instance metadata
|
|
||||||
"""
|
|
||||||
if not isinstance(items, list):
|
|
||||||
raise TypeError(
|
|
||||||
"set_instance_metadata: items should be instance of list")
|
|
||||||
metadata = get_instance_metadata(compute, project, zone, instance)
|
|
||||||
if operation == 'add':
|
|
||||||
if 'items' in metadata:
|
|
||||||
metadata['items'].extend(items)
|
|
||||||
else:
|
|
||||||
metadata['items'] = items
|
|
||||||
LOG.info("Adding metadata %s" % (metadata, ))
|
|
||||||
# TODO: Add del operation if required
|
|
||||||
return compute.instances().setMetadata(project=project, zone=zone,
|
|
||||||
instance=instance,
|
|
||||||
body=metadata).execute()
|
|
||||||
|
|
||||||
|
|
||||||
def create_instance(compute, project, zone, name, image_link, machine_link):
|
|
||||||
"""Create GCE instance
|
|
||||||
: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, Name of instance to be launched
|
|
||||||
:param image_link: url, GCE Image link for instance launch
|
|
||||||
:param machine_link: url, GCE Machine link for instance launch
|
|
||||||
"""
|
|
||||||
# source_disk_image = "projects/%s/global/images/%s" % (
|
|
||||||
# "debian-cloud", "debian-8-jessie-v20170327")
|
|
||||||
# machine_link = "zones/%s/machineTypes/n1-standard-1" % zone
|
|
||||||
LOG.info("Launching instance %s with image %s and machine %s" %
|
|
||||||
(name, image_link, machine_link))
|
|
||||||
|
|
||||||
config = {
|
|
||||||
'kind':
|
|
||||||
'compute#instance',
|
|
||||||
'name':
|
|
||||||
name,
|
|
||||||
'machineType':
|
|
||||||
machine_link,
|
|
||||||
|
|
||||||
# Specify the boot disk and the image to use as a source.
|
|
||||||
'disks': [{
|
|
||||||
'boot': True,
|
|
||||||
'autoDelete': True,
|
|
||||||
'initializeParams': {
|
|
||||||
'sourceImage': image_link,
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
|
|
||||||
# Specify a network interface with NAT to access the public
|
|
||||||
# internet.
|
|
||||||
'networkInterfaces': [{
|
|
||||||
'network':
|
|
||||||
'global/networks/default',
|
|
||||||
'accessConfigs': [{
|
|
||||||
'type': 'ONE_TO_ONE_NAT',
|
|
||||||
'name': 'External NAT'
|
|
||||||
}]
|
|
||||||
}],
|
|
||||||
|
|
||||||
# Allow the instance to access cloud storage and logging.
|
|
||||||
'serviceAccounts': [{
|
|
||||||
'email':
|
|
||||||
'default',
|
|
||||||
'scopes': [
|
|
||||||
'https://www.googleapis.com/auth/devstorage.full_control',
|
|
||||||
'https://www.googleapis.com/auth/logging.write',
|
|
||||||
'https://www.googleapis.com/auth/compute'
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
|
|
||||||
return compute.instances().insert(project=project, zone=zone,
|
|
||||||
body=config).execute()
|
|
||||||
|
|
||||||
|
|
||||||
def delete_instance(compute, project, zone, name):
|
|
||||||
"""Delete GCE instance
|
|
||||||
: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, Name of the GCE instance
|
|
||||||
"""
|
|
||||||
return compute.instances().delete(project=project, zone=zone,
|
|
||||||
instance=name).execute()
|
|
||||||
|
|
||||||
|
|
||||||
def stop_instance(compute, project, zone, name):
|
|
||||||
"""Stop GCE instance
|
|
||||||
: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, Name of the GCE instance
|
|
||||||
"""
|
|
||||||
return compute.instances().stop(project=project, zone=zone,
|
|
||||||
instance=name).execute()
|
|
||||||
|
|
||||||
|
|
||||||
def start_instance(compute, project, zone, name):
|
|
||||||
"""Start GCE instance
|
|
||||||
: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, Name of the GCE instance
|
|
||||||
"""
|
|
||||||
return compute.instances().start(project=project, zone=zone,
|
|
||||||
instance=name).execute()
|
|
||||||
|
|
||||||
|
|
||||||
def reset_instance(compute, project, zone, name):
|
|
||||||
"""Hard reset GCE instance
|
|
||||||
: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, Name of the GCE instance
|
|
||||||
"""
|
|
||||||
return compute.instances().reset(project=project, zone=zone,
|
|
||||||
instance=name).execute()
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_operation(compute, project, zone, 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
|
|
||||||
"""
|
|
||||||
operation_name = operation['name']
|
|
||||||
if interval < 1:
|
|
||||||
raise ValueError("wait_for_operation: Interval should be positive")
|
|
||||||
iterations = timeout / interval
|
|
||||||
for i in range(iterations):
|
|
||||||
result = compute.zoneOperations().get(
|
|
||||||
project=project, zone=zone, operation=operation_name).execute()
|
|
||||||
if result['status'] == 'DONE':
|
|
||||||
LOG.info("Operation %s status is %s" % (operation_name,
|
|
||||||
result['status']))
|
|
||||||
if 'error' in result:
|
|
||||||
raise Exception(result['error'])
|
|
||||||
return result
|
|
||||||
time.sleep(interval)
|
|
||||||
raise Exception(
|
|
||||||
"wait_for_operation: Operation %s failed to perform in timeout %s" %
|
|
||||||
(operation_name, timeout))
|
|
||||||
|
|
||||||
|
|
||||||
def get_gce_service(service_key):
|
def get_gce_service(service_key):
|
||||||
"""Returns GCE compute resource object for interacting with GCE API
|
"""Returns GCE compute resource object for interacting with GCE API
|
||||||
:param service_key: string, Path of service key obtained from
|
:param service_key: string, Path of service key obtained from
|
||||||
|
@ -265,24 +30,6 @@ def get_gce_service(service_key):
|
||||||
return service
|
return service
|
||||||
|
|
||||||
|
|
||||||
def get_machines_info(compute, project, zone):
|
|
||||||
"""Return machine type info from GCE
|
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
||||||
:param project: string, GCE Project Id
|
|
||||||
:param zone: string, GCE Name of zone
|
|
||||||
"""
|
|
||||||
response = compute.machineTypes().list(project=project,
|
|
||||||
zone=zone).execute()
|
|
||||||
GCE_MAP = {
|
|
||||||
machine_type['name']: {
|
|
||||||
'memory_mb': machine_type['memoryMb'],
|
|
||||||
'vcpus': machine_type['guestCpus']
|
|
||||||
}
|
|
||||||
for machine_type in response['items']
|
|
||||||
}
|
|
||||||
return GCE_MAP
|
|
||||||
|
|
||||||
|
|
||||||
def get_images(compute, project):
|
def get_images(compute, project):
|
||||||
"""Return public images info from GCE
|
"""Return public images info from GCE
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
|
|
@ -13,10 +13,13 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
import time
|
||||||
|
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
|
from googleapiclient.errors import HttpError
|
||||||
from oauth2client.client import GoogleCredentials
|
from oauth2client.client import GoogleCredentials
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import reflection
|
||||||
|
|
||||||
from neutron_lib import exceptions as e
|
from neutron_lib import exceptions as e
|
||||||
from neutron._i18n import _LI, _
|
from neutron._i18n import _LI, _
|
||||||
|
@ -26,6 +29,46 @@ from six.moves import urllib
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
class GceOperationError(Exception):
|
class GceOperationError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -333,3 +376,51 @@ def release_floatingip(compute, project, zone, floatingip):
|
||||||
accessConfig=accessconfig['name'],
|
accessConfig=accessconfig['name'],
|
||||||
networkInterface=interface['name']).execute()
|
networkInterface=interface['name']).execute()
|
||||||
wait_for_operation(compute, project, operation)
|
wait_for_operation(compute, project, operation)
|
||||||
|
|
||||||
|
|
||||||
|
def create_firewall_rule(compute, project, body):
|
||||||
|
"""Create firewall rule in GCE
|
||||||
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
:param project: string, GCE Project Id
|
||||||
|
:param body: dict, Information required for creating firewall
|
||||||
|
Refer format at https://developers.google.com/resources/api-libraries/documentation/compute/beta/python/latest/compute_beta.firewalls.html#insert
|
||||||
|
:return: Operation information
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return compute.firewalls().insert(project=project, body=body).execute()
|
||||||
|
|
||||||
|
|
||||||
|
def update_firewall_rule(compute, project, name, body):
|
||||||
|
"""Update existing firewall rule in GCE
|
||||||
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
:param project: string, GCE Project Id
|
||||||
|
:param name: string, GCE firewall name
|
||||||
|
:param body: dict, Information required for updating firewall
|
||||||
|
Refer format at https://developers.google.com/resources/api-libraries/documentation/compute/beta/python/latest/compute_beta.firewalls.html#update
|
||||||
|
:return: Operation information
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return compute.firewalls().update(project=project, firewall=name,
|
||||||
|
body=body).execute()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_firewall_rule(compute, project, name):
|
||||||
|
"""Delete firewall rule in GCE
|
||||||
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
:param project: string, GCE Project Id
|
||||||
|
:param name: string, GCE firewall name
|
||||||
|
:return: Operation information
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return compute.firewalls().delete(project=project, firewall=name).execute()
|
||||||
|
|
||||||
|
|
||||||
|
def get_firewall_rule(compute, project, name):
|
||||||
|
"""Get firewall rule info in GCE
|
||||||
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
:param project: string, GCE Project Id
|
||||||
|
:param name: string, GCE firewall name
|
||||||
|
:return: Firewall info
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return compute.firewalls().get(project=project, firewall=name).execute()
|
||||||
|
|
|
@ -17,14 +17,25 @@ import random
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
import ipaddr
|
import ipaddr
|
||||||
|
from neutron._i18n import _LI, _
|
||||||
|
from neutron.callbacks import events
|
||||||
|
from neutron.callbacks import registry
|
||||||
|
from neutron.callbacks import resources
|
||||||
from neutron.common import gceconf
|
from neutron.common import gceconf
|
||||||
from neutron.common import gceutils
|
from neutron.common import gceutils
|
||||||
from neutron._i18n import _LI
|
from neutron.manager import NeutronManager
|
||||||
from neutron.plugins.ml2 import driver_api as api
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
from neutron_lib import exceptions as e
|
||||||
|
from neutron.extensions import securitygroup as sg
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityGroupInvalidDirection(e.InvalidInput):
|
||||||
|
message = _("Security group rule for direction '%(direction)s' not "
|
||||||
|
"supported. Allowed values are %(values)s.")
|
||||||
|
|
||||||
|
|
||||||
class GceMechanismDriver(api.MechanismDriver):
|
class GceMechanismDriver(api.MechanismDriver):
|
||||||
"""Ml2 Mechanism driver for GCE"""
|
"""Ml2 Mechanism driver for GCE"""
|
||||||
|
|
||||||
|
@ -40,6 +51,18 @@ class GceMechanismDriver(api.MechanismDriver):
|
||||||
LOG.info(
|
LOG.info(
|
||||||
_LI("GCE Mechanism driver init with %s project, %s region") %
|
_LI("GCE Mechanism driver init with %s project, %s region") %
|
||||||
(self.gce_project, self.gce_region))
|
(self.gce_project, self.gce_region))
|
||||||
|
self._subscribe_events()
|
||||||
|
|
||||||
|
def _subscribe_events(self):
|
||||||
|
registry.subscribe(self.secgroup_callback, resources.SECURITY_GROUP,
|
||||||
|
events.BEFORE_DELETE)
|
||||||
|
registry.subscribe(self.secgroup_callback, resources.SECURITY_GROUP,
|
||||||
|
events.BEFORE_UPDATE)
|
||||||
|
|
||||||
|
registry.subscribe(self.secgroup_callback,
|
||||||
|
resources.SECURITY_GROUP_RULE, events.BEFORE_DELETE)
|
||||||
|
registry.subscribe(self.secgroup_callback,
|
||||||
|
resources.SECURITY_GROUP_RULE, events.BEFORE_UPDATE)
|
||||||
|
|
||||||
def _gce_network_name(self, context):
|
def _gce_network_name(self, context):
|
||||||
return 'net-' + context.current[api.ID]
|
return 'net-' + context.current[api.ID]
|
||||||
|
@ -116,12 +139,175 @@ class GceMechanismDriver(api.MechanismDriver):
|
||||||
LOG.info(
|
LOG.info(
|
||||||
_LI("Deleted subnet %s in region %s on GCE") % (name, region))
|
_LI("Deleted subnet %s in region %s on GCE") % (name, region))
|
||||||
|
|
||||||
|
def _gce_secgrp_id(self, openstack_id):
|
||||||
|
return "secgrp-" + openstack_id
|
||||||
|
|
||||||
|
def _convert_secgrp_rule_to_gce(self, rule, network_link):
|
||||||
|
if rule['ethertype'] != 'IPv4':
|
||||||
|
raise sg.SecurityGroupRuleInvalidEtherType(
|
||||||
|
ethertype=rule['ethertype'], values=('IPv4', ))
|
||||||
|
|
||||||
|
gce_rule = {
|
||||||
|
'sourceRanges': [],
|
||||||
|
'sourceTags': [],
|
||||||
|
'targetTags': [],
|
||||||
|
'allowed': [{}],
|
||||||
|
'destinationRanges': [],
|
||||||
|
}
|
||||||
|
gce_rule['name'] = self._gce_secgrp_id(rule['id'])
|
||||||
|
gce_rule['network'] = network_link
|
||||||
|
|
||||||
|
directions = {
|
||||||
|
'ingress': 'INGRESS',
|
||||||
|
}
|
||||||
|
gce_protocols = ('tcp', 'udp', 'icmp', 'esp', 'ah', 'sctp')
|
||||||
|
|
||||||
|
if rule['direction'] in directions:
|
||||||
|
gce_rule['direction'] = directions[rule['direction']]
|
||||||
|
else:
|
||||||
|
raise SecurityGroupInvalidDirection(direction=rule['direction'],
|
||||||
|
values=directions.keys())
|
||||||
|
|
||||||
|
protocol = rule['protocol']
|
||||||
|
if protocol is None:
|
||||||
|
gce_rule['allowed'][0]['IPProtocol'] = 'all'
|
||||||
|
elif protocol in gce_protocols:
|
||||||
|
gce_rule['allowed'][0]['IPProtocol'] = protocol
|
||||||
|
# GCE allows port specification for tcp and udp only
|
||||||
|
if protocol in ('tcp', 'udp'):
|
||||||
|
ports = []
|
||||||
|
port_range_max = rule['port_range_max']
|
||||||
|
port_range_min = rule['port_range_min']
|
||||||
|
if port_range_max is None or port_range_min is None:
|
||||||
|
ports.append('0-65535')
|
||||||
|
elif port_range_max == port_range_min:
|
||||||
|
ports.append(str(port_range_max))
|
||||||
|
else:
|
||||||
|
ports.append("%s-%s" % (port_range_min, port_range_max))
|
||||||
|
gce_rule['allowed'][0]['ports'] = ports
|
||||||
|
else:
|
||||||
|
raise sg.SecurityGroupRuleInvalidProtocol(protocol=protocol,
|
||||||
|
values=gce_protocols)
|
||||||
|
|
||||||
|
if rule['remote_ip_prefix'] is None:
|
||||||
|
gce_rule['sourceRanges'].append('0.0.0.0/0')
|
||||||
|
else:
|
||||||
|
gce_rule['sourceRanges'].append(rule['remote_ip_prefix'])
|
||||||
|
return gce_rule
|
||||||
|
|
||||||
|
def _create_secgrp_rule(self, context, rule, network_link):
|
||||||
|
compute, project = self.gce_svc, self.gce_project
|
||||||
|
try:
|
||||||
|
gce_rule = self._convert_secgrp_rule_to_gce(rule, network_link)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(
|
||||||
|
"An error occured while creating security group: %s" % e)
|
||||||
|
return
|
||||||
|
LOG.info(_LI("Create GCE firewall rule %s") % gce_rule)
|
||||||
|
operation = gceutils.create_firewall_rule(compute, project, gce_rule)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
|
||||||
|
def _update_secgrp_rule(self, context, rule_id):
|
||||||
|
compute, project = self.gce_svc, self.gce_project
|
||||||
|
name = self._gce_secgrp_id(rule_id)
|
||||||
|
try:
|
||||||
|
gce_firewall_info = gceutils.get_firewall_rule(
|
||||||
|
compute, project, name)
|
||||||
|
except gceutils.HttpError:
|
||||||
|
return
|
||||||
|
|
||||||
|
core_plugin = NeutronManager.get_plugin()
|
||||||
|
rule = core_plugin.get_security_group_rule(context, rule_id)
|
||||||
|
|
||||||
|
network_link = gce_firewall_info['network']
|
||||||
|
try:
|
||||||
|
gce_rule = self._convert_secgrp_rule_to_gce(rule, network_link)
|
||||||
|
LOG.info(_LI("Update GCE firewall rule %s") % name)
|
||||||
|
operation = gceutils.update_firewall_rule(compute, project, name,
|
||||||
|
gce_rule)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(
|
||||||
|
_LI("An error occured while updating security group: %s") % e)
|
||||||
|
LOG.error(_LI("Deleting existing GCE firewall rule %s") % name)
|
||||||
|
operation = gceutils.delete_firewall_rule(compute, project, name)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
|
||||||
|
def _delete_secgrp_rule(self, context, rule_id):
|
||||||
|
name = self._gce_secgrp_id(rule_id)
|
||||||
|
compute, project = self.gce_svc, self.gce_project
|
||||||
|
try:
|
||||||
|
LOG.warn(
|
||||||
|
_LI("Delete existing GCE firewall rule %s,"
|
||||||
|
"as firewall rule update not GCE compatible.") % name)
|
||||||
|
operation = gceutils.delete_firewall_rule(compute, project, name)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
except gceutils.HttpError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _create_secgrp_rules_if_needed(self, context, secgrp_ids):
|
||||||
|
core_plugin = NeutronManager.get_plugin()
|
||||||
|
secgrp_rules = []
|
||||||
|
for secgrp_id in secgrp_ids:
|
||||||
|
secgrp = core_plugin.get_security_group(context._plugin_context,
|
||||||
|
secgrp_id)
|
||||||
|
secgrp_rules.extend(secgrp['security_group_rules'])
|
||||||
|
if secgrp_rules:
|
||||||
|
network_name = self._gce_subnet_network_name(context)
|
||||||
|
compute, project = self.gce_svc, self.gce_project
|
||||||
|
network = gceutils.get_network(compute, project, network_name)
|
||||||
|
network_link = network['selfLink']
|
||||||
|
for secgrp_rule in secgrp_rules:
|
||||||
|
try:
|
||||||
|
gce_rule_name = self._gce_secgrp_id(secgrp_rule['id'])
|
||||||
|
gceutils.get_firewall_rule(compute, project, gce_rule_name)
|
||||||
|
except gceutils.HttpError:
|
||||||
|
self._create_secgrp_rule(context, secgrp_rule,
|
||||||
|
network_link)
|
||||||
|
|
||||||
|
def _update_secgrp(self, context, secgrp_id):
|
||||||
|
core_plugin = NeutronManager.get_plugin()
|
||||||
|
secgrp = core_plugin.get_security_group(context, secgrp_id)
|
||||||
|
secgrp_rules = secgrp['security_group_rules']
|
||||||
|
for secgrp_rule in secgrp_rules:
|
||||||
|
self._update_secgrp_rule(context, secgrp_rule['id'])
|
||||||
|
|
||||||
|
def _delete_secgrp(self, context, secgrp_id):
|
||||||
|
core_plugin = NeutronManager.get_plugin()
|
||||||
|
secgrp = core_plugin.get_security_group(context, secgrp_id)
|
||||||
|
secgrp_rules = secgrp['security_group_rules']
|
||||||
|
for secgrp_rule in secgrp_rules:
|
||||||
|
self._delete_secgrp_rule(context, secgrp_rule['id'])
|
||||||
|
|
||||||
def bind_port(self, context):
|
def bind_port(self, context):
|
||||||
fixed_ip_dict = dict()
|
fixed_ip_dict = dict()
|
||||||
if 'fixed_ips' in context.current:
|
if 'fixed_ips' in context.current:
|
||||||
if len(context.current['fixed_ips']) > 0:
|
if len(context.current['fixed_ips']):
|
||||||
fixed_ip_dict = context.current['fixed_ips'][0]
|
fixed_ip_dict = context.current['fixed_ips'][0]
|
||||||
|
secgrp_ids = context.current['security_groups']
|
||||||
|
if secgrp_ids:
|
||||||
|
self._create_secgrp_rules_if_needed(context, secgrp_ids)
|
||||||
|
|
||||||
segment_id = random.choice(context.segments_to_bind)[api.ID]
|
segment_id = random.choice(context.segments_to_bind)[api.ID]
|
||||||
context.set_binding(segment_id, "vip_type_a", fixed_ip_dict,
|
context.set_binding(segment_id, "vip_type_a", fixed_ip_dict,
|
||||||
status='ACTIVE')
|
status='ACTIVE')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def secgroup_callback(self, resource, event, trigger, **kwargs):
|
||||||
|
if resource == resources.SECURITY_GROUP_RULE:
|
||||||
|
context = kwargs['context']
|
||||||
|
if event == events.BEFORE_DELETE:
|
||||||
|
rule_id = kwargs['security_group_rule_id']
|
||||||
|
self._delete_secgrp_rule(context, rule_id)
|
||||||
|
elif event == events.BEFORE_UPDATE:
|
||||||
|
rule_id = kwargs['security_group_rule_id']
|
||||||
|
self._update_secgrp_rule(context, rule_id)
|
||||||
|
elif resource == resources.SECURITY_GROUP:
|
||||||
|
if event == events.BEFORE_DELETE:
|
||||||
|
context = kwargs['context']
|
||||||
|
security_group_id = kwargs.get('security_group_id')
|
||||||
|
if security_group_id:
|
||||||
|
self._delete_secgrp(context, security_group_id)
|
||||||
|
else:
|
||||||
|
LOG.warn(
|
||||||
|
_LI("Security group ID not found in delete request"))
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
google-api-python-client
|
google-api-python-client
|
||||||
ipaddr
|
ipaddr
|
||||||
oslo.service>=1.19.0
|
|
||||||
|
|
|
@ -15,20 +15,23 @@
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import uuid
|
import uuid
|
||||||
|
import time
|
||||||
|
|
||||||
import nova.conf
|
import nova.conf
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
from nova.image import glance
|
||||||
from nova.i18n import _LI
|
from nova.i18n import _LI
|
||||||
from nova.virt import driver, hardware
|
from nova.virt import driver, hardware
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from nova.compute import task_states
|
||||||
|
|
||||||
import gceutils
|
import gceutils
|
||||||
|
from googleapiclient.errors import HttpError
|
||||||
|
|
||||||
from nova.virt.gce.constants import GCE_STATE_MAP
|
from nova.virt.gce.constants import GCE_STATE_MAP
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
gce_group = cfg.OptGroup(name='GCE',
|
gce_group = cfg.OptGroup(name='GCE',
|
||||||
title='Options to connect to Google cloud')
|
title='Options to connect to Google cloud')
|
||||||
|
|
||||||
|
@ -227,11 +230,12 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
attached to the instance.
|
attached to the instance.
|
||||||
"""
|
"""
|
||||||
compute, project, zone = self.gce_svc, self.gce_project, self.gce_zone
|
compute, project, zone = self.gce_svc, self.gce_project, self.gce_zone
|
||||||
# TODO: Use instance id as instance name
|
# GCE expects instance name in format "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
|
||||||
instance_name = instance.display_name
|
# So we need to construct it for GCE from uuid
|
||||||
|
gce_instance_name = 'inst-' + instance.uuid
|
||||||
LOG.info(
|
LOG.info(
|
||||||
_LI("Creating instance %s as %s on GCE.") % (instance.display_name,
|
_LI("Creating instance %s as %s on GCE.") % (instance.display_name,
|
||||||
instance_name))
|
gce_instance_name))
|
||||||
# Image Info
|
# Image Info
|
||||||
image_link = instance.system_metadata['image_gce_link']
|
image_link = instance.system_metadata['image_gce_link']
|
||||||
# Flavor Info
|
# Flavor Info
|
||||||
|
@ -241,11 +245,11 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
network_interfaces = self._process_network_info(network_info)
|
network_interfaces = self._process_network_info(network_info)
|
||||||
# Create Instance
|
# Create Instance
|
||||||
operation = gceutils.create_instance(compute, project, zone,
|
operation = gceutils.create_instance(compute, project, zone,
|
||||||
instance_name, image_link,
|
gce_instance_name, image_link,
|
||||||
flavor_link, network_interfaces)
|
flavor_link, network_interfaces)
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
gce_instance = gceutils.get_instance(compute, project, zone,
|
gce_instance = gceutils.get_instance(compute, project, zone,
|
||||||
instance_name)
|
gce_instance_name)
|
||||||
# Update GCE info in openstack instance metadata
|
# Update GCE info in openstack instance metadata
|
||||||
instance.metadata.update({'gce_id': gce_instance['name']})
|
instance.metadata.update({'gce_id': gce_instance['name']})
|
||||||
gce_metadata = [
|
gce_metadata = [
|
||||||
|
@ -260,17 +264,161 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
operation = gceutils.set_instance_metadata(
|
operation = gceutils.set_instance_metadata(
|
||||||
compute, project, zone, gce_instance['name'], gce_metadata,
|
compute, project, zone, gce_instance['name'], gce_metadata,
|
||||||
operation='add')
|
operation='add')
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
self._uuid_to_gce_instance[instance.uuid] = gceutils.get_instance(
|
self._uuid_to_gce_instance[instance.uuid] = gceutils.get_instance(
|
||||||
compute, project, zone, instance_name)
|
compute, project, zone, gce_instance_name)
|
||||||
|
|
||||||
def snapshot(self, context, instance, image_id, update_task_state):
|
def snapshot(self, context, instance, image_id, update_task_state):
|
||||||
"""Snapshot an image of the specified instance
|
"""Snapshot an image of the specified instance
|
||||||
:param context: security context
|
:param context: security context
|
||||||
:param instance: nova.objects.instance.Instance
|
:param instance: nova.objects.instance.Instance
|
||||||
:param image_id: Reference to a pre-created image holding the snapshot.
|
:param image_id: Reference to a pre-created image holding the snapshot.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
1. Find boot disk
|
||||||
|
2. Stop instance
|
||||||
|
3. Create temporary boot disk snapshot
|
||||||
|
4. Start instance
|
||||||
|
5. Create temporary disk from snapshot
|
||||||
|
6. Create image from disk
|
||||||
|
7. Add Image info to glance
|
||||||
|
8. Delete temporary disk
|
||||||
|
9. Delete temporary snapshot
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
instance_stopped = False
|
||||||
|
temp_disk_snapshot = False
|
||||||
|
temp_disk_from_snapshot = False
|
||||||
|
image_created = False
|
||||||
|
|
||||||
|
compute, project, zone = self.gce_svc, self.gce_project, self.gce_zone
|
||||||
|
|
||||||
|
try:
|
||||||
|
gce_id = self._get_gce_id_from_instance(instance)
|
||||||
|
LOG.info(_LI("Taking snapshot of instance %s") % instance.uuid)
|
||||||
|
try:
|
||||||
|
boot_disk = gceutils.get_instance_boot_disk(
|
||||||
|
compute, project, zone, gce_id)
|
||||||
|
except AssertionError:
|
||||||
|
reason = "Unable to find boot disk from instance metadata %s" % instance.uuid
|
||||||
|
raise exception.InvalidMetadata(reason=reason)
|
||||||
|
disk_name = boot_disk['name']
|
||||||
|
LOG.debug(
|
||||||
|
_LI("1. Found boot disk %s for instance %s") % (disk_name,
|
||||||
|
instance.uuid))
|
||||||
|
|
||||||
|
operation = gceutils.stop_instance(compute, project, zone, gce_id)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
instance_stopped = True
|
||||||
|
LOG.debug(
|
||||||
|
_LI("2. Temporarily stopped instance %s") % instance.uuid)
|
||||||
|
|
||||||
|
snapshot_name = 'novasnap-' + disk_name + time.strftime("%s")
|
||||||
|
operation = gceutils.snapshot_disk(
|
||||||
|
compute, project, zone, boot_disk['name'], snapshot_name)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
temp_disk_snapshot = True
|
||||||
|
LOG.debug(_LI("3. Created boot disk snapshot %s") % snapshot_name)
|
||||||
|
|
||||||
|
operation = gceutils.start_instance(compute, project, zone, gce_id)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
instance_stopped = False
|
||||||
|
LOG.debug(
|
||||||
|
_LI("4. Restart instance after disk snapshot %s") %
|
||||||
|
instance.uuid)
|
||||||
|
|
||||||
|
snapshot_disk_name = 'vol-' + snapshot_name
|
||||||
|
operation = gceutils.create_disk_from_snapshot(
|
||||||
|
compute, project, zone, snapshot_disk_name, snapshot_name)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
snapshot_disk_info = gceutils.get_disk(compute, project, zone,
|
||||||
|
snapshot_disk_name)
|
||||||
|
temp_disk_from_snapshot = True
|
||||||
|
LOG.debug(
|
||||||
|
_LI("5. Created disk %s from snapshot %s") %
|
||||||
|
(snapshot_disk_name, snapshot_name))
|
||||||
|
|
||||||
|
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
|
||||||
|
image_api = glance.get_default_image_service()
|
||||||
|
image_data = image_api.show(context, image_id)
|
||||||
|
name = image_data['name']
|
||||||
|
operation = gceutils.create_image_from_disk(
|
||||||
|
compute, project, name, snapshot_disk_info['selfLink'])
|
||||||
|
gceutils.wait_for_operation(compute, project, operation,
|
||||||
|
timeout=120)
|
||||||
|
image_created = True
|
||||||
|
LOG.debug(
|
||||||
|
_LI("6. Created image %s from disk %s") % (name,
|
||||||
|
snapshot_disk_name))
|
||||||
|
LOG.info(
|
||||||
|
_LI("Created GCE image %s from instance %s") % (name,
|
||||||
|
instance.uuid))
|
||||||
|
|
||||||
|
update_task_state(task_state=task_states.IMAGE_UPLOADING,
|
||||||
|
expected_state=task_states.IMAGE_PENDING_UPLOAD)
|
||||||
|
gce_img_data = gceutils.get_image(compute, project, name)
|
||||||
|
image_metadata = {
|
||||||
|
'name': name,
|
||||||
|
'container_format': 'bare',
|
||||||
|
'disk_format': 'raw',
|
||||||
|
'is_public': False,
|
||||||
|
'status': 'active',
|
||||||
|
'properties': {
|
||||||
|
'image_state': 'available',
|
||||||
|
'owner_id': instance.project_id,
|
||||||
|
'ramdisk_id': instance.ramdisk_id,
|
||||||
|
'location': 'gce://%s/%s/%s' % (project, name, image_id),
|
||||||
|
'gce_image_id': gce_img_data['id'],
|
||||||
|
'gce_link': gce_img_data['selfLink'],
|
||||||
|
'gce_size': gce_img_data['diskSizeGb']
|
||||||
|
},
|
||||||
|
}
|
||||||
|
image_api.update(context, image_id, image_metadata)
|
||||||
|
LOG.debug(_LI("7. Added image to glance %s") % name)
|
||||||
|
|
||||||
|
disk_operation = gceutils.delete_disk(compute, project, zone,
|
||||||
|
snapshot_disk_name)
|
||||||
|
snap_operation = gceutils.delete_snapshot(compute, project,
|
||||||
|
snapshot_name)
|
||||||
|
gceutils.wait_for_operation(compute, project, disk_operation)
|
||||||
|
temp_disk_from_snapshot = False
|
||||||
|
LOG.debug(_LI("8. Delete temporary disk %s") % snapshot_disk_name)
|
||||||
|
|
||||||
|
gceutils.wait_for_operation(compute, project, snap_operation)
|
||||||
|
temp_disk_snapshot = False
|
||||||
|
LOG.debug(
|
||||||
|
_LI("9. Delete temporary disk snapshot %s") % snapshot_name)
|
||||||
|
LOG.info(_LI("Completed snapshot for instance %s") % instance.uuid)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception("An error occured during image creation: %s" % e)
|
||||||
|
if instance_stopped:
|
||||||
|
operation = gceutils.start_instance(compute, project, zone,
|
||||||
|
gce_id)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
LOG.debug(
|
||||||
|
_LI("Restart instance after disk snapshot %s") %
|
||||||
|
instance.uuid)
|
||||||
|
if image_created:
|
||||||
|
LOG.info(
|
||||||
|
_LI("Rollback snapshot for instance %s, deleting image %s from GCE"
|
||||||
|
) % (instance.uuid, name))
|
||||||
|
operation = gceutils.delete_image(compute, project, name)
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
|
if temp_disk_from_snapshot:
|
||||||
|
disk_operation = gceutils.delete_disk(compute, project, zone,
|
||||||
|
snapshot_disk_name)
|
||||||
|
gceutils.wait_for_operation(compute, project, disk_operation)
|
||||||
|
LOG.debug(
|
||||||
|
_LI("Rollback snapshot for instace %s, delete temporary disk %s"
|
||||||
|
) % (instance.uuid, snapshot_disk_name))
|
||||||
|
if temp_disk_snapshot:
|
||||||
|
snap_operation = gceutils.delete_snapshot(
|
||||||
|
compute, project, snapshot_name)
|
||||||
|
gceutils.wait_for_operation(compute, project, snap_operation)
|
||||||
|
LOG.debug(
|
||||||
|
_LI("Rollback snapshot for instance %s, delete temporary disk snapshot %s"
|
||||||
|
) % (instance.uuid, snapshot_name))
|
||||||
|
raise e
|
||||||
|
|
||||||
def reboot(self, context, instance, network_info, reboot_type,
|
def reboot(self, context, instance, network_info, reboot_type,
|
||||||
block_device_info=None, bad_volumes_callback=None):
|
block_device_info=None, bad_volumes_callback=None):
|
||||||
|
@ -303,10 +451,10 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
gce_id = self._get_gce_id_from_instance(instance)
|
gce_id = self._get_gce_id_from_instance(instance)
|
||||||
LOG.info(_LI('Stopping instance %s') % instance.uuid)
|
LOG.info(_LI('Stopping instance %s') % instance.uuid)
|
||||||
operation = gceutils.stop_instance(compute, project, zone, gce_id)
|
operation = gceutils.stop_instance(compute, project, zone, gce_id)
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
LOG.info(_LI('Starting instance %s') % instance.uuid)
|
LOG.info(_LI('Starting instance %s') % instance.uuid)
|
||||||
operation = gceutils.start_instance(compute, project, zone, gce_id)
|
operation = gceutils.start_instance(compute, project, zone, gce_id)
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
LOG.info(_LI('Soft Reboot Complete for instance %s') % instance.uuid)
|
LOG.info(_LI('Soft Reboot Complete for instance %s') % instance.uuid)
|
||||||
|
|
||||||
def _hard_reboot(self, context, instance, network_info,
|
def _hard_reboot(self, context, instance, network_info,
|
||||||
|
@ -315,7 +463,7 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
gce_id = self._get_gce_id_from_instance(instance)
|
gce_id = self._get_gce_id_from_instance(instance)
|
||||||
LOG.info(_LI('Resetting instance %s') % instance.uuid)
|
LOG.info(_LI('Resetting instance %s') % instance.uuid)
|
||||||
operation = gceutils.reset_instance(compute, project, zone, gce_id)
|
operation = gceutils.reset_instance(compute, project, zone, gce_id)
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
LOG.info(_LI('Hard Reboot Complete %s') % instance.uuid)
|
LOG.info(_LI('Hard Reboot Complete %s') % instance.uuid)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -370,7 +518,7 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
gce_id = self._get_gce_id_from_instance(instance)
|
gce_id = self._get_gce_id_from_instance(instance)
|
||||||
LOG.info(_LI('Stopping instance %s') % instance.uuid)
|
LOG.info(_LI('Stopping instance %s') % instance.uuid)
|
||||||
operation = gceutils.stop_instance(compute, project, zone, gce_id)
|
operation = gceutils.stop_instance(compute, project, zone, gce_id)
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
LOG.info(_LI('Power off complete %s') % instance.uuid)
|
LOG.info(_LI('Power off complete %s') % instance.uuid)
|
||||||
|
|
||||||
def power_on(self, context, instance, network_info, block_device_info):
|
def power_on(self, context, instance, network_info, block_device_info):
|
||||||
|
@ -379,7 +527,7 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
gce_id = self._get_gce_id_from_instance(instance)
|
gce_id = self._get_gce_id_from_instance(instance)
|
||||||
LOG.info(_LI('Starting instance %s') % instance.uuid)
|
LOG.info(_LI('Starting instance %s') % instance.uuid)
|
||||||
operation = gceutils.start_instance(compute, project, zone, gce_id)
|
operation = gceutils.start_instance(compute, project, zone, gce_id)
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
LOG.info(_LI("Power on Complete %s") % instance.uuid)
|
LOG.info(_LI("Power on Complete %s") % instance.uuid)
|
||||||
|
|
||||||
def soft_delete(self, instance):
|
def soft_delete(self, instance):
|
||||||
|
@ -449,10 +597,25 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
:param migrate_data: implementation specific params
|
:param migrate_data: implementation specific params
|
||||||
"""
|
"""
|
||||||
compute, project, zone = self.gce_svc, self.gce_project, self.gce_zone
|
compute, project, zone = self.gce_svc, self.gce_project, self.gce_zone
|
||||||
gce_id = self._get_gce_id_from_instance(instance)
|
|
||||||
LOG.info(_LI('Deleting instance %s') % instance.uuid)
|
LOG.info(_LI('Deleting instance %s') % instance.uuid)
|
||||||
operation = gceutils.delete_instance(compute, project, zone, gce_id)
|
try:
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gce_id = self._get_gce_id_from_instance(instance)
|
||||||
|
except exception.InstanceNotFound:
|
||||||
|
LOG.error(
|
||||||
|
_LI("Unable to find GCE mapping for instance %s") %
|
||||||
|
instance.uuid)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
operation = gceutils.delete_instance(compute, project, zone,
|
||||||
|
gce_id)
|
||||||
|
except HttpError:
|
||||||
|
# Sometimes instance may not exist in GCE, in that case we just
|
||||||
|
# allow deleting VM from openstack
|
||||||
|
LOG.error(
|
||||||
|
_LI("Instance %s not found in GCE, removing from openstack.") %
|
||||||
|
instance.uuid)
|
||||||
|
return
|
||||||
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
LOG.info(_LI("Destroy Complete %s") % instance.uuid)
|
LOG.info(_LI("Destroy Complete %s") % instance.uuid)
|
||||||
|
|
||||||
def attach_volume(self, context, connection_info, instance, mountpoint,
|
def attach_volume(self, context, connection_info, instance, mountpoint,
|
||||||
|
@ -466,7 +629,7 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
disk_link = gce_volume['selfLink']
|
disk_link = gce_volume['selfLink']
|
||||||
operation = gceutils.attach_disk(compute, project, zone, gce_id,
|
operation = gceutils.attach_disk(compute, project, zone, gce_id,
|
||||||
disk_name, disk_link)
|
disk_name, disk_link)
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
LOG.info(
|
LOG.info(
|
||||||
_LI("Volume %s attached to instace %s") % (disk_name,
|
_LI("Volume %s attached to instace %s") % (disk_name,
|
||||||
instance.uuid))
|
instance.uuid))
|
||||||
|
@ -481,7 +644,7 @@ class GCEDriver(driver.ComputeDriver):
|
||||||
disk_name = gce_volume['name']
|
disk_name = gce_volume['name']
|
||||||
operation = gceutils.detach_disk(compute, project, zone, gce_id,
|
operation = gceutils.detach_disk(compute, project, zone, gce_id,
|
||||||
disk_name)
|
disk_name)
|
||||||
gceutils.wait_for_operation(compute, project, zone, operation)
|
gceutils.wait_for_operation(compute, project, operation)
|
||||||
LOG.info(
|
LOG.info(
|
||||||
_LI("Volume %s detached from instace %s") % (disk_name,
|
_LI("Volume %s detached from instace %s") % (disk_name,
|
||||||
instance.uuid))
|
instance.uuid))
|
||||||
|
|
|
@ -12,17 +12,64 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import six
|
|
||||||
import time
|
import time
|
||||||
|
import six
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from nova.i18n import _LI
|
from nova.i18n import _LI, _
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
from oauth2client.client import GoogleCredentials
|
from oauth2client.client import GoogleCredentials
|
||||||
|
from oslo_service import loopingcall
|
||||||
|
from oslo_utils import reflection
|
||||||
|
from six.moves import urllib
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class GceOperationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def list_instances(compute, project, zone):
|
def list_instances(compute, project, zone):
|
||||||
"""Returns list of GCE instance resources for specified project
|
"""Returns list of GCE instance resources for specified project
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
@ -210,33 +257,42 @@ def reset_instance(compute, project, zone, name):
|
||||||
instance=name).execute()
|
instance=name).execute()
|
||||||
|
|
||||||
|
|
||||||
def wait_for_operation(compute, project, zone, operation, interval=1,
|
def wait_for_operation(compute, project, operation, interval=1, timeout=60):
|
||||||
timeout=60):
|
|
||||||
"""Wait for GCE operation to complete, raise error if operation failure
|
"""Wait for GCE operation to complete, raise error if operation failure
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
:param project: string, GCE Project Id
|
:param project: string, GCE Project Id
|
||||||
:param zone: string, GCE Name of zone
|
:param operation: object, Operation resource obtained by calling GCE asynchronous API
|
||||||
:param operation: object, Operation resource obtained by calling GCE API
|
All GCE asynchronous API's return operation resource to followup there completion.
|
||||||
:param interval: int, Time period(seconds) between two GCE operation checks
|
:param interval: int, Time period(seconds) between two GCE operation checks
|
||||||
:param timeout: int, Absoulte time period(seconds) to monitor GCE operation
|
:param timeout: int, Absoulte time period(seconds) to monitor GCE operation
|
||||||
"""
|
"""
|
||||||
operation_name = operation['name']
|
|
||||||
if interval < 1:
|
def watch_operation(name, request):
|
||||||
raise ValueError("wait_for_operation: Interval should be positive")
|
result = request.execute()
|
||||||
iterations = timeout / interval
|
|
||||||
for i in range(iterations):
|
|
||||||
result = compute.zoneOperations().get(
|
|
||||||
project=project, zone=zone, operation=operation_name).execute()
|
|
||||||
if result['status'] == 'DONE':
|
if result['status'] == 'DONE':
|
||||||
LOG.info("Operation %s status is %s" % (operation_name,
|
LOG.info(
|
||||||
result['status']))
|
_LI("Operation %s status is %s") % (name, result['status']))
|
||||||
if 'error' in result:
|
if 'error' in result:
|
||||||
raise Exception(result['error'])
|
raise GceOperationError(result['error'])
|
||||||
return result
|
raise loopingcall.LoopingCallDone()
|
||||||
time.sleep(interval)
|
|
||||||
raise Exception(
|
operation_name = operation['name']
|
||||||
"wait_for_operation: Operation %s failed to perform in timeout %s" %
|
|
||||||
(operation_name, timeout))
|
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):
|
def get_gce_service(service_key):
|
||||||
|
@ -289,6 +345,18 @@ def get_image(compute, project, name):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def delete_image(compute, project, name):
|
||||||
|
"""Delete image from GCE
|
||||||
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
:param project: string, GCE Project Id
|
||||||
|
:param name: string, GCE image name
|
||||||
|
:return: Operation information
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
result = compute.images().delete(project=project, image=name).execute()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_network(compute, project, name):
|
def get_network(compute, project, name):
|
||||||
"""Return network info
|
"""Return network info
|
||||||
:param compute: GCE compute resource object using googleapiclient.discovery
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
||||||
|
@ -337,3 +405,134 @@ def detach_disk(compute, project, zone, instance_name, disk_name):
|
||||||
return compute.instances().detachDisk(project=project, zone=zone,
|
return compute.instances().detachDisk(project=project, zone=zone,
|
||||||
instance=instance_name,
|
instance=instance_name,
|
||||||
deviceName=disk_name).execute()
|
deviceName=disk_name).execute()
|
||||||
|
|
||||||
|
|
||||||
|
def get_instance_boot_disk(compute, project, zone, instance):
|
||||||
|
"""Return boot disk info for instance
|
||||||
|
"""
|
||||||
|
gce_instance = get_instance(compute, project, zone, instance)
|
||||||
|
for disk in gce_instance['disks']:
|
||||||
|
if disk['boot']:
|
||||||
|
disk_url = disk['source']
|
||||||
|
# Extracting disk details from disk URL,
|
||||||
|
# Eg. projects/<project>/zones/<zone>/disks/<disk_name>
|
||||||
|
items = urllib.parse.urlparse(disk_url).path.strip('/').split('/')
|
||||||
|
if len(items) < 4 or items[-2] != 'disks':
|
||||||
|
LOG.error(_LI('Invalid disk URL %s') % (disk_url))
|
||||||
|
disk_name, zone = items[-1], items[-3]
|
||||||
|
disk_info = get_disk(compute, project, zone, disk_name)
|
||||||
|
return disk_info
|
||||||
|
# We should never reach here
|
||||||
|
raise AssertionError("Boot disk not found for instance %s" % instance)
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
disk_type="pd-standard"):
|
||||||
|
"""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
|
||||||
|
:param disk_type: string, Disk type from (pd-standard, pd-sdd, local-ssd)
|
||||||
|
: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/%s" % (project, zone,
|
||||||
|
disk_type),
|
||||||
|
"sourceSnapshot": gce_snapshot["selfLink"],
|
||||||
|
"sizeGb": gce_snapshot["diskSizeGb"]
|
||||||
|
}
|
||||||
|
return compute.disks().insert(project=project, zone=zone, body=body,
|
||||||
|
sourceImage=None).execute()
|
||||||
|
|
||||||
|
|
||||||
|
def create_image_from_disk(compute, project, name, disk_link):
|
||||||
|
body = {"sourceDisk": disk_link, "name": name, "rawDisk": {}}
|
||||||
|
return compute.images().insert(project=project, body=body).execute()
|
||||||
|
|
Loading…
Reference in New Issue