454 lines
16 KiB
Python
454 lines
16 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 os
|
|
import time
|
|
import uuid
|
|
|
|
from googleapiclient.discovery import build
|
|
from oauth2client.client import GoogleCredentials
|
|
from oslo_log import log as logging
|
|
from oslo_utils import reflection
|
|
|
|
from neutron._i18n import _
|
|
from neutron_lib import exceptions as e
|
|
from oslo_service import loopingcall
|
|
from six.moves import urllib
|
|
|
|
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
|
|
|
|
|
|
class GceResourceNotFound(e.NotFound):
|
|
message = _("GCE Resource %(name)s %(identifier)s was not found")
|
|
|
|
|
|
class GceServiceKeyNotFound(e.NotFound):
|
|
message = _("GCE service key was not found at %(path)s location")
|
|
|
|
|
|
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
|
|
:return: Instances information
|
|
:rtype: list
|
|
"""
|
|
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
|
|
:return: Instance information
|
|
:rtype: dict
|
|
"""
|
|
result = compute.instances().get(project=project, zone=zone,
|
|
instance=instance).execute()
|
|
return result
|
|
|
|
|
|
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("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
|
|
"""
|
|
if not os.path.exists(service_key):
|
|
raise GceServiceKeyNotFound(path=service_key)
|
|
|
|
credentials = GoogleCredentials.from_stream(service_key)
|
|
service = build('compute', 'beta', credentials=credentials)
|
|
return service
|
|
|
|
|
|
def create_network(compute, project, name):
|
|
"""Create network in GCE
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param name: string, GCE Name of network
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
body = {'autoCreateSubnetworks': False, 'name': name}
|
|
return compute.networks().insert(project=project, body=body).execute()
|
|
|
|
|
|
def get_network(compute, project, name):
|
|
"""Get info of network in GCE
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param name: string, GCE Name of network
|
|
:return: GCE Network information
|
|
:rtype: dict
|
|
"""
|
|
result = compute.networks().get(project=project, network=name).execute()
|
|
return result
|
|
|
|
|
|
def create_subnet(compute, project, region, name, ipcidr, network_link):
|
|
"""Create subnet with particular GCE network
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param region: string, GCE region
|
|
:param name: string, Subnet name
|
|
:param ipcidr: string, IP CIDR for subnet
|
|
:param network_link: url, GCE network resource url
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
body = {
|
|
'privateIpGoogleAccess': False,
|
|
'name': name,
|
|
'ipCidrRange': ipcidr,
|
|
'network': network_link
|
|
}
|
|
return compute.subnetworks().insert(project=project, region=region,
|
|
body=body).execute()
|
|
|
|
|
|
def delete_subnet(compute, project, region, name):
|
|
"""Delete subnet in GCE
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param region: string, GCE region
|
|
:param name: string, Subnet name
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
return compute.subnetworks().delete(project=project, region=region,
|
|
subnetwork=name).execute()
|
|
|
|
|
|
def delete_network(compute, project, name):
|
|
"""Delete network in GCE
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param name: string, GCE network name
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
return compute.networks().delete(project=project, network=name).execute()
|
|
|
|
|
|
def create_static_ip(compute, project, region, name):
|
|
"""Create global static IP
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param region: string, GCE region
|
|
:param name: string, Static IP name
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
return compute.addresses().insert(project=project, region=region, body={
|
|
'name': name,
|
|
}).execute()
|
|
|
|
|
|
def get_static_ip(compute, project, region, name):
|
|
"""Get static IP
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param region: string, GCE region
|
|
:param name: string, Static IP name
|
|
:return: GCE static IP information
|
|
:rtype: dict
|
|
"""
|
|
return compute.addresses().get(project=project, region=region,
|
|
address=name).execute()
|
|
|
|
|
|
def delete_static_ip(compute, project, region, name):
|
|
"""Delete static IP
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param region: string, GCE region
|
|
:param name: string, Static IP name
|
|
:return: Operation information
|
|
:rtype: dict
|
|
"""
|
|
return compute.addresses().delete(project=project, region=region,
|
|
address=name).execute()
|
|
|
|
|
|
def get_floatingip(compute, project, region, ip):
|
|
"""Get details of static IP in GCE
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param region: string, GCE region
|
|
:param ip: string, GCE Static IP
|
|
:return: GCE address information
|
|
:rtype: dict
|
|
"""
|
|
query = 'address eq %s' % ip
|
|
result = compute.addresses().list(project=project, region=region,
|
|
filter=query).execute()
|
|
if 'items' in result and len(result['items']) == 1:
|
|
return result['items'][0]
|
|
|
|
raise GceResourceNotFound(name='Floating IP', identifier=ip)
|
|
|
|
|
|
def allocate_floatingip(compute, project, region):
|
|
"""Get global static IP in GCE
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param region: string, GCE region
|
|
:return: GCE static IP
|
|
:rtype: str
|
|
"""
|
|
name = 'ip-' + str(uuid.uuid4())
|
|
operation = create_static_ip(compute, project, region, name)
|
|
wait_for_operation(compute, project, operation)
|
|
address = get_static_ip(compute, project, region, name)
|
|
return address['address']
|
|
|
|
|
|
def delete_floatingip(compute, project, region, ip):
|
|
"""Delete particular static IP
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param region: string, GCE region
|
|
:param ip: string, GCE Static IP
|
|
"""
|
|
address = get_floatingip(compute, project, region, ip)
|
|
name = address['name']
|
|
operation = delete_static_ip(compute, project, region, name)
|
|
wait_for_operation(compute, project, operation)
|
|
|
|
|
|
def assign_floatingip(compute, project, zone, fixedip, floatingip):
|
|
"""Assign static IP to interface with mentioned fixed IP
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param zone: string, GCE zone
|
|
:param fixedip: string, GCE private IP from private network
|
|
:param floatingip: string, GCE static IP
|
|
"""
|
|
instances = list_instances(compute, project, zone)
|
|
instance_name = None
|
|
for instance in instances:
|
|
for interface in instance['networkInterfaces']:
|
|
if interface['networkIP'] == fixedip:
|
|
instance_name = instance['name']
|
|
interface_name = interface['name']
|
|
break
|
|
if not instance_name:
|
|
raise GceResourceNotFound(name='Instance with fixed IP',
|
|
identifier=fixedip)
|
|
|
|
LOG.info('Assigning floating ip %s to instance %s' %
|
|
(floatingip, instance_name))
|
|
|
|
operation = compute.instances().addAccessConfig(
|
|
project=project, zone=zone, instance=instance_name,
|
|
networkInterface=interface_name, body={
|
|
'type': 'ONE_TO_ONE_NAT',
|
|
'name': 'External NAT',
|
|
'natIP': floatingip
|
|
}).execute()
|
|
wait_for_operation(compute, project, operation)
|
|
|
|
|
|
def release_floatingip(compute, project, zone, floatingip):
|
|
"""Release GCE static IP from instances using it
|
|
|
|
:param compute: GCE compute resource object using googleapiclient.discovery
|
|
:param project: string, GCE Project Id
|
|
:param zone: string, GCE zone
|
|
:param floatingip: string, GCE static IP
|
|
"""
|
|
address = get_floatingip(compute, project, zone, floatingip)
|
|
for user in address.get('users', []):
|
|
# Parse instance info
|
|
# Eg. /compute/v1/projects/<name>/zones/<zone>/instances/<name>
|
|
|
|
items = urllib.parse.urlparse(user).path.strip('/').split('/')
|
|
if len(items) < 4 or items[-2] != 'instances':
|
|
LOG.warning('Unknown referrer %s to GCE static IP %s' %
|
|
(user, floatingip))
|
|
continue
|
|
|
|
instance, zone = items[-1], items[-3]
|
|
instance_info = get_instance(compute, project, zone, instance)
|
|
for interface in instance_info['networkInterfaces']:
|
|
for accessconfig in interface.get('accessConfigs', []):
|
|
if accessconfig.get('natIP') == floatingip:
|
|
LOG.info('Releasing %s from instance %s' %
|
|
(floatingip, instance))
|
|
operation = compute.instances().deleteAccessConfig(
|
|
project=project, zone=zone, instance=instance,
|
|
accessConfig=accessconfig['name'],
|
|
networkInterface=interface['name']).execute()
|
|
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()
|