Applied pep8 fixes

Closes-Bug: #1703514

Change-Id: I89c25bd9e678dc2f7cc27f952aa4833e26c90f4a
This commit is contained in:
Pratik Shah 2017-07-21 17:27:33 +05:30
parent c030c3d645
commit 264939cfb5
38 changed files with 1122 additions and 984 deletions

View File

@ -1,21 +1,19 @@
# 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
"""
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.
"""
from apiclient.http import HttpMock
from googleapiclient.discovery import build
import os
DATA_DIR = os.path.dirname(os.path.abspath(__file__)) + '/data'

View File

@ -1,26 +1,25 @@
# 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.
"""
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 mock
import os
from cinder import context
from cinder import test
from cinder.volume.drivers.gce.driver import GceDriver
from cinder.tests.unit.volume.drivers.gce import gce_mock
from cinder.tests.unit.fake_volume import fake_volume_obj
from cinder.tests.unit.fake_snapshot import fake_snapshot_obj
from cinder.tests.unit.fake_volume import fake_volume_obj
from cinder.tests.unit.volume.drivers.gce import gce_mock
from cinder.volume.drivers.gce.driver import GceDriver
from cinder.volume.drivers.gce.gceutils import GceOperationError
DATA_DIR = os.path.dirname(os.path.abspath(__file__)) + '/data'

View File

@ -10,19 +10,20 @@ 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.
"""
import mock
from oslo_service import loopingcall
from cinder import context
from cinder.exception import APITimeout
from cinder.exception import NotFound
from cinder.exception import VolumeNotFound
from cinder import test
from cinder.exception import APITimeout, NotFound, VolumeNotFound
from cinder.volume.drivers.aws import ebs
from cinder.volume.drivers.aws.exception import AvailabilityZoneNotFound
import mock
from moto import mock_ec2
from oslo_service import loopingcall
class EBSVolumeTestCase(test.TestCase):
@mock_ec2
def setUp(self):
super(EBSVolumeTestCase, self).setUp()
@ -79,11 +80,13 @@ class EBSVolumeTestCase(test.TestCase):
def wait(*args):
def _wait():
raise loopingcall.LoopingCallDone(False)
timer = loopingcall.FixedIntervalLoopingCall(_wait)
return timer.start(interval=1).wait()
mock_wait.side_effect = wait
self.assertRaises(APITimeout, self._driver.create_volume, self._stub_volume())
self.assertRaises(APITimeout, self._driver.create_volume,
self._stub_volume())
@mock_ec2
def test_volume_deletion(self):
@ -121,6 +124,7 @@ class EBSVolumeTestCase(test.TestCase):
timer = loopingcall.FixedIntervalLoopingCall(_wait)
return timer.start(interval=1).wait()
mock_wait.side_effect = wait
ss = self._stub_snapshot()
self._driver.create_volume(ss['volume'])
@ -132,7 +136,8 @@ class EBSVolumeTestCase(test.TestCase):
volume = self._stub_volume()
self._driver.create_volume(volume)
self._driver.create_snapshot(snapshot)
self.assertIsNone(self._driver.create_volume_from_snapshot(volume, snapshot))
self.assertIsNone(
self._driver.create_volume_from_snapshot(volume, snapshot))
@mock_ec2
def test_volume_from_non_existing_snapshot(self):

View File

@ -10,34 +10,39 @@ 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.
"""
import time
from boto import ec2
from boto.regioninfo import RegionInfo
from oslo_service import loopingcall
from oslo_log import log as logging
from oslo_config import cfg
from cinder.exception import VolumeNotFound, NotFound, APITimeout, InvalidConfigurationValue
from cinder.exception import APITimeout
from cinder.exception import InvalidConfigurationValue
from cinder.exception import NotFound
from cinder.exception import VolumeNotFound
from cinder.volume.driver import BaseVD
from cinder.volume.drivers.aws.exception import AvailabilityZoneNotFound
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import loopingcall
from exception import AvailabilityZoneNotFound
aws_group = cfg.OptGroup(name='AWS', title='Options to connect to an AWS environment')
aws_group = cfg.OptGroup(name='AWS',
title='Options to connect to an AWS environment')
aws_opts = [
cfg.StrOpt('secret_key', help='Secret key of AWS account', secret=True),
cfg.StrOpt('access_key', help='Access key of AWS account', secret=True),
cfg.StrOpt('region_name', help='AWS region'),
cfg.StrOpt('az', help='AWS availability zone'),
cfg.IntOpt('wait_time_min', help='Maximum wait time for AWS operations', default=5)
cfg.IntOpt('wait_time_min', help='Maximum wait time for AWS operations',
default=5)
]
ebs_opts = [
cfg.StrOpt('ebs_pool_name', help='Storage pool name'),
cfg.IntOpt('ebs_free_capacity_gb', help='Free space available on EBS storage pool',
default=1024),
cfg.IntOpt('ebs_total_capacity_gb', help='Total space available on EBS storage pool',
default=1024)
cfg.IntOpt('ebs_free_capacity_gb',
help='Free space available on EBS storage pool', default=1024),
cfg.IntOpt('ebs_total_capacity_gb',
help='Total space available on EBS storage pool', default=1024)
]
CONF = cfg.CONF
@ -48,9 +53,7 @@ LOG = logging.getLogger(__name__)
class EBSDriver(BaseVD):
"""
Implements cinder volume interface with EBS as storage backend.
"""
"""Implements cinder volume interface with EBS as storage backend."""
def __init__(self, *args, **kwargs):
super(EBSDriver, self).__init__(*args, **kwargs)
self.VERSION = '1.0.0'
@ -70,21 +73,21 @@ class EBSDriver(BaseVD):
region_name = CONF.AWS.region_name
endpoint = '.'.join(['ec2', region_name, 'amazonaws.com'])
region = RegionInfo(name=region_name, endpoint=endpoint)
self._conn = ec2.EC2Connection(aws_access_key_id=CONF.AWS.access_key,
aws_secret_access_key=CONF.AWS.secret_key,
region=region)
# resort to first AZ for now. TODO: expose this through API
self._conn = ec2.EC2Connection(
aws_access_key_id=CONF.AWS.access_key,
aws_secret_access_key=CONF.AWS.secret_key,
region=region)
# resort to first AZ for now. TODO(do_setup): expose this through API
az = CONF.AWS.az
try:
self._zone = filter(lambda z: z.name == az,
self._conn.get_all_zones())[0]
self._conn.get_all_zones())[0]
except IndexError:
raise AvailabilityZoneNotFound(az=az)
self.set_initialized()
def _wait_for_create(self, id, final_state):
def _wait_for_status(start_time):
current_time = time.time()
@ -96,7 +99,8 @@ class EBSDriver(BaseVD):
if obj.status == final_state:
raise loopingcall.LoopingCallDone(True)
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_status, time.time())
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_status,
time.time())
return timer.start(interval=5).wait()
def _wait_for_snapshot(self, id, final_state):
@ -109,7 +113,8 @@ class EBSDriver(BaseVD):
if obj.status == final_state:
raise loopingcall.LoopingCallDone(True)
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_status, time.time())
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_status,
time.time())
return timer.start(interval=5).wait()
def create_volume(self, volume):
@ -117,11 +122,12 @@ class EBSDriver(BaseVD):
ebs_vol = self._conn.create_volume(size, self._zone)
if self._wait_for_create(ebs_vol.id, 'available') is False:
raise APITimeout(service='EC2')
self._conn.create_tags([ebs_vol.id], {'project_id': volume['project_id'],
'uuid': volume['id'],
'is_clone': False,
'created_at': volume['created_at'],
'Name': volume['display_name']})
self._conn.create_tags([ebs_vol.id],
{'project_id': volume['project_id'],
'uuid': volume['id'],
'is_clone': False,
'created_at': volume['created_at'],
'Name': volume['display_name']})
def _find(self, obj_id, find_func):
ebs_objs = find_func(filters={'tag:uuid': obj_id})
@ -139,7 +145,7 @@ class EBSDriver(BaseVD):
self._conn.delete_volume(ebs_vol.id)
def check_for_setup_error(self):
# TODO throw errors if AWS config is broken
# TODO(check_setup_error) throw errors if AWS config is broken
pass
def create_export(self, context, volume, connector):
@ -198,11 +204,12 @@ class EBSDriver(BaseVD):
if self._wait_for_snapshot(ebs_snap.id, 'completed') is False:
raise APITimeout(service='EC2')
self._conn.create_tags([ebs_snap.id], {'project_id': snapshot['project_id'],
'uuid': snapshot['id'],
'is_clone': True,
'created_at': snapshot['created_at'],
'Name': snapshot['display_name']})
self._conn.create_tags([ebs_snap.id],
{'project_id': snapshot['project_id'],
'uuid': snapshot['id'],
'is_clone': True,
'created_at': snapshot['created_at'],
'Name': snapshot['display_name']})
def delete_snapshot(self, snapshot):
try:
@ -222,11 +229,12 @@ class EBSDriver(BaseVD):
if self._wait_for_create(ebs_vol.id, 'available') is False:
raise APITimeout(service='EC2')
self._conn.create_tags([ebs_vol.id], {'project_id': volume['project_id'],
'uuid': volume['id'],
'is_clone': False,
'created_at': volume['created_at'],
'Name': volume['display_name']})
self._conn.create_tags([ebs_vol.id],
{'project_id': volume['project_id'],
'uuid': volume['id'],
'is_clone': False,
'created_at': volume['created_at'],
'Name': volume['display_name']})
def copy_image_to_volume(self, context, volume, image_service, image_id):
raise NotImplemented()
@ -239,7 +247,3 @@ class EBSDriver(BaseVD):
def copy_volume_data(self, context, src_vol, dest_vol, remote=None):
raise NotImplemented()

View File

@ -15,5 +15,6 @@ limitations under the License.
from cinder.exception import CinderException
from cinder.i18n import _
class AvailabilityZoneNotFound(CinderException):
message = _("Availability Zone %(az)s was not found")

View File

@ -1,16 +1,15 @@
# 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.
"""
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
@ -71,6 +70,7 @@ if not hasattr(loopingcall, '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
@ -108,6 +108,7 @@ def wait_for_operation(compute, project, operation, interval=1, timeout=60):
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
@ -120,6 +121,7 @@ def get_gce_service(service_key):
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
@ -140,6 +142,7 @@ def create_disk(compute, project, zone, name, size):
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
@ -153,6 +156,7 @@ def delete_disk(compute, project, zone, name):
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
@ -165,6 +169,7 @@ def get_disk(compute, project, zone, name):
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
@ -180,6 +185,7 @@ def snapshot_disk(compute, project, zone, name, snapshot_name):
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
@ -191,6 +197,7 @@ def get_snapshot(compute, project, name):
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
@ -202,6 +209,7 @@ def delete_snapshot(compute, project, name):
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

View File

@ -1,24 +1,17 @@
# Copyright (c) 2016 Platform9 Systems Inc. (http://www.platform9.com)
#
# 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.
'''
1. Source your Openstack RC file.
2. Run this script as: python create-glance-images-aws.py <access-key> <secret-key> <region-name>
'''
"""
Copyright (c) 2016 Platform9 Systems Inc. (http://www.platform9.com)
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.
"""
import boto3
import ConfigParser
import hashlib
import keystoneauth1
import os
@ -26,26 +19,28 @@ import requests
import sys
import uuid
from keystoneauth1 import session
from keystoneauth1.identity import v3
from keystoneauth1 import session
class AwsImages(object):
def __init__(self, credentials):
self.ec2_client = boto3.client('ec2', **credentials)
self.glance_client = RestClient()
self.aws_image_types = {'machine': 'ami', 'kernel': 'aki', 'ramdisk': 'ari'}
def __init__(self, credentials):
self.ec2_client = boto3.client('ec2', **credentials)
self.glance_client = RestClient()
self.aws_image_types = {'machine': 'ami', 'kernel': 'aki',
'ramdisk': 'ari'}
def register_aws_images(self):
response = self.ec2_client.describe_images(Owners=['self'])
images = response['Images']
def register_aws_images(self):
response = self.ec2_client.describe_images(Owners=['self'])
images = response['Images']
for img in images:
self.create_image(self._aws_to_ostack_formatter(img))
for img in images:
self.create_image(self._aws_to_ostack_formatter(img))
def create_image(self, img_data):
"""Create an OpenStack image.
def create_image(self, img_data):
"""
Create an OpenStack image.
:param img_data: dict -- Describes AWS AMI
:returns: dict -- Response from REST call
:raises: requests.HTTPError
@ -58,20 +53,21 @@ class AwsImages(object):
'metadata': {'ami_id': ami_id}}]
}
try:
resp = self.glance_client.request('POST', '/v2/images', json=img_data)
resp = self.glance_client.request('POST', '/v2/images',
json=img_data)
resp.raise_for_status()
# Need to update the image in the registry with location information so
# the status changes from 'queued' to 'active'
self.update_properties(glance_id, img_props)
# Need to update the image in the registry with location
# information so the status changes from 'queued' to 'active'
self.update_properties(glance_id, img_props)
except keystoneauth1.exceptions.http.Conflict as e:
# ignore error if image already exists
pass
except requests.HTTPError as e:
raise e
def update_properties(self, imageid, props):
"""
Add or update a set of image properties on an image.
def update_properties(self, imageid, props):
"""Add or update a set of image properties on an image.
:param imageid: int -- The Ostack image UUID
:param props: dict -- Image properties to update
"""
@ -84,17 +80,18 @@ class AwsImages(object):
'path': '/%s' % name,
'value': value
})
resp = self.glance_client.request('PATCH', '/v2/images/%s' % imageid, json=patch_body)
resp = self.glance_client.request('PATCH', '/v2/images/%s' % imageid,
json=patch_body)
resp.raise_for_status()
def _get_image_uuid(self, ami_id):
def _get_image_uuid(self, ami_id):
md = hashlib.md5()
md.update(ami_id)
return str(uuid.UUID(bytes=md.digest()))
def _aws_to_ostack_formatter(self, aws_obj):
"""
Converts aws img data to Openstack img data format.
def _aws_to_ostack_formatter(self, aws_obj):
"""Converts aws img data to Openstack img data format.
:param img(dict): aws img data
:return(dict): ostack img data
"""
@ -105,30 +102,32 @@ class AwsImages(object):
for bdm in aws_obj.get('BlockDeviceMappings'):
if 'Ebs' in bdm:
ebs_vol_sizes.append(bdm['Ebs']['VolumeSize'])
elif 'VirtualName' in bdm and bdm['VirtualName'].startswith('ephemeral'):
elif 'VirtualName' in bdm and bdm['VirtualName'].startswith(
'ephemeral'):
# for instance-store volumes, size is not available
num_istore_vols += 1
if aws_obj.get('RootDeviceType' == 'instance-store') and num_istore_vols == 0:
if (aws_obj.get('RootDeviceType' == 'instance-store') and
num_istore_vols == 0):
# list of bdms can be empty for instance-store volumes
num_istore_vols = 1
# generate glance image uuid based on AWS image id
image_id = self._get_image_uuid(aws_obj.get('ImageId'))
description = aws_obj.get('Description') or 'Discovered image'
return {
'id' : image_id,
'name' : aws_obj.get('Name') or aws_obj.get('ImageId'),
'container_format' : self.aws_image_types[aws_obj.get('ImageType')],
'disk_format' : self.aws_image_types[aws_obj.get('ImageType')],
'visibility' : visibility,
'pf9_description' : aws_obj.get('Description') or 'Discovered image',
'aws_image_id' : aws_obj.get('ImageId'),
'id': image_id,
'name': aws_obj.get('Name') or aws_obj.get('ImageId'),
'container_format': self.aws_image_types[aws_obj.get('ImageType')],
'disk_format': self.aws_image_types[aws_obj.get('ImageType')],
'visibility': visibility,
'pf9_description': description,
'aws_image_id': aws_obj.get('ImageId'),
'aws_root_device_type': aws_obj.get('RootDeviceType'),
'aws_ebs_vol_sizes' : str(ebs_vol_sizes),
'aws_num_istore_vols' : str(num_istore_vols),
'aws_ebs_vol_sizes': str(ebs_vol_sizes),
'aws_num_istore_vols': str(num_istore_vols),
}
class RestClient(object):
def __init__(self):
os_auth_url = os.getenv('OS_AUTH_URL')
@ -139,24 +138,24 @@ class RestClient(object):
os_username = os.getenv('OS_USERNAME')
os_password = os.getenv('OS_PASSWORD')
os_tenant_name = os.getenv('OS_TENANT_NAME')
os_region_name = os.getenv('OS_REGION_NAME')
self.glance_endpoint = os_auth_url.replace('keystone/v3', 'glance')
sys.stdout.write('Using glance endpoint: ' + self.glance_endpoint)
v3_auth = v3.Password(auth_url = os_auth_url, username = os_username,
password = os_password, project_name = os_tenant_name,
project_domain_name = 'default', user_domain_name = 'default')
self.sess = session.Session(auth=v3_auth, verify=False) # verify=True
v3_auth = v3.Password(auth_url=os_auth_url, username=os_username,
password=os_password,
project_name=os_tenant_name,
project_domain_name='default',
user_domain_name='default')
self.sess = session.Session(auth=v3_auth, verify=False) # verify=True
def request(self, method, path, **kwargs):
"""
Make a requests request with retry/relogin on auth failure.
"""
"""Make a requests request with retry/relogin on auth failure."""
url = self.glance_endpoint + path
headers = self.sess.get_auth_headers()
if method == 'PUT' or method == 'PATCH':
headers['Content-Type'] = 'application/openstack-images-v2.1-json-patch'
content_type = 'application/openstack-images-v2.1-json-patch'
headers['Content-Type'] = content_type
resp = requests.request(method, url, headers=headers, **kwargs)
else:
resp = self.sess.request(url, method, headers=headers, **kwargs)
@ -164,11 +163,12 @@ class RestClient(object):
return resp
### MAIN ###
# MAIN
if __name__ == '__main__':
if len(sys.argv) != 4:
sys.stderr.write('Incorrect usage: this script takes exactly 3 arguments.\n')
sys.stderr.write('Incorrect usage: this script takes exactly 3 '
'arguments.\n')
sys.exit(1)
credentials = {}
@ -178,4 +178,3 @@ if __name__ == '__main__':
aws_images = AwsImages(credentials)
aws_images.register_aws_images()

View File

@ -1,30 +1,26 @@
# Copyright (c) 2017 Platform9 Systems Inc. (http://www.platform9.com)
#
# 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.
'''
1. Export Openstack RC file
2. Run this script as: python create-glance-credentials.py <service-key-path>
'''
"""
Copyright (c) 2017 Platform9 Systems Inc. (http://www.platform9.com)
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.
"""
import gceutils
import hashlib
import keystoneauth1
import os
import requests
import sys
import uuid
import keystoneauth1
import gceutils
from keystoneauth1 import loading, session
from keystoneauth1 import loading
from keystoneauth1 import session
from keystoneclient import client
from six.moves import urllib

View File

@ -1,21 +1,19 @@
# 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.
from oslo_log import log as logging
"""
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.
"""
from googleapiclient.discovery import build
from oauth2client.client import GoogleCredentials
from oslo_log import log as logging
LOG = logging.getLogger(__name__)

View File

@ -1,30 +1,25 @@
# Copyright (c) 2016 Platform9 Systems Inc. (http://www.platform9.com)
#
# 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.
"""
Copyright (c) 2016 Platform9 Systems Inc. (http://www.platform9.com)
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.
"""
import logging
import socket
from six.moves import http_client
from six.moves import urllib
from oslo_config import cfg
from ConfigParser import ConfigParser
from glance_store import capabilities
import glance_store.driver
from glance_store import exceptions
from glance_store.i18n import _
import glance_store.driver
import glance_store.location
from oslo_config import cfg
from six.moves import urllib
import boto3
import botocore.exceptions
@ -35,11 +30,10 @@ MAX_REDIRECTS = 5
STORE_SCHEME = 'aws'
aws_opts_group = cfg.OptGroup(name='aws', title='AWS specific options')
aws_opts = [
cfg.StrOpt('access_key', help='AWS access key ID'),
cfg.StrOpt('secret_key', help='AWS secret access key'),
cfg.StrOpt('region_name', help='AWS region name'),
]
aws_opts = [cfg.StrOpt('access_key', help='AWS access key ID'),
cfg.StrOpt('secret_key', help='AWS secret access key'),
cfg.StrOpt('region_name', help='AWS region name')]
class StoreLocation(glance_store.location.StoreLocation):
@ -85,7 +79,7 @@ class Store(glance_store.driver.Store):
def __init__(self, conf):
super(Store, self).__init__(conf)
conf.register_group(aws_opts_group)
conf.register_opts(aws_opts, group = aws_opts_group)
conf.register_opts(aws_opts, group=aws_opts_group)
self.credentials = {}
self.credentials['aws_access_key_id'] = conf.aws.access_key
self.credentials['aws_secret_access_key'] = conf.aws.secret_key
@ -103,7 +97,6 @@ class Store(glance_store.driver.Store):
self.__ec2_resource = boto3.resource('ec2', **self.credentials)
return self.__ec2_resource
@capabilities.check
def get(self, location, offset=0, chunk_size=None, context=None):
"""
@ -133,7 +126,6 @@ class Store(glance_store.driver.Store):
LOG.warn('**** ID of ami being deleted: {}'.format(ami_id))
aws_client.deregister_image(ImageId=ami_id)
def get_schemes(self):
"""
:retval tuple: containing valid scheme names to
@ -141,7 +133,6 @@ class Store(glance_store.driver.Store):
"""
return ('aws',)
def get_size(self, location, context=None):
"""
Takes a `glance_store.location.Location` object that indicates
@ -169,5 +160,6 @@ class Store(glance_store.driver.Store):
if ce.response['Error']['Code'] == 'InvalidAMIID.NotFound':
raise exceptions.ImageDataNotFound()
else:
raise exceptions.GlanceStoreException(ce.response['Error']['Code'])
raise exceptions.GlanceStoreException(
ce.response['Error']['Code'])
return size

View File

@ -1,23 +1,23 @@
# Copyright (c) 2017 Platform9 Systems Inc. (http://www.platform9.com)
#
# 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.
import logging
"""
Copyright (c) 2017 Platform9 Systems Inc. (http://www.platform9.com)
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.
"""
import gceutils
import glance_store.driver
import glance_store.location
from glance_store import capabilities, exceptions
import logging
from glance_store import capabilities
from glance_store import exceptions
from glance_store.i18n import _
from oslo_config import cfg
from oslo_utils import units

View File

@ -1,16 +1,15 @@
# 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.
"""
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

View File

@ -1,27 +1,26 @@
# 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.
"""
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 mock
import os
from glance_store._drivers.gce import Store
from glance_store._drivers.gce import StoreLocation
from glance_store import exceptions
from glance_store import location
from glance_store.location import Location
from glance_store._drivers.gce import Store
from glance_store._drivers.gce import StoreLocation
from glance_store.tests.unit.gce import gce_mock
from glance_store.tests import base
from glance_store.tests.unit.gce import gce_mock
from oslo_config import cfg
from oslo_utils import units

View File

@ -1,34 +1,36 @@
# Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
#
# 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.
"""
Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
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.
"""
import time
from ConfigParser import ConfigParser
from neutron.common import exceptions
from novaclient.v2 import client as novaclient
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import loopingcall
import boto3
import botocore
import time
aws_group = cfg.OptGroup(name='AWS', title='Options to connect to an AWS environment')
aws_group = cfg.OptGroup(name='AWS',
title='Options to connect to an AWS environment')
aws_opts = [
cfg.StrOpt('secret_key', help='Secret key of AWS account', secret=True),
cfg.StrOpt('access_key', help='Access key of AWS account', secret=True),
cfg.StrOpt('region_name', help='AWS region'),
cfg.StrOpt('az', help='AWS availability zone'),
cfg.IntOpt('wait_time_min', help='Maximum wait time for AWS operations', default=5)
cfg.IntOpt('wait_time_min', help='Maximum wait time for AWS operations',
default=5)
]
cfg.CONF.register_group(aws_group)
@ -36,28 +38,31 @@ cfg.CONF.register_opts(aws_opts, group=aws_group)
LOG = logging.getLogger(__name__)
def _process_exception(e, dry_run):
if dry_run:
error_code = e.response['Code']
if not error_code == 'DryRunOperation':
raise exceptions.AwsException(error_code='AuthFailure',
raise exceptions.AwsException(
error_code='AuthFailure',
message='Check your AWS authorization')
else:
if isinstance(e, botocore.exceptions.ClientError):
error_code = e.response['Error']['Code']
error_message = e.response['Error']['Message']
raise exceptions.AwsException(error_code=error_code,
message=error_message)
message=error_message)
elif isinstance(e, exceptions.AwsException):
# If the exception is already an AwsException, do not nest it.
# Instead just propagate it up.
raise e
else:
# TODO: This might display all Exceptions to the user which
# might be irrelevant, keeping it until it becomes stable
# TODO(exceptions): This might display all Exceptions to the user
# which might be irrelevant, keeping it until it becomes stable
error_message = e.msg
raise exceptions.AwsException(error_code="NeutronError",
message=error_message)
message=error_message)
def aws_exception(fn):
def wrapper(*args, **kwargs):
@ -68,7 +73,7 @@ def aws_exception(fn):
return wrapper
class AwsUtils:
class AwsUtils(object):
def __init__(self):
self.__ec2_client = None
@ -83,20 +88,24 @@ class AwsUtils:
def get_nova_client(self):
if self._nova_client is None:
self._nova_client = novaclient.Client(username=cfg.CONF.nova_admin_username,
api_key=cfg.CONF.nova_admin_password, auth_url=cfg.CONF.nova_admin_auth_url,
self._nova_client = novaclient.Client(
username=cfg.CONF.nova_admin_username,
api_key=cfg.CONF.nova_admin_password,
auth_url=cfg.CONF.nova_admin_auth_url,
tenant_id=cfg.CONF.nova_admin_tenant_id,
region_name=cfg.CONF.nova_region_name, insecure=True)
return self._nova_client
def _get_ec2_client(self):
if self.__ec2_client is None:
self.__ec2_client = boto3.client('ec2', **self._neutron_credentials)
self.__ec2_client = boto3.client('ec2',
**self._neutron_credentials)
return self.__ec2_client
def _get_ec2_resource(self):
if self.__ec2_resource is None:
self.__ec2_resource = boto3.resource('ec2', **self._neutron_credentials)
self.__ec2_resource = boto3.resource('ec2',
**self._neutron_credentials)
return self.__ec2_resource
# Internet Gateway Operations
@ -117,7 +126,8 @@ class AwsUtils:
return internet_gateway['InternetGatewayId']
@aws_exception
def create_tags_internet_gw_from_router_id(self, router_id, tags_list, dry_run=False):
def create_tags_internet_gw_from_router_id(self, router_id, tags_list,
dry_run=False):
ig_id = self.get_internet_gw_from_router_id(router_id, dry_run)
internet_gw_res = self._get_ec2_resource().InternetGateway(ig_id)
internet_gw_res.create_tags(Tags=tags_list)
@ -156,7 +166,8 @@ class AwsUtils:
@aws_exception
def create_internet_gateway_resource(self, dry_run=False):
internet_gw = self._get_ec2_client().create_internet_gateway(DryRun=dry_run)
internet_gw = self._get_ec2_client().create_internet_gateway(
DryRun=dry_run)
ig_id = internet_gw['InternetGateway']['InternetGatewayId']
return self._get_ec2_resource().InternetGateway(ig_id)
@ -169,15 +180,17 @@ class AwsUtils:
return eip_addresses['Addresses']
@aws_exception
def associate_elastic_ip_to_ec2_instance(self, elastic_ip, ec2_instance_id, dry_run=False):
def associate_elastic_ip_to_ec2_instance(self, elastic_ip, ec2_instance_id,
dry_run=False):
allocation_id = None
eid_addresses = self.get_elastic_addresses_by_elastic_ip(elastic_ip, dry_run)
eid_addresses = self.get_elastic_addresses_by_elastic_ip(elastic_ip,
dry_run)
if len(eid_addresses) > 0:
if 'AllocationId' in eid_addresses[0]:
allocation_id = eid_addresses[0]['AllocationId']
if allocation_id is None:
raise exceptions.AwsException(error_code="Allocation ID",
message="Allocation ID not found")
message="Allocation ID not found")
return self._get_ec2_client().associate_address(
DryRun=dry_run,
InstanceId=ec2_instance_id,
@ -193,15 +206,17 @@ class AwsUtils:
return response
@aws_exception
def disassociate_elastic_ip_from_ec2_instance(self, elastic_ip, dry_run=False):
def disassociate_elastic_ip_from_ec2_instance(self, elastic_ip,
dry_run=False):
association_id = None
eid_addresses = self.get_elastic_addresses_by_elastic_ip(elastic_ip, dry_run)
eid_addresses = self.get_elastic_addresses_by_elastic_ip(elastic_ip,
dry_run)
if len(eid_addresses) > 0:
if 'AssociationId' in eid_addresses[0]:
association_id = eid_addresses[0]['AssociationId']
if association_id is None:
raise exceptions.AwsException(error_code="Association ID",
message="Association ID not found")
message="Association ID not found")
return self._get_ec2_client().disassociate_address(
DryRun=dry_run,
AssociationId=association_id
@ -209,20 +224,22 @@ class AwsUtils:
@aws_exception
def delete_elastic_ip(self, elastic_ip, dry_run=False):
eid_addresses = self.get_elastic_addresses_by_elastic_ip(elastic_ip, dry_run)
eid_addresses = self.get_elastic_addresses_by_elastic_ip(elastic_ip,
dry_run)
if len(eid_addresses) > 0:
if 'AllocationId' in eid_addresses[0]:
allocation_id = eid_addresses[0]['AllocationId']
if allocation_id is None:
raise exceptions.AwsException(error_code="Allocation ID",
message="Allocation ID not found")
message="Allocation ID not found")
return self._get_ec2_client().release_address(
DryRun=dry_run,
AllocationId=allocation_id)
# VPC Operations
@aws_exception
def get_vpc_from_neutron_network_id(self, neutron_network_id, dry_run=False):
def get_vpc_from_neutron_network_id(self, neutron_network_id,
dry_run=False):
response = self._get_ec2_client().describe_vpcs(
DryRun=dry_run,
Filters=[
@ -245,9 +262,7 @@ class AwsUtils:
CidrBlock=cidr)['Vpc']['VpcId']
vpc = self._get_ec2_resource().Vpc(vpc_id)
waiter = self._get_ec2_client().get_waiter('vpc_available')
waiter.wait(
DryRun=dry_run,
VpcIds = [ vpc_id ])
waiter.wait(DryRun=dry_run, VpcIds=[vpc_id])
vpc.create_tags(Tags=tags_list)
return vpc_id
@ -275,7 +290,7 @@ class AwsUtils:
waiter = self._get_ec2_client().get_waiter('subnet_available')
waiter.wait(
DryRun=dry_run,
SubnetIds = [ subnet.id ])
SubnetIds=[subnet.id])
subnet.create_tags(Tags=tags_list)
@aws_exception
@ -292,7 +307,8 @@ class AwsUtils:
)
@aws_exception
def get_subnet_from_neutron_subnet_id(self, neutron_subnet_id, dry_run=False):
def get_subnet_from_neutron_subnet_id(self, neutron_subnet_id,
dry_run=False):
response = self._get_ec2_client().describe_subnets(
DryRun=dry_run,
Filters=[
@ -336,7 +352,8 @@ class AwsUtils:
return response['RouteTables']
# Has ignore_errors special case so can't use decorator
def create_default_route_to_ig(self, route_table_id, ig_id, dry_run=False, ignore_errors=False):
def create_default_route_to_ig(self, route_table_id, ig_id, dry_run=False,
ignore_errors=False):
try:
self._get_ec2_client().create_route(
DryRun=dry_run,
@ -345,12 +362,14 @@ class AwsUtils:
GatewayId=ig_id,
)
except Exception as e:
LOG.warning("Ignoring failure in creating default route to IG: %s" % e)
LOG.warning("Ignoring failure in creating default route to IG: "
"%s" % e)
if not ignore_errors:
_process_exception(e, dry_run)
# Has ignore_errors special case so can't use decorator
def delete_default_route_to_ig(self, route_table_id, dry_run=False, ignore_errors=False):
def delete_default_route_to_ig(self, route_table_id, dry_run=False,
ignore_errors=False):
try:
self._get_ec2_client().delete_route(
DryRun=dry_run,
@ -361,7 +380,8 @@ class AwsUtils:
if not ignore_errors:
_process_exception(e, dry_run)
else:
LOG.warning("Ignoring failure in deleting default route to IG: %s" % e)
LOG.warning("Ignoring failure in deleting default route to IG:"
" %s" % e)
# Security group
def _create_sec_grp_tags(self, secgrp, tags):
@ -372,12 +392,13 @@ class AwsUtils:
try:
secgrp.reload()
secgrp.create_tags(Tags=tags)
except Exception as ex:
except Exception:
LOG.exception('Exception when adding tags to security groups.'
' Retrying.')
return
raise loopingcall.LoopingCallDone(True)
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_state, time.time())
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_state,
time.time())
return timer.start(interval=5).wait()
def _convert_openstack_rules_to_vpc(self, rules):
@ -443,6 +464,7 @@ class AwsUtils:
def _create_sec_grp_rules(self, secgrp, rules):
ingress, egress = self._convert_openstack_rules_to_vpc(rules)
def _wait_for_state(start_time):
current_time = time.time()
@ -450,7 +472,7 @@ class AwsUtils:
raise loopingcall.LoopingCallDone(False)
try:
self._refresh_sec_grp_rules(secgrp, ingress, egress)
except Exception as ex:
except Exception:
LOG.exception('Error creating security group rules. Retrying.')
return
raise loopingcall.LoopingCallDone(True)
@ -461,33 +483,28 @@ class AwsUtils:
def create_security_group_rules(self, ec2_secgrp, rules):
if self._create_sec_grp_rules(ec2_secgrp, rules) is False:
exceptions.AwsException(
message='Timed out creating security groups',
error_code='Time Out')
message='Timed out creating security groups',
error_code='Time Out')
def create_security_group(self, name, description, vpc_id, os_secgrp_id,
tags):
if not description:
description = 'Created by Platform9 OpenStack'
secgrp = self._get_ec2_resource().create_security_group(
GroupName=name, Description=description, VpcId=vpc_id)
GroupName=name, Description=description, VpcId=vpc_id)
if self._create_sec_grp_tags(secgrp, tags) is False:
delete_sec_grp(secgrp.id)
delete_sec_grp(secgrp.id) # noqa
raise exceptions.AwsException(
message='Timed out creating tags on security group',
error_code='Time Out')
message='Timed out creating tags on security group',
error_code='Time Out')
return secgrp
@aws_exception
def get_sec_group_by_id(self, secgrp_id, vpc_id = None, dry_run=False):
filters = [{
'Name': 'tag-value',
'Values': [secgrp_id]
}]
def get_sec_group_by_id(self, secgrp_id, vpc_id=None, dry_run=False):
filters = [{'Name': 'tag-value',
'Values': [secgrp_id]}]
if vpc_id:
filters.append({
'Name': 'vpc-id',
'Values': [vpc_id]
})
filters.append({'Name': 'vpc-id', 'Values': [vpc_id]})
response = self._get_ec2_client().describe_security_groups(
DryRun=dry_run, Filters=filters)
@ -529,4 +546,3 @@ class AwsUtils:
ec2_sg_id = aws_secgrp['GroupId']
self._update_sec_group(ec2_sg_id, old_ingress, old_egress, ingress,
egress)

View File

@ -1,28 +1,26 @@
# 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.
"""
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 uuid
import time
import uuid
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from oauth2client.client import GoogleCredentials
from oslo_log import log as logging
from oslo_utils import reflection
from neutron_lib import exceptions as e
from neutron._i18n import _
from neutron_lib import exceptions as e
from oslo_service import loopingcall
from six.moves import urllib
@ -66,7 +64,7 @@ class _FixedIntervalWithTimeoutLoopingCall(loopingcall.LoopingCallBase):
# definition _FixedIntervalWithTimeoutLoopingCall
if not hasattr(loopingcall, 'FixedIntervalWithTimeoutLoopingCall'):
loopingcall.FixedIntervalWithTimeoutLoopingCall = \
_FixedIntervalWithTimeoutLoopingCall
_FixedIntervalWithTimeoutLoopingCall
class GceOperationError(Exception):
@ -79,6 +77,7 @@ class GceResourceNotFound(e.NotFound):
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
@ -93,6 +92,7 @@ def list_instances(compute, project, zone):
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
@ -107,6 +107,7 @@ def get_instance(compute, project, zone, instance):
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
@ -144,6 +145,7 @@ def wait_for_operation(compute, project, operation, interval=1, timeout=60):
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
@ -156,6 +158,7 @@ def get_gce_service(service_key):
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
@ -168,6 +171,7 @@ def create_network(compute, project, name):
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
@ -180,6 +184,7 @@ def get_network(compute, project, name):
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
@ -201,6 +206,7 @@ def create_subnet(compute, project, region, name, ipcidr, network_link):
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
@ -214,6 +220,7 @@ def delete_subnet(compute, project, region, name):
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
@ -225,6 +232,7 @@ def delete_network(compute, project, name):
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
@ -239,6 +247,7 @@ def create_static_ip(compute, project, region, name):
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
@ -252,6 +261,7 @@ def get_static_ip(compute, project, region, name):
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
@ -265,6 +275,7 @@ def delete_static_ip(compute, project, region, name):
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
@ -283,6 +294,7 @@ def get_floatingip(compute, project, region, 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
@ -298,6 +310,7 @@ def allocate_floatingip(compute, project, region):
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
@ -311,6 +324,7 @@ def delete_floatingip(compute, project, region, ip):
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
@ -344,6 +358,7 @@ def assign_floatingip(compute, project, zone, fixedip, floatingip):
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
@ -376,10 +391,13 @@ def release_floatingip(compute, project, zone, floatingip):
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
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
"""
@ -388,11 +406,14 @@ def create_firewall_rule(compute, project, body):
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
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
"""
@ -402,6 +423,7 @@ def update_firewall_rule(compute, project, name, body):
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
@ -413,6 +435,7 @@ def delete_firewall_rule(compute, project, name):
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

View File

@ -1,19 +1,17 @@
# Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
#
# 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.
"""
Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
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.
"""
from neutron.callbacks import events
from neutron.callbacks import exceptions
from neutron.callbacks import registry
from neutron.callbacks import resources
from oslo_log import log as logging
@ -26,9 +24,9 @@ def subscribe(mech_driver):
events.BEFORE_DELETE)
registry.subscribe(mech_driver.secgroup_callback, resources.SECURITY_GROUP,
events.BEFORE_UPDATE)
registry.subscribe(mech_driver.secgroup_callback, resources.SECURITY_GROUP_RULE,
events.BEFORE_DELETE)
registry.subscribe(mech_driver.secgroup_callback, resources.SECURITY_GROUP_RULE,
events.BEFORE_UPDATE)
registry.subscribe(mech_driver.secgroup_callback, resources.SECURITY_GROUP_RULE,
events.BEFORE_CREATE)
registry.subscribe(mech_driver.secgroup_callback,
resources.SECURITY_GROUP_RULE, events.BEFORE_DELETE)
registry.subscribe(mech_driver.secgroup_callback,
resources.SECURITY_GROUP_RULE, events.BEFORE_UPDATE)
registry.subscribe(mech_driver.secgroup_callback,
resources.SECURITY_GROUP_RULE, events.BEFORE_CREATE)

View File

@ -1,27 +1,27 @@
# Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
#
# 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.
"""
Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
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.
"""
import json
import random
from oslo_log import log
from neutron import manager
from neutron.callbacks import resources
from neutron.callbacks import events
from neutron.callbacks import resources
from neutron.common.aws_utils import AwsUtils
from neutron import manager
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2.drivers.aws import callbacks
from neutron_lib.exceptions import NeutronException
from oslo_log import log
LOG = log.getLogger(__name__)
@ -62,11 +62,14 @@ class AwsMechanismDriver(api.MechanismDriver):
def delete_network_precommit(self, context):
neutron_network_id = context.current['id']
# If user is deleting an empty neutron network then nothing to be done on AWS side
# If user is deleting an empty neutron network then nothing to be done
# on AWS side
if len(context.current['subnets']) > 0:
vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(neutron_network_id)
vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
neutron_network_id)
if vpc_id is not None:
LOG.info("Deleting network %s (VPC_ID: %s)" % (neutron_network_id, vpc_id))
LOG.info("Deleting network %s (VPC_ID: %s)" %
(neutron_network_id, vpc_id))
self.aws_utils.delete_vpc(vpc_id=vpc_id)
def delete_network_postcommit(self, context):
@ -74,40 +77,48 @@ class AwsMechanismDriver(api.MechanismDriver):
# SUBNET
def create_subnet_precommit(self, context):
LOG.info("Create subnet for network %s" % context.network.current['id'])
LOG.info("Create subnet for network %s" %
context.network.current['id'])
# External Network doesn't exist on AWS, so no operations permitted
if 'provider:physical_network' in context.network.current and \
context.network.current['provider:physical_network'] == "external":
# Do not create subnets for external & provider networks. Only
# allow tenant network subnet creation at the moment.
LOG.info('Creating external network %s' %
context.network.current['id'])
if 'provider:physical_network' in context.network.current:
if context.network.current[
'provider:physical_network'] == "external":
# Do not create subnets for external & provider networks. Only
# allow tenant network subnet creation at the moment.
LOG.info('Creating external network {0}'.format(
context.network.current['id']))
return
if context.current['ip_version'] == 6:
raise AwsException(error_code="IPv6Error", message="Cannot create subnets with IPv6")
raise AwsException(error_code="IPv6Error",
message="Cannot create subnets with IPv6")
mask = int(context.current['cidr'][-2:])
if mask < 16 or mask > 28:
raise AwsException(error_code="InvalidMask", message="Subnet mask has to be >16 and <28")
raise AwsException(error_code="InvalidMask",
message="Subnet mask has to be >16 and <28")
try:
# Check if this is the first subnet to be added to a network
neutron_network = context.network.current
associated_vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(neutron_network['id'])
associated_vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
neutron_network['id'])
if associated_vpc_id is None:
# Need to create EC2 VPC
vpc_cidr = context.current['cidr'][:-2] + '16'
tags = [
{'Key': 'Name', 'Value': neutron_network['name']},
{'Key': 'openstack_network_id', 'Value': neutron_network['id']},
{'Key': 'openstack_tenant_id', 'Value': context.current['tenant_id']}
{'Key': 'openstack_network_id',
'Value': neutron_network['id']},
{'Key': 'openstack_tenant_id',
'Value': context.current['tenant_id']}
]
associated_vpc_id = self.aws_utils.create_vpc_and_tags(cidr=vpc_cidr,
tags_list=tags)
associated_vpc_id = self.aws_utils.create_vpc_and_tags(
cidr=vpc_cidr, tags_list=tags)
# Create Subnet in AWS
tags = [
{'Key': 'Name', 'Value': context.current['name']},
{'Key': 'openstack_subnet_id', 'Value': context.current['id']},
{'Key': 'openstack_tenant_id', 'Value': context.current['tenant_id']}
{'Key': 'openstack_tenant_id',
'Value': context.current['tenant_id']}
]
self.aws_utils.create_subnet_and_tags(vpc_id=associated_vpc_id,
cidr=context.current['cidr'],
@ -133,13 +144,16 @@ class AwsMechanismDriver(api.MechanismDriver):
pass
def delete_subnet_precommit(self, context):
if 'provider:physical_network' in context.network.current and context.network.current[
'provider:physical_network'] == "external":
LOG.error("Deleting provider and external networks not supported")
return
if 'provider:physical_network' in context.network.current:
if context.network.current[
'provider:physical_network'] == "external":
LOG.error("Deleting provider and external networks not "
"supported")
return
try:
LOG.info("Deleting subnet %s" % context.current['id'])
subnet_id = self.aws_utils.get_subnet_from_neutron_subnet_id(context.current['id'])
subnet_id = self.aws_utils.get_subnet_from_neutron_subnet_id(
context.current['id'])
if subnet_id is not None:
self.aws_utils.delete_subnet(subnet_id=subnet_id)
except Exception as e:
@ -156,12 +170,15 @@ class AwsMechanismDriver(api.MechanismDriver):
return
try:
subnets = neutron_network['subnets']
if len(subnets) == 1 and subnets[0] == context.current['id'] or len(subnets) == 0:
if (len(subnets) == 1 and subnets[0] == context.current['id'] or
len(subnets) == 0):
# Last subnet for this network was deleted, so delete VPC
# because VPC gets created during first subnet creation under
# an OpenStack network
vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(neutron_network['id'])
LOG.info("Deleting VPC %s since this was the last subnet in the vpc" % vpc_id)
vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
neutron_network['id'])
LOG.info("Deleting VPC %s since this was the last subnet in "
"the vpc" % vpc_id)
self.aws_utils.delete_vpc(vpc_id=vpc_id)
except Exception as e:
LOG.error("Error in delete subnet postcommit: %s" % e)
@ -195,18 +212,15 @@ class AwsMechanismDriver(api.MechanismDriver):
fixed_ip_dict['subnet_id'])
secgroup_ids = context.current['security_groups']
self.create_security_groups_if_needed(context, secgroup_ids)
segment_id = random.choice(context.network.network_segments)[api.ID]
context.set_binding(segment_id,
"vip_type_a",
json.dumps(fixed_ip_dict),
status='ACTIVE')
context.set_binding(segment_id, "vip_type_a",
json.dumps(fixed_ip_dict), status='ACTIVE')
return True
def create_security_groups_if_needed(self, context, secgrp_ids):
core_plugin = manager.NeutronManager.get_plugin()
vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
context.current['network_id'])
context.current['network_id'])
for secgrp_id in secgrp_ids:
tags = [
{'Key': 'openstack_id', 'Value': secgrp_id},
@ -222,7 +236,7 @@ class AwsMechanismDriver(api.MechanismDriver):
desc = secgrp['description']
rules = secgrp['security_group_rules']
ec2_secgrp = self.aws_utils.create_security_group(
grp_name, desc, vpc_id, secgrp_id, tags)
grp_name, desc, vpc_id, secgrp_id, tags)
self.aws_utils.create_security_group_rules(ec2_secgrp, rules)
def delete_security_group(self, security_group_id):
@ -234,7 +248,7 @@ class AwsMechanismDriver(api.MechanismDriver):
secgrp_id = rule['security_group_id']
secgrp = core_plugin.get_security_group(context, secgrp_id)
old_rules = secgrp['security_group_rules']
for idx in range(len(old_rules)-1, -1, -1):
for idx in range(len(old_rules) - 1, -1, -1):
if old_rules[idx]['id'] == rule_id:
old_rules.pop(idx)
self.aws_utils.update_sec_group(secgrp_id, old_rules)
@ -253,7 +267,7 @@ class AwsMechanismDriver(api.MechanismDriver):
secgrp_id = rule['security_group_id']
secgrp = core_plugin.get_security_group(context, secgrp_id)
old_rules = secgrp['security_group_rules']
for idx in range(len(old_rules)-1, -1, -1):
for idx in range(len(old_rules) - 1, -1, -1):
if old_rules[idx]['id'] == rule_id:
old_rules.pop(idx)
break
@ -262,7 +276,7 @@ class AwsMechanismDriver(api.MechanismDriver):
def secgroup_callback(self, resource, event, trigger, **kwargs):
if resource == resources.SECURITY_GROUP:
if event == events.BEFORE_DELETE:
if event == events.BEFORE_DELETE:
security_group_id = kwargs.get('security_group_id')
if security_group_id:
self.delete_security_group(security_group_id)
@ -279,5 +293,3 @@ class AwsMechanismDriver(api.MechanismDriver):
elif event == events.BEFORE_UPDATE:
rule_id = kwargs['security_group_rule_id']
self.update_security_group_rules(context, rule_id)

View File

@ -1,32 +1,31 @@
# Copyright 2017 Platform9 Systems Inc.(http://www.platform9.com)
#
# 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.
"""
Copyright 2017 Platform9 Systems Inc.(http://www.platform9.com)
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.
"""
import random
from oslo_log import log
import ipaddr
from neutron._i18n import _
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.common import gceconf
from neutron.common import gceutils
from neutron.extensions import securitygroup as sg
from neutron.manager import NeutronManager
from neutron.plugins.ml2 import driver_api as api
from neutron.extensions import securitygroup as sg
from neutron_lib import exceptions as e
from oslo_log import log
import ipaddr
try:
from neutron_lib.plugins import directory
except ImportError:
@ -110,7 +109,9 @@ class GceMechanismDriver(api.MechanismDriver):
pass
def create_subnet_postcommit(self, context):
compute, project, region = self.gce_svc, self.gce_project, self.gce_region
compute = self.gce_svc
project = self.gce_project
region = self.gce_region
network_name = self._gce_subnet_network_name(context)
name = self._gce_subnet_name(context)
cidr = context.current['cidr']
@ -132,7 +133,9 @@ class GceMechanismDriver(api.MechanismDriver):
pass
def delete_subnet_postcommit(self, context):
compute, project, region = self.gce_svc, self.gce_project, self.gce_region
compute = self.gce_svc
project = self.gce_project
region = self.gce_region
cidr = context.current['cidr']
if self.is_private_network(cidr):
name = self._gce_subnet_name(context)

View File

@ -1,18 +1,19 @@
# Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
#
# 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.
"""
Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
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.
"""
from neutron.common.aws_utils import AwsUtils
from neutron.common import constants as n_const
from neutron.common import exceptions
from neutron.db import common_db_mixin
from neutron.db import extraroute_db
from neutron.db import l3_db
@ -20,24 +21,20 @@ from neutron.db import l3_dvrscheduler_db
from neutron.db import l3_gwmode_db
from neutron.db import l3_hamode_db
from neutron.db import l3_hascheduler_db
from neutron.db import securitygroups_db
from neutron.plugins.common import constants
from neutron.quota import resource_registry
from neutron.services import service_base
from oslo_log import log as logging
from neutron.common import exceptions
from neutron.db import securitygroups_db
from neutron.common.aws_utils import AwsUtils
LOG = logging.getLogger(__name__)
class AwsRouterPlugin(service_base.ServicePluginBase,
common_db_mixin.CommonDbMixin,
extraroute_db.ExtraRoute_db_mixin,
l3_hamode_db.L3_HA_NAT_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
l3_dvrscheduler_db.L3_DVRsch_db_mixin,
l3_hascheduler_db.L3_HA_scheduler_db_mixin):
class AwsRouterPlugin(
service_base.ServicePluginBase, common_db_mixin.CommonDbMixin,
extraroute_db.ExtraRoute_db_mixin, l3_hamode_db.L3_HA_NAT_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin, l3_dvrscheduler_db.L3_DVRsch_db_mixin,
l3_hascheduler_db.L3_HA_scheduler_db_mixin):
"""Implementation of the Neutron L3 Router Service Plugin.
This class implements a L3 service plugin that provides
@ -47,13 +44,14 @@ class AwsRouterPlugin(service_base.ServicePluginBase,
l3_db.L3_NAT_db_mixin, l3_hamode_db.L3_HA_NAT_db_mixin,
l3_dvr_db.L3_NAT_with_dvr_db_mixin, and extraroute_db.ExtraRoute_db_mixin.
"""
supported_extension_aliases = ["dvr", "router", "ext-gw-mode",
"extraroute", "l3_agent_scheduler",
"l3-ha", "security-group"]
supported_extension_aliases = [
"dvr", "router", "ext-gw-mode", "extraroute", "l3_agent_scheduler",
"l3-ha", "security-group"
]
@resource_registry.tracked_resources(router=l3_db.Router,
floatingip=l3_db.FloatingIP,
security_group=securitygroups_db.SecurityGroup)
@resource_registry.tracked_resources(
router=l3_db.Router, floatingip=l3_db.FloatingIP,
security_group=securitygroups_db.SecurityGroup)
def __init__(self):
self.aws_utils = AwsUtils()
super(AwsRouterPlugin, self).__init__()
@ -68,7 +66,7 @@ class AwsRouterPlugin(service_base.ServicePluginBase,
" between (L2) Neutron networks and access to external"
" networks via a NAT gateway.")
########## FLOATING IP FEATURES ###############
# FLOATING IP FEATURES
def create_floatingip(self, context, floatingip):
try:
@ -76,12 +74,15 @@ class AwsRouterPlugin(service_base.ServicePluginBase,
public_ip_allocated = response['PublicIp']
LOG.info("Created elastic IP %s" % public_ip_allocated)
if 'floatingip' in floatingip:
floatingip['floatingip']['floating_ip_address'] = public_ip_allocated
floatingip['floatingip'][
'floating_ip_address'] = public_ip_allocated
if 'port_id' in floatingip['floatingip'] and floatingip['floatingip']['port_id'] is not None:
if ('port_id' in floatingip['floatingip'] and
floatingip['floatingip']['port_id'] is not None):
# Associate to a Port
port_id = floatingip['floatingip']['port_id']
self._associate_floatingip_to_port(context, public_ip_allocated, port_id)
self._associate_floatingip_to_port(
context, public_ip_allocated, port_id)
except Exception as e:
LOG.error("Error in Creation/Allocating EIP")
if public_ip_allocated:
@ -94,57 +95,73 @@ class AwsRouterPlugin(service_base.ServicePluginBase,
context, floatingip,
initial_status=n_const.FLOATINGIP_STATUS_DOWN)
except Exception as e:
LOG.error("Error when adding floating ip in openstack. Deleting Elastic IP: %s" % public_ip_allocated)
LOG.error("Error when adding floating ip in openstack. "
"Deleting Elastic IP: %s" % public_ip_allocated)
self.aws_utils.delete_elastic_ip(public_ip_allocated)
raise e
return res
def _associate_floatingip_to_port(self, context, floating_ip_address, port_id):
def _associate_floatingip_to_port(self, context, floating_ip_address,
port_id):
port = self._core_plugin.get_port(context, port_id)
ec2_id = None
fixed_ip_address = None
# TODO: Assuming that there is only one fixed IP
# TODO(fixed_ip): Assuming that there is only one fixed IP
if len(port['fixed_ips']) > 0:
fixed_ip = port['fixed_ips'][0]
if 'ip_address' in fixed_ip:
fixed_ip_address = fixed_ip['ip_address']
search_opts = {'ip': fixed_ip_address, 'tenant_id': context.tenant_id}
server_list = self.aws_utils.get_nova_client().servers.list(search_opts=search_opts)
search_opts = {
'ip': fixed_ip_address,
'tenant_id': context.tenant_id
}
server_list = self.aws_utils.get_nova_client().servers.list(
search_opts=search_opts)
if len(server_list) > 0:
server = server_list[0]
if 'ec2_id' in server.metadata:
ec2_id = server.metadata['ec2_id']
if floating_ip_address is not None and ec2_id is not None:
self.aws_utils.associate_elastic_ip_to_ec2_instance(floating_ip_address, ec2_id)
LOG.info("EC2 ID found for IP %s : %s" % (fixed_ip_address, ec2_id))
self.aws_utils.associate_elastic_ip_to_ec2_instance(
floating_ip_address, ec2_id)
LOG.info("EC2 ID found for IP %s : %s" % (fixed_ip_address,
ec2_id))
else:
LOG.warning("EC2 ID not found to associate the floating IP")
raise exceptions.AwsException(error_code="No Server Found",
raise exceptions.AwsException(
error_code="No Server Found",
message="No server found with the Required IP")
def update_floatingip(self, context, id, floatingip):
floating_ip_dict = super(AwsRouterPlugin, self).get_floatingip(context, id)
if 'floatingip' in floatingip and 'port_id' in floatingip['floatingip']:
floating_ip_dict = super(AwsRouterPlugin, self).get_floatingip(
context, id)
if ('floatingip' in floatingip and
'port_id' in floatingip['floatingip']):
port_id = floatingip['floatingip']['port_id']
if port_id is not None:
# Associate Floating IP
LOG.info("Associating elastic IP %s with port %s" %
(floating_ip_dict['floating_ip_address'], port_id))
self._associate_floatingip_to_port(context,
floating_ip_dict['floating_ip_address'], port_id)
(floating_ip_dict['floating_ip_address'], port_id))
self._associate_floatingip_to_port(
context, floating_ip_dict['floating_ip_address'], port_id)
else:
try:
# Port Disassociate
self.aws_utils.disassociate_elastic_ip_from_ec2_instance(floating_ip_dict['floating_ip_address'])
self.aws_utils.disassociate_elastic_ip_from_ec2_instance(
floating_ip_dict['floating_ip_address'])
except exceptions.AwsException as e:
if 'Association ID not found' in e.msg:
# Since its already disassociated on EC2, we continue and remove the association here.
LOG.warn("Association for Elastic IP not found. Probable out of band change on EC2.")
# Since its already disassociated on EC2, we continue
# and remove the association here.
LOG.warn("Association for Elastic IP not found. "
"Probable out of band change on EC2.")
elif 'InvalidAddress.NotFound' in e.msg:
LOG.warn("Elastic IP cannot be found in EC2. Probably removed out of band on EC2.")
LOG.warn("Elastic IP cannot be found in EC2. Probably "
"removed out of band on EC2.")
else:
raise e
return super(AwsRouterPlugin, self).update_floatingip(context, id, floatingip)
return super(AwsRouterPlugin, self).update_floatingip(
context, id, floatingip)
def delete_floatingip(self, context, id):
floating_ip = super(AwsRouterPlugin, self).get_floatingip(context, id)
@ -159,19 +176,23 @@ class AwsRouterPlugin(service_base.ServicePluginBase,
raise e
return super(AwsRouterPlugin, self).delete_floatingip(context, id)
##### ROUTERS #####
# ROUTERS
def create_router(self, context, router):
try:
router_name = router['router']['name']
internet_gw_res = self.aws_utils.create_internet_gateway_resource()
ret_obj = super(AwsRouterPlugin, self).create_router(context, router)
internet_gw_res.create_tags(Tags=[
{'Key': 'Name', 'Value': router_name},
{'Key': 'openstack_router_id', 'Value': ret_obj['id']}
])
ret_obj = super(AwsRouterPlugin, self).create_router(
context, router)
internet_gw_res.create_tags(Tags=[{
'Key': 'Name',
'Value': router_name
}, {
'Key': 'openstack_router_id',
'Value': ret_obj['id']
}])
LOG.info("Created AWS router %s with openstack id %s" %
(router_name, ret_obj['id']))
(router_name, ret_obj['id']))
return ret_obj
except Exception as e:
LOG.error("Error while creating router %s" % e)
@ -188,22 +209,27 @@ class AwsRouterPlugin(service_base.ServicePluginBase,
return super(AwsRouterPlugin, self).delete_router(context, id)
def update_router(self, context, id, router):
## get internet gateway resource by openstack router id and update the tags
# get internet gateway resource by openstack router id and update the
# tags
try:
if 'router' in router and 'name' in router['router']:
router_name = router['router']['name']
tags_list = [
{'Key': 'Name', 'Value': router_name},
{'Key': 'openstack_router_id', 'Value': id}
]
tags_list = [{
'Key': 'Name',
'Value': router_name
}, {
'Key': 'openstack_router_id',
'Value': id
}]
LOG.info("Updated router %s" % id)
self.aws_utils.create_tags_internet_gw_from_router_id(id, tags_list)
self.aws_utils.create_tags_internet_gw_from_router_id(
id, tags_list)
except Exception as e:
LOG.error("Error in Updating Router: %s " % e)
raise e
return super(AwsRouterPlugin, self).update_router(context, id, router)
###### ROUTER INTERFACE ######
# ROUTER INTERFACE
def add_router_interface(self, context, router_id, interface_info):
subnet_id = interface_info['subnet_id']
@ -214,32 +240,44 @@ class AwsRouterPlugin(service_base.ServicePluginBase,
# Get Internet Gateway ID
ig_id = self.aws_utils.get_internet_gw_from_router_id(router_id)
# Get VPC ID
vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(neutron_network_id)
vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
neutron_network_id)
self.aws_utils.attach_internet_gateway(ig_id, vpc_id)
# Search for a Route table tagged with Router-id
route_tables = self.aws_utils.get_route_table_by_router_id(router_id)
route_tables = self.aws_utils.get_route_table_by_router_id(
router_id)
if len(route_tables) == 0:
# If not tagged, Fetch all the Route Tables Select one and tag it
route_tables = self.aws_utils.describe_route_tables_by_vpc_id(vpc_id)
# If not tagged, Fetch all the Route Tables Select one and tag
# it
route_tables = self.aws_utils.describe_route_tables_by_vpc_id(
vpc_id)
if len(route_tables) > 0:
route_table = route_tables[0]
route_table_res = self.aws_utils._get_ec2_resource().RouteTable(route_table['RouteTableId'])
route_table_res.create_tags(Tags=[
{'Key': 'openstack_router_id', 'Value': router_id}
])
route_table_res = self.aws_utils._get_ec2_resource(
).RouteTable(route_table['RouteTableId'])
route_table_res.create_tags(Tags=[{
'Key':
'openstack_router_id',
'Value':
router_id
}])
if len(route_tables) > 0:
route_table = route_tables[0]
self.aws_utils.create_default_route_to_ig(route_table['RouteTableId'], ig_id, ignore_errors=True)
self.aws_utils.create_default_route_to_ig(
route_table['RouteTableId'], ig_id, ignore_errors=True)
except Exception as e:
LOG.error("Error in Creating Interface: %s " % e)
raise e
return super(AwsRouterPlugin, self).add_router_interface(context, router_id, interface_info)
return super(AwsRouterPlugin, self).add_router_interface(
context, router_id, interface_info)
def remove_router_interface(self, context, router_id, interface_info):
LOG.info("Deleting port %s from router %s" % (interface_info['port_id'], router_id))
LOG.info("Deleting port %s from router %s" %
(interface_info['port_id'], router_id))
self.aws_utils.detach_internet_gateway_by_router_id(router_id)
route_tables = self.aws_utils.get_route_table_by_router_id(router_id)
if route_tables:
route_table_id = route_tables[0]['RouteTableId']
self.aws_utils.delete_default_route_to_ig(route_table_id)
return super(AwsRouterPlugin, self).remove_router_interface(context, router_id, interface_info)
return super(AwsRouterPlugin, self).remove_router_interface(
context, router_id, interface_info)

View File

@ -1,21 +1,19 @@
# Copyright 2017 Platform9 Systems Inc.(http://www.platform9.com)
#
# 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.
"""
Copyright 2017 Platform9 Systems Inc.(http://www.platform9.com)
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.
"""
import neutron_lib
from distutils.version import LooseVersion
from oslo_log import log as logging
from distutils.version import LooseVersion
from neutron.common import exceptions
from neutron.common import gceconf
from neutron.common import gceutils
@ -30,6 +28,7 @@ from neutron.plugins.common import constants
from neutron.quota import resource_registry
from neutron.services import service_base
from neutron_lib import constants as n_const
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
@ -37,17 +36,19 @@ if LooseVersion(neutron_lib.__version__) < LooseVersion("1.0.0"):
router = l3_db.Router
floating_ip = l3_db.FloatingIP
plugin_type = constants.L3_ROUTER_NAT
service_plugin_class = service_base.ServicePluginBase
else:
from neutron.db.models import l3
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.services import base as service_base
from neutron_lib.services import base
router = l3.Router
floating_ip = l3.FloatingIP
plugin_type = plugin_constants.L3
service_plugin_class = base.ServicePluginBase
class GceRouterPlugin(
service_base.ServicePluginBase, common_db_mixin.CommonDbMixin,
service_plugin_class, common_db_mixin.CommonDbMixin,
extraroute_db.ExtraRoute_db_mixin, l3_hamode_db.L3_HA_NAT_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin, l3_dvrscheduler_db.L3_DVRsch_db_mixin,
l3_hascheduler_db.L3_HA_scheduler_db_mixin):
@ -93,7 +94,9 @@ class GceRouterPlugin(
LOG.info('Released GCE static IP %s' % floatingip)
def create_floatingip(self, context, floatingip):
compute, project, region = self.gce_svc, self.gce_project, self.gce_region
compute = self.gce_svc
project = self.gce_project
region = self.gce_region
public_ip_allocated = None
try:
@ -154,7 +157,9 @@ class GceRouterPlugin(
context, id)
public_ip_allocated = orig_floatingip['floating_ip_address']
port_id = floatingip_dict['port_id']
compute, project, region = self.gce_svc, self.gce_project, self.gce_region
compute = self.gce_svc
project = self.gce_project
region = self.gce_region
gceutils.release_floatingip(compute, project, region,
public_ip_allocated)
if port_id:
@ -166,7 +171,9 @@ class GceRouterPlugin(
def delete_floatingip(self, context, id):
floating_ip = super(GceRouterPlugin, self).get_floatingip(context, id)
public_ip_allocated = floating_ip['floating_ip_address']
compute, project, region = self.gce_svc, self.gce_project, self.gce_region
compute = self.gce_svc
project = self.gce_project
region = self.gce_region
self._cleanup_floatingip(compute, project, region, public_ip_allocated)
return super(GceRouterPlugin, self).delete_floatingip(context, id)

View File

@ -1,16 +1,15 @@
# 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.
"""
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

View File

@ -1,32 +1,32 @@
# 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.
"""
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 mock
import os
from neutron.tests import base
from neutron.plugins.ml2.drivers.gce.mech_gce import GceMechanismDriver
from neutron.plugins.ml2.drivers.gce.mech_gce import SecurityGroupInvalidDirection
from neutron.extensions import securitygroup as sg
from neutron.manager import NeutronManager
from neutron.plugins.ml2.drivers.gce.mech_gce import GceMechanismDriver
from neutron.plugins.ml2.drivers.gce.mech_gce import SecurityGroupInvalidDirection # noqa
from neutron.tests import base
from neutron.tests.common.gce import gce_mock
from neutron.tests.common.gce.gce_mock import FakeNeutronManager
from neutron.tests.unit.extensions import test_securitygroup as test_sg
from neutron.extensions import securitygroup as sg
from neutron_lib import constants as const
DATA_DIR = os.path.dirname(os.path.abspath("gce_mock.py")) + '/data'
NETWORK_LINK = "projects/omni-163105/global/networks/net-03c4f178-670e-4805-a511-9470ca4a0b06"
NETWORKS_LINK = "projects/omni-163105/global/networks"
NETWORK_LINK = NETWORKS_LINK + "/net-03c4f178-670e-4805-a511-9470ca4a0b06"
if hasattr(NeutronManager, "get_plugin"):
neutron_get_plugin = 'neutron.manager.NeutronManager.get_plugin'

View File

@ -1,39 +1,35 @@
# Copyright 2016 Platform9 Systems Inc.
# 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.
"""
Copyright 2016 Platform9 Systems Inc.
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.
"""
from moto import mock_ec2
from moto import mock_cloudwatch
from moto.ec2 import ec2_backends
from nova import context
from nova import exception
from nova import objects
from nova import test
from nova.compute import power_state
from nova.compute import vm_states
from nova.compute import task_states
from nova.image import glance
from nova.tests.unit import fake_instance
from nova.tests.unit import matchers
from nova.virt.ec2 import EC2Driver
from oslo_config import cfg
from oslo_utils import uuidutils
import base64
import boto
import contextlib
import mock
from moto import mock_cloudwatch
from moto import mock_ec2
from nova.compute import task_states
from nova import context
from nova import exception
from nova.image import glance
from nova import objects
from nova import test
from nova.tests.unit import fake_instance
from nova.tests.unit import matchers
from nova.virt.ec2 import EC2Driver
from oslo_utils import uuidutils
if hasattr(glance, "GlanceImageService"):
from nova.image.glance import GlanceImageService
else:

View File

@ -1,16 +1,15 @@
# 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.
"""
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 six

View File

@ -1,28 +1,27 @@
# 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.
"""
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 mock
import os
from nova import test
from nova import context
from nova import exception
from nova import test
from nova.tests.unit import fake_instance
from nova.virt import fake
from nova.virt.gce.driver import GCEDriver
from nova.tests.unit.virt.gce import gce_mock
from nova.tests.unit.virt.gce.gce_mock import FakeImageService
from nova.virt import fake
from nova.virt.gce.driver import GCEDriver
DATA_DIR = os.path.dirname(os.path.abspath(__file__)) + '/data'

View File

@ -1,17 +1,16 @@
# Copyright (c) 2014 Thoughtworks.
# Copyright (c) 2016 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.
"""
Copyright (c) 2014 Thoughtworks.
Copyright (c) 2016 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.
"""
from nova.virt.ec2 import ec2driver

View File

@ -1,35 +1,36 @@
# Copyright (c) 2014 ThoughtWorks
# Copyright (c) 2016 Platform9 Systems Inc.
# 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.
"""
Copyright (c) 2014 ThoughtWorks
Copyright (c) 2016 Platform9 Systems Inc.
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.
"""
from oslo.config import cfg
from nova import db
from nova.openstack.common import log as logging
from nova.scheduler import filters
from oslo.config import cfg
opts = [
cfg.BoolOpt('cloud_burst',
help='Switch to enable could bursting'),
cfg.StrOpt('cloud_burst_availability_zone',
help='The availability zone of only compute hosts with the public cloud driver'),
cfg.BoolOpt('cloud_burst', help='Switch to enable could bursting'),
cfg.StrOpt(
'cloud_burst_availability_zone',
help='Availability zone of compute hosts with the public cloud driver'
),
]
CONF = cfg.CONF
CONF.register_opts(opts)
LOG = logging.getLogger(__name__)
class CloudBurstFilter(filters.BaseHostFilter):
"""Filter for cloud burst availability zone"""
@ -37,11 +38,11 @@ class CloudBurstFilter(filters.BaseHostFilter):
def host_passes(self, host_state, filter_properties):
context = filter_properties['context'].elevated()
metadata = db.aggregate_metadata_get_by_host(context, host_state.host, key='availability_zone')
metadata = db.aggregate_metadata_get_by_host(context, host_state.host,
key='availability_zone')
if CONF.cloud_burst:
return CONF.cloud_burst_availability_zone in metadata['availability_zone']
else:
return CONF.cloud_burst_availability_zone not in metadata['availability_zone']
return True
return CONF.cloud_burst_availability_zone in metadata[
'availability_zone']
return CONF.cloud_burst_availability_zone not in metadata[
'availability_zone']

View File

@ -1,52 +1,49 @@
# Copyright (c) 2014 Thoughtworks.
# Copyright (c) 2016 Platform9 Systems Inc.
# 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 expressed or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Connection to the Amazon Web Services - EC2 service"""
"""
Copyright (c) 2014 Thoughtworks.
Copyright (c) 2016 Platform9 Systems Inc.
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 expressed or implied. See the
License for the specific language governing permissions and limitations
under the License.
"""
import base64
import boto.ec2.cloudwatch
import datetime
import hashlib
import json
import sys
import time
import uuid
from threading import Lock
from boto import ec2, vpc
from boto import ec2
from boto import exception as boto_exc
from boto.exception import EC2ResponseError
from boto.regioninfo import RegionInfo
from oslo_config import cfg
from nova.i18n import *
from nova import block_device
from nova.compute import power_state
from nova.compute import task_states
from nova.console import type as ctype
from nova import db
from nova import exception
from nova.i18n import _
from nova.image import glance
from nova.virt import driver
from nova.virt.ec2.exception_handler import Ec2ExceptionHandler
from nova.virt import hardware
from nova.virt import virtapi
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import loopingcall
from nova.virt import driver
from nova.virt import virtapi
from nova.virt import hardware
from nova.virt.ec2.exception_handler import Ec2ExceptionHandler
LOG = logging.getLogger(__name__)
aws_group = cfg.OptGroup(name='AWS', title='Options to connect to an AWS cloud')
aws_group = cfg.OptGroup(name='AWS',
title='Options to connect to an AWS cloud')
aws_opts = [
cfg.StrOpt('secret_key', help='Secret key of AWS account', secret=True),
@ -70,7 +67,7 @@ aws_opts = [
]
CONF = cfg.CONF
#CONF.import_opt('my_ip', 'nova.netconf')
# CONF.import_opt('my_ip', 'nova.netconf')
CONF.register_group(aws_group)
CONF.register_opts(aws_opts, group=aws_group)
@ -126,7 +123,7 @@ EC2_FLAVOR_MAP = {
't2.small': {'memory_mb': 2048.0, 'vcpus': 1},
'x1.32xlarge': {'memory_mb': 1998848.0, 'vcpus': 128}
}
_EC2_NODES = None
DIAGNOSTIC_KEYS_TO_FILTER = ['group', 'block_device_mapping']
@ -175,7 +172,7 @@ class EC2Driver(driver.ComputeDriver):
'cpu_info': {},
'disk_available_least': CONF.AWS.max_disk_gb,
}
global _EC2_NODES
self._mounts = {}
self._interfaces = {}
self._uuid_to_ec2_instance = {}
@ -184,28 +181,27 @@ class EC2Driver(driver.ComputeDriver):
aws_endpoint = "ec2." + aws_region + ".amazonaws.com"
region = RegionInfo(name=aws_region, endpoint=aws_endpoint)
self.ec2_conn = ec2.EC2Connection(aws_access_key_id=CONF.AWS.access_key,
aws_secret_access_key=CONF.AWS.secret_key,
region=region)
self.ec2_conn = ec2.EC2Connection(
aws_access_key_id=CONF.AWS.access_key,
aws_secret_access_key=CONF.AWS.secret_key,
region=region)
self.cloudwatch_conn = ec2.cloudwatch.connect_to_region(
aws_region, aws_access_key_id=CONF.AWS.access_key,
aws_secret_access_key=CONF.AWS.secret_key)
LOG.info("EC2 driver init with %s region" % aws_region)
if not '_EC2_NODES' in globals():
if _EC2_NODES is None:
set_nodes([CONF.host])
def init_host(self, host):
"""
Initialize anything that is necessary for the driver to function,
"""Initialize anything that is necessary for the driver to function,
including catching up with currently running VM's on the given host.
"""
return
def list_instances(self):
"""
Return the names of all the instances known to the virtualization
"""Return the names of all the instances known to the virtualization
layer, as a list.
"""
all_instances = self.ec2_conn.get_only_instances()
@ -217,8 +213,8 @@ class EC2Driver(driver.ComputeDriver):
continue
if len(instance.tags) > 0:
if 'openstack_id' in instance.tags:
self._uuid_to_ec2_instance[instance.tags['openstack_id']] = \
instance
self._uuid_to_ec2_instance[
instance.tags['openstack_id']] = instance
else:
generate_uuid = True
else:
@ -238,13 +234,14 @@ class EC2Driver(driver.ComputeDriver):
pass
def _add_ssh_keys(self, key_name, key_data):
"""
Adds SSH Keys into AWS EC2 account
"""Adds SSH Keys into AWS EC2 account
:param key_name:
:param key_data:
:return:
"""
# TODO: Need to handle the cases if a key with the same keyname exists and different key content
# TODO(add_ssh_keys): Need to handle the cases if a key with the same
# keyname exists and different key content
exist_key_pair = self.ec2_conn.get_key_pair(key_name)
if not exist_key_pair:
LOG.info("Adding SSH key to AWS")
@ -253,8 +250,8 @@ class EC2Driver(driver.ComputeDriver):
LOG.info("SSH key already exists in AWS")
def _get_image_ami_id_from_meta(self, context, image_lacking_meta):
"""
Pulls the Image AMI ID from the location attribute of Image Meta
"""Pulls the Image AMI ID from the location attribute of Image Meta
:param image_meta:
:return: ami_id
"""
@ -266,14 +263,17 @@ class EC2Driver(driver.ComputeDriver):
return image_meta['aws_image_id']
except Exception as e:
LOG.error("Error in parsing Image Id: %s" % e)
raise exception.BuildAbortException("Invalid or Non-Existent Image ID Error")
raise exception.BuildAbortException("Invalid or Non-Existent Image"
" ID Error")
def _process_network_info(self, network_info):
"""
Will process network_info object by picking up only one Network out of many
"""Will process network_info object by picking up only one Network out
of many
:param network_info:
:return:
"""
LOG.info("Networks to be processed : %s" % network_info)
subnet_id = None
fixed_ip = None
@ -335,18 +335,20 @@ class EC2Driver(driver.ComputeDriver):
image_ami_id = self._get_image_ami_id_from_meta(context, image_meta)
subnet_id, fixed_ip, port_id, network_id = self._process_network_info(
network_info)
network_info)
if subnet_id is None or fixed_ip is None:
raise exception.BuildAbortException("Network configuration failure")
security_groups = self._get_instance_sec_grps(context, port_id, network_id)
raise exception.BuildAbortException("Network configuration "
"failure")
security_groups = self._get_instance_sec_grps(context, port_id,
network_id)
# Flavor
flavor_dict = instance['flavor']
flavor_type = flavor_dict['name']
# SSH Keys
if instance['key_name'] is not None and instance['key_data'] is not None:
if (instance['key_name'] is not None and
instance['key_data'] is not None):
self._add_ssh_keys(instance['key_name'], instance['key_data'])
# Creating the EC2 instance
@ -356,17 +358,17 @@ class EC2Driver(driver.ComputeDriver):
if 'user_data' in instance and instance['user_data'] is not None:
user_data = instance['user_data']
user_data = base64.b64decode(user_data)
try:
reservation = self.ec2_conn.run_instances(
instance_type=flavor_type, key_name=instance['key_name'],
image_id=image_ami_id, user_data=user_data,
subnet_id=subnet_id, private_ip_address=fixed_ip,
security_group_ids=security_groups)
instance_type=flavor_type, key_name=instance['key_name'],
image_id=image_ami_id, user_data=user_data,
subnet_id=subnet_id, private_ip_address=fixed_ip,
security_group_ids=security_groups)
ec2_instance = reservation.instances
ec2_instance_obj = ec2_instance[0]
ec2_id = ec2_instance[0].id
self._wait_for_state(instance, ec2_id, "running", power_state.RUNNING)
self._wait_for_state(instance, ec2_id, "running",
power_state.RUNNING)
instance['metadata'].update({'ec2_id': ec2_id})
ec2_instance_obj.add_tag("Name", instance['display_name'])
ec2_instance_obj.add_tag("openstack_id", instance['uuid'])
@ -377,9 +379,11 @@ class EC2Driver(driver.ComputeDriver):
if len(instances) > 0:
public_ip = instances[0].ip_address
if public_ip is not None:
instance['metadata'].update({'public_ip_address': public_ip})
instance['metadata'].update({
'public_ip_address': public_ip})
except EC2ResponseError as ec2_exception:
actual_exception = Ec2ExceptionHandler.get_processed_exception(ec2_exception)
actual_exception = Ec2ExceptionHandler.get_processed_exception(
ec2_exception)
LOG.info("Error in starting instance %s" % (actual_exception))
raise exception.BuildAbortException(actual_exception.message)
@ -390,19 +394,20 @@ class EC2Driver(driver.ComputeDriver):
return self._uuid_to_ec2_instance[instance.uuid].id
# if none of the conditions are met we cannot map OpenStack UUID to
# AWS ID.
raise exception.InstanceNotFound('Instance %s not found' % instance.uuid)
raise exception.InstanceNotFound('Instance {0} not found'.format(
instance.uuid))
def snapshot(self, context, instance, image_id, update_task_state):
"""Snapshot an image of the specified instance
on EC2 and create an Image which gets stored in AMI (internally in EBS Snapshot)
"""Snapshot an image of the specified instance on EC2 and create an
Image which gets stored in AMI (internally in EBS Snapshot)
:param context: security context
:param instance: nova.objects.instance.Instance
:param image_id: Reference to a pre-created image that will hold the snapshot.
:param image_id: Reference to a pre-created image that will hold the
snapshot.
"""
if instance.metadata.get('ec2_id', None) is None:
raise exception.InstanceNotFound(instance_id=instance['uuid'])
# Adding the below line only alters the state of the instance and not
# its image in OpenStack.
update_task_state(
@ -418,7 +423,6 @@ class EC2Driver(driver.ComputeDriver):
name=str(image_id), description="Image created by OpenStack",
no_reboot=False, dry_run=False)
LOG.info("Image created: %s." % ec2_image_id)
# The instance will be in pending state when it comes up, waiting
# for it to be in available
self._wait_for_image_state(ec2_image_id, "available")
@ -433,8 +437,7 @@ class EC2Driver(driver.ComputeDriver):
'image_state': 'available',
'owner_id': instance['project_id'],
'ramdisk_id': instance['ramdisk_id'],
'ec2_image_id': ec2_image_id }
}
'ec2_image_id': ec2_image_id}}
# TODO(jhurt): This currently fails, leaving the status of an instance
# as 'snapshotting'
image_api.update(context, image_id, metadata)
@ -455,7 +458,7 @@ class EC2Driver(driver.ComputeDriver):
:param reboot_type: Either a HARD or SOFT reboot
:param block_device_info: Info pertaining to attached volumes
:param bad_volumes_callback: Function to handle any bad volumes
encountered
encountered
"""
if reboot_type == 'SOFT':
self._soft_reboot(
@ -464,12 +467,14 @@ class EC2Driver(driver.ComputeDriver):
self._hard_reboot(
context, instance, network_info, block_device_info)
def _soft_reboot(self, context, instance, network_info, block_device_info=None):
def _soft_reboot(self, context, instance, network_info,
block_device_info=None):
ec2_id = self._get_ec2_id_from_instance(instance)
self.ec2_conn.reboot_instances(instance_ids=[ec2_id], dry_run=False)
LOG.info("Soft Reboot Complete.")
def _hard_reboot(self, context, instance, network_info, block_device_info=None):
def _hard_reboot(self, context, instance, network_info,
block_device_info=None):
self.power_off(instance)
self.power_on(context, instance, network_info, block_device)
LOG.info("Hard Reboot Complete.")
@ -480,9 +485,8 @@ class EC2Driver(driver.ComputeDriver):
return CONF.my_ip
def set_admin_password(self, instance, new_pass):
"""
Boto doesn't support setting the password at the time of creating an instance.
hence not implemented.
"""Boto doesn't support setting the password at the time of creating an
instance, hence not implemented.
"""
pass
@ -519,14 +523,14 @@ class EC2Driver(driver.ComputeDriver):
pass
def power_off(self, instance, timeout=0, retry_interval=0):
"""
Power off the specified instance.
"""Power off the specified instance.
:param instance: nova.objects.instance.Instance
:param timeout: time to wait for GuestOS to shutdown
:param retry_interval: How often to signal guest while
waiting for it to shutdown
"""
# TODO: Need to use timeout and retry_interval
# TODO(timeout): Need to use timeout and retry_interval
ec2_id = self._get_ec2_id_from_instance(instance)
self.ec2_conn.stop_instances(
instance_ids=[ec2_id], force=False, dry_run=False)
@ -546,38 +550,38 @@ class EC2Driver(driver.ComputeDriver):
pass
def pause(self, instance):
"""
Boto doesn't support pause and cannot save system state and hence
"""Boto doesn't support pause and cannot save system state and hence
we've implemented the closest functionality which is to poweroff the
instance.
:param instance: nova.objects.instance.Instance
"""
self.power_off(instance)
def unpause(self, instance):
"""
Since Boto doesn't support pause and cannot save system state, we
"""Since Boto doesn't support pause and cannot save system state, we
had implemented the closest functionality which is to poweroff the
instance. and powering on such an instance in this method.
:param instance: nova.objects.instance.Instance
"""
self.power_on(
context=None, instance=instance, network_info=None, block_device_info=None)
self.power_on(context=None, instance=instance, network_info=None,
block_device_info=None)
def suspend(self, context, instance):
"""
Boto doesn't support suspend and cannot save system state and hence
"""Boto doesn't support suspend and cannot save system state and hence
we've implemented the closest functionality which is to poweroff the
instance.
:param instance: nova.objects.instance.Instance
"""
self.power_off(instance)
def resume(self, context, instance, network_info, block_device_info=None):
"""
Since Boto doesn't support suspend and we cannot save system state,
"""Since Boto doesn't support suspend and we cannot save system state,
we've implemented the closest functionality which is to power on the
instance.
:param instance: nova.objects.instance.Instance
"""
self.power_on(context, instance, network_info, block_device_info)
@ -603,7 +607,7 @@ class EC2Driver(driver.ComputeDriver):
try:
ec2_id = self._get_ec2_id_from_instance(instance)
ec2_instances = self.ec2_conn.get_only_instances(
instance_ids=[ec2_id])
instance_ids=[ec2_id])
except exception.InstanceNotFound as ex:
# Exception while fetching instance info from AWS
LOG.exception('Exception in destroy while fetching EC2 id for '
@ -625,13 +629,12 @@ class EC2Driver(driver.ComputeDriver):
power_state.SHUTDOWN)
except Exception as ex:
LOG.exception("Exception while destroying instance: %s" %
str(ex))
str(ex))
raise ex
def attach_volume(self, context, connection_info, instance, mountpoint,
disk_bus=None, device_type=None, encryption=None):
"""Attach the disk to the instance at mountpoint using info.
"""
"""Attach the disk to the instance at mountpoint using info."""
instance_name = instance['name']
if instance_name not in self._mounts:
self._mounts[instance_name] = {}
@ -644,9 +647,9 @@ class EC2Driver(driver.ComputeDriver):
self.ec2_conn.attach_volume(volume_id, ec2_id, mountpoint,
dry_run=False)
def detach_volume(self, connection_info, instance, mountpoint, encryption=None):
"""Detach the disk attached to the instance.
"""
def detach_volume(self, connection_info, instance, mountpoint,
encryption=None):
"""Detach the disk attached to the instance."""
try:
del self._mounts[instance['name']][mountpoint]
except KeyError:
@ -659,21 +662,20 @@ class EC2Driver(driver.ComputeDriver):
def swap_volume(self, old_connection_info, new_connection_info,
instance, mountpoint, resize_to):
"""Replace the disk attached to the instance.
"""
# TODO: Use resize_to parameter
"""Replace the disk attached to the instance."""
# TODO(resize_to): Use resize_to parameter
instance_name = instance['name']
if instance_name not in self._mounts:
self._mounts[instance_name] = {}
self._mounts[instance_name][mountpoint] = new_connection_info
old_volume_id = old_connection_info['data']['volume_id']
new_volume_id = new_connection_info['data']['volume_id']
self.detach_volume(old_connection_info, instance, mountpoint)
# wait for the old volume to detach successfully to make sure
# /dev/sdn is available for the new volume to be attached
# TODO: remove the sleep and poll AWS for the status of volume
# TODO(remove_sleep): remove sleep and poll AWS for the status of
# volume
time.sleep(60)
ec2_id = self._get_ec2_id_from_instance(instance)
self.ec2_conn.attach_volume(new_volume_id,
@ -696,6 +698,7 @@ class EC2Driver(driver.ComputeDriver):
def get_info(self, instance):
"""Get the current status of an instance, by name (not ID!)
:param instance: nova.objects.instance.Instance object
Returns a dict containing:
:state: the running state, one of the power_state codes
@ -710,11 +713,11 @@ class EC2Driver(driver.ComputeDriver):
elif 'metadata' in instance and 'ec2_id' in instance['metadata']:
ec2_id = instance['metadata']['ec2_id']
ec2_instances = self.ec2_conn.get_only_instances(
instance_ids=[ec2_id], filters=None, dry_run=False,
max_results=None)
instance_ids=[ec2_id], filters=None, dry_run=False,
max_results=None)
if len(ec2_instances) == 0:
LOG.warning(_("EC2 instance with ID %s not found") % ec2_id,
instance=instance)
instance=instance)
raise exception.InstanceNotFound(instance_id=instance['name'])
ec2_instance = ec2_instances[0]
else:
@ -725,13 +728,9 @@ class EC2Driver(driver.ComputeDriver):
memory_mb = ec2_flavor['memory_mb']
vcpus = ec2_flavor['vcpus']
return hardware.InstanceInfo(
state=power_state,
max_mem_kb=memory_mb,
mem_kb=memory_mb,
num_cpu=vcpus,
cpu_time_ns=0,
id=instance.id)
return hardware.InstanceInfo(state=power_state, max_mem_kb=memory_mb,
mem_kb=memory_mb, num_cpu=vcpus,
cpu_time_ns=0, id=instance.id)
def allow_key(self, key):
for key_to_filter in DIAGNOSTIC_KEYS_TO_FILTER:
@ -743,13 +742,12 @@ class EC2Driver(driver.ComputeDriver):
"""Return data about VM diagnostics."""
ec2_id = self._get_ec2_id_from_instance(instance)
ec2_instances = self.ec2_conn.get_only_instances(instance_ids=[ec2_id],
filters=None,
dry_run=False,
max_results=None)
ec2_instances = self.ec2_conn.get_only_instances(
instance_ids=[ec2_id], filters=None, dry_run=False,
max_results=None)
if len(ec2_instances) == 0:
LOG.warning(_("EC2 instance with ID %s not found") % ec2_id,
instance=instance)
instance=instance)
raise exception.InstanceNotFound(instance_id=instance['name'])
ec2_instance = ec2_instances[0]
@ -758,7 +756,8 @@ class EC2Driver(driver.ComputeDriver):
if self.allow_key(key):
diagnostics['instance.' + key] = str(value)
metrics = self.cloudwatch_conn.list_metrics(dimensions={'InstanceId': ec2_id})
metrics = self.cloudwatch_conn.list_metrics(
dimensions={'InstanceId': ec2_id})
for metric in metrics:
end = datetime.datetime.utcnow()
@ -766,7 +765,6 @@ class EC2Driver(driver.ComputeDriver):
details = metric.query(start, end, 'Average', None, 3600)
if len(details) > 0:
diagnostics['metrics.' + str(metric)] = details[0]
return diagnostics
def get_all_bw_counters(self, instances):
@ -804,7 +802,8 @@ class EC2Driver(driver.ComputeDriver):
if instance.ip_address is not None:
host_ip = instance.ip_address
if host_ip is not None:
LOG.info("Found the IP of the instance IP:%s and port:%s" % (host_ip, vnc_port))
LOG.info("Found the IP of the instance IP:%s and port:%s" % (
host_ip, vnc_port))
return ctype.ConsoleVNC(host=host_ip, port=vnc_port)
else:
LOG.info("Ip not Found for the instance")
@ -813,10 +812,9 @@ class EC2Driver(driver.ComputeDriver):
'port': 5901}
def get_spice_console(self, instance):
""" Simple Protocol for Independent Computing Environments
"""Simple Protocol for Independent Computing Environments
Doesn't seem to be supported by AWS EC2 directly
"""
return {'internal_access_path': 'EC2',
'host': 'EC2spiceconsole.com',
'port': 6969,
@ -833,13 +831,17 @@ class EC2Driver(driver.ComputeDriver):
def get_available_resource(self, nodename):
"""Retrieve resource information.
Updates compute manager resource info on ComputeNode table.
This method is called when nova-compute launches and as part of a periodic task that records results in the DB.
Since we don't have a real hypervisor, pretend we have lots of disk and ram.
This method is called when nova-compute launches and as part of a
periodic task that records results in the DB.
Since we don't have a real hypervisor, pretend we have lots of disk and
ram.
:param nodename:
node which the caller want to get resources from
a driver that manages only one node can safely ignore this
:returns: Dictionary describing resources
"""
global _EC2_NODES
if nodename not in _EC2_NODES:
return {}
@ -891,29 +893,35 @@ class EC2Driver(driver.ComputeDriver):
network_info, image_meta, resize_instance,
block_device_info=None, power_on=True):
"""Completes a resize
:param migration: the migrate/resize information
:param instance: nova.objects.instance.Instance being migrated/resized
:param power_on: is True the instance should be powered on
"""
ec2_id = self._get_ec2_id_from_instance(instance)
ec_instance_info = self.ec2_conn.get_only_instances(
instance_ids=[ec2_id], filters=None, dry_run=False, max_results=None)
instance_ids=[ec2_id], filters=None, dry_run=False,
max_results=None)
ec2_instance = ec_instance_info[0]
# EC2 instance needs to be stopped to modify it's attribute. So we stop the instance,
# modify the instance type in this case, and then restart the instance.
# EC2 instance needs to be stopped to modify it's attribute. So we stop
# the instance, modify the instance type in this case, and then restart
# the instance.
ec2_instance.stop()
self._wait_for_state(instance, ec2_id, "stopped", power_state.SHUTDOWN)
new_instance_type = flavor_map[migration['new_instance_type_id']]
# TODO(flavor_map is undefined): need to check flavor type variable
new_instance_type = flavor_map[migration['new_instance_type_id']] # noqa
ec2_instance.modify_attribute('instanceType', new_instance_type)
def confirm_migration(self, migration, instance, network_info):
"""Confirms a resize, destroying the source VM.
:param instance: nova.objects.instance.Instance
"""
ec2_id = self._get_ec2_id_from_instance(instance)
ec_instance_info = self.ec2_conn.get_only_instances(
instance_ids=[ec2_id], filters=None, dry_run=False, max_results=None)
instance_ids=[ec2_id], filters=None, dry_run=False,
max_results=None)
ec2_instance = ec_instance_info[0]
ec2_instance.start()
self._wait_for_state(instance, ec2_id, "running", power_state.RUNNING)
@ -928,6 +936,7 @@ class EC2Driver(driver.ComputeDriver):
def get_host_stats(self, refresh=False):
"""Return EC2 Host Status of name, ram, disk, network."""
stats = []
global _EC2_NODES
for nodename in _EC2_NODES:
host_status = self.host_status_base.copy()
host_status['hypervisor_hostname'] = nodename
@ -976,6 +985,7 @@ class EC2Driver(driver.ComputeDriver):
return {'ip': '127.0.0.1', 'initiator': 'EC2', 'host': 'EC2host'}
def get_available_nodes(self, refresh=False):
global _EC2_NODES
return _EC2_NODES
def instance_on_disk(self, instance):
@ -997,8 +1007,8 @@ class EC2Driver(driver.ComputeDriver):
continue
if len(instance.tags) > 0:
if 'openstack_id' in instance.tags:
self._uuid_to_ec2_instance[instance.tags['openstack_id']] = \
instance
self._uuid_to_ec2_instance[
instance.tags['openstack_id']] = instance
else:
# Possibly a new discovered instance
generate_uuid = True
@ -1010,15 +1020,19 @@ class EC2Driver(driver.ComputeDriver):
self._uuid_to_ec2_instance[instance_uuid] = instance
return self._uuid_to_ec2_instance.keys()
def _wait_for_state(self, instance, ec2_id, desired_state, desired_power_state):
"""Wait for the state of the corrosponding ec2 instance to be in completely available state.
def _wait_for_state(self, instance, ec2_id, desired_state,
desired_power_state):
"""Wait for the state of the corrosponding ec2 instance to be in
completely available state.
:params:ec2_id: the instance's corrosponding ec2 id.
:params:desired_state: the desired state of the instance to be in.
"""
def _wait_for_power_state():
"""Called at an interval until the VM is running again.
"""
ec2_instance = self.ec2_conn.get_only_instances(instance_ids=[ec2_id])
ec2_instance = self.ec2_conn.get_only_instances(
instance_ids=[ec2_id])
state = ec2_instance[0].state
if state == desired_state:
@ -1026,28 +1040,33 @@ class EC2Driver(driver.ComputeDriver):
raise loopingcall.LoopingCallDone()
def _wait_for_status_check():
"""Power state of a machine might be ON, but status check is the one which gives the real
"""Power state of a machine might be ON, but status check is the
one which gives the real
"""
ec2_instance = self.ec2_conn.get_all_instance_status(instance_ids=[ec2_id])[0]
ec2_instance = self.ec2_conn.get_all_instance_status(
instance_ids=[ec2_id])[0]
if ec2_instance.system_status.status == 'ok':
LOG.info("Instance status check is %s / %s" %
(ec2_instance.system_status.status, ec2_instance.instance_status.status))
(ec2_instance.system_status.status,
ec2_instance.instance_status.status))
raise loopingcall.LoopingCallDone()
#waiting for the power state to change
# waiting for the power state to change
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_state)
timer.start(interval=1).wait()
def _wait_for_image_state(self, ami_id, desired_state):
"""Timer to wait for the image/snapshot to reach a desired state
:params:ami_id: correspoding image id in Amazon
:params:desired_state: the desired new state of the image to be in.
"""
def _wait_for_state():
"""Called at an interval until the AMI image is available."""
try:
images = self.ec2_conn.get_all_images(image_ids=[ami_id], owners=None,
executable_by=None, filters=None, dry_run=None)
images = self.ec2_conn.get_all_images(
image_ids=[ami_id], owners=None,
executable_by=None, filters=None, dry_run=None)
state = images[0].state
if state == desired_state:
LOG.info("Image has changed state to %s." % desired_state)

View File

@ -1,17 +1,30 @@
"""
Copyright (c) 2014 Thoughtworks.
Copyright (c) 2016 Platform9 Systems Inc.
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 expressed or implied. See the
License for the specific language governing permissions and limitations
under the License.
"""
from nova import exception
class Ec2ExceptionHandler:
"""
This is a class which can be used to create mapping between EC2 Exception messages to Nova based Exceptions.
Also gives control on the error message displayed to the user.
class Ec2ExceptionHandler(object):
"""This is a class which can be used to create mapping between EC2
Exception messages to Nova based Exceptions. Also gives control on the
error message displayed to the user.
"""
@staticmethod
def get_processed_exception(ec2_response_error_exc):
if ec2_response_error_exc.error_code == "AuthFailure":
return exception.Forbidden("Please check AWS credentials")
elif ec2_response_error_exc.error_code == "InvalidAMIID.NotFound":
if ec2_response_error_exc.error_code == "InvalidAMIID.NotFound":
return exception.ImageNotFoundEC2("Invalid Image")
else:
return exception.NovaException(ec2_response_error_exc.message)
return exception.NovaException(ec2_response_error_exc.message)

View File

@ -1,16 +1,15 @@
# 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.
"""
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.
"""
from nova.virt.gce import driver

View File

@ -1,16 +1,15 @@
# 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.
"""
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.
"""
from nova.compute import power_state

View File

@ -1,27 +1,23 @@
# Copyright (c) 2017 Platform9 Systems Inc. (http://www.platform9.com)
#
# 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.
'''
1. Source openstack RC file
2. python create-nova-flavors-gce.py <service_key_path> <project> <zone>
'''
"""
Copyright (c) 2017 Platform9 Systems Inc. (http://www.platform9.com)
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.
"""
import os
import sys
import gceutils
from gce import gceutils
from keystoneauth1 import loading
from keystoneauth1 import session
from novaclient import client as nova_client
from keystoneauth1 import loading, session
def get_env_param(env_name):
@ -42,7 +38,8 @@ def get_keystone_session(
if not project_name:
if not tenant_name:
raise Exception("Either OS_PROJECT_NAME or OS_TENANT_NAME is required.")
raise Exception("Either OS_PROJECT_NAME or OS_TENANT_NAME is "
"required.")
project_name = tenant_name
loader = loading.get_plugin_loader('password')
@ -78,7 +75,8 @@ class GceFlavors(object):
if __name__ == '__main__':
if len(sys.argv) != 4:
print('Usage: {0} <service_key_path> <project> <zone>'.format(sys.argv[0]))
print('Usage: {0} <service_key_path> <project> <zone>'.format(
sys.argv[0]))
sys.exit(1)
gce_flavors = GceFlavors(sys.argv[1], sys.argv[2], sys.argv[3])
gce_flavors.register_gce_flavors()

View File

@ -1,36 +1,36 @@
# 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.
"""Connection to the Google Cloud Platform - GCE service"""
"""
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 gceutils
import hashlib
import uuid
import time
import uuid
from googleapiclient.errors import HttpError
from nova.compute import task_states
import nova.conf
from nova import exception
from nova.image import glance
from nova.virt import driver, hardware
from nova.virt import driver
from nova.virt.gce.constants import GCE_STATE_MAP
from nova.virt import hardware
from oslo_config import cfg
from oslo_log import log as logging
from nova.compute import task_states
import gceutils
from googleapiclient.errors import HttpError
from nova.virt.gce.constants import GCE_STATE_MAP
LOG = logging.getLogger(__name__)
_GCE_NODES = None
gce_group = cfg.OptGroup(name='GCE',
title='Options to connect to Google cloud')
@ -61,7 +61,6 @@ DIAGNOSTIC_KEYS_TO_FILTER = ['group', 'block_device_mapping']
def set_nodes(nodes):
"""Sets GCE Driver's node.list.
It has effect on the following methods:
get_available_nodes()
get_available_resource
@ -75,7 +74,6 @@ def set_nodes(nodes):
def restore_nodes():
"""Resets GCE Driver's node list modified by set_nodes().
Usually called from tearDown().
"""
global _GCE_NODES
@ -112,15 +110,14 @@ class GCEDriver(driver.ComputeDriver):
self.gce_svc_key = CONF.GCE.service_key_path
def init_host(self, host):
"""
Initialize anything that is necessary for the driver to function
"""
"""Initialize anything that is necessary for the driver to function"""
global _GCE_NODES
self.gce_svc = gceutils.get_gce_service(self.gce_svc_key)
self.gce_flavor_info = gceutils.get_machines_info(
self.gce_svc, self.gce_project, self.gce_zone)
LOG.info("GCE driver init with %s project, %s region" %
(self.gce_project, self.gce_zone))
if '_GCE_NODES' not in globals():
if _GCE_NODES is None:
set_nodes([CONF.host])
def _get_uuid_from_gce_id(self, gce_id):
@ -208,11 +205,9 @@ class GCEDriver(driver.ComputeDriver):
admin_password, network_info=None, block_device_info=None):
"""Create a new instance/VM/domain on the virtualization platform.
Once this successfully completes, the instance should be
running (power_state.RUNNING).
If this fails, any partial instance should be completely
cleaned up, and the virtualization platform should be in the state
that it was before this call began.
running (power_state.RUNNING). If this fails, any partial instance
should be completely cleaned up, and the virtualization platform should
be in the state that it was before this call began.
:param context: security context <Not Yet Implemented>
:param instance: nova.objects.instance.Instance
@ -228,7 +223,8 @@ class GCEDriver(driver.ComputeDriver):
attached to the instance.
"""
compute, project, zone = self.gce_svc, self.gce_project, self.gce_zone
# GCE expects instance name in format "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
# GCE expects instance name in format
# "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
# So we need to construct it for GCE from uuid
gce_instance_name = 'inst-' + instance.uuid
LOG.info("Creating instance %s as %s on GCE." %
@ -267,6 +263,7 @@ class GCEDriver(driver.ComputeDriver):
def snapshot(self, context, instance, image_id, update_task_state):
"""Snapshot an image of the specified instance
:param context: security context
:param instance: nova.objects.instance.Instance
:param image_id: Reference to a pre-created image holding the snapshot.
@ -296,7 +293,8 @@ class GCEDriver(driver.ComputeDriver):
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
reason = "Unable to find boot disk from instance metadata {0}"
reason = reason.format(instance.uuid)
raise exception.InvalidMetadata(reason=reason)
disk_name = boot_disk['name']
LOG.debug("1. Found boot disk %s for instance %s" %
@ -408,12 +406,10 @@ class GCEDriver(driver.ComputeDriver):
def reboot(self, context, instance, network_info, reboot_type,
block_device_info=None, bad_volumes_callback=None):
"""
Reboot the specified instance.
After this is called successfully, the instance's state
goes back to power_state.RUNNING. The virtualization
platform should ensure that the reboot action has completed
successfully even in cases in which the underlying domain/vm
"""Reboot the specified instance. After this is called successfully,
the instance's state goes back to power_state.RUNNING. The
virtualization platform should ensure that the reboot action has
completed successfully even in cases in which the underlying domain/vm
is paused or halted/stopped.
:param instance: nova.objects.instance.Instance
@ -493,8 +489,8 @@ class GCEDriver(driver.ComputeDriver):
raise NotImplementedError()
def power_off(self, instance, timeout=0, retry_interval=0):
"""
Power off the specified instance.
"""Power off the specified instance.
:param instance: nova.objects.instance.Instance
:param timeout: time to wait for GuestOS to shutdown
:param retry_interval: How often to signal guest while
@ -529,6 +525,7 @@ class GCEDriver(driver.ComputeDriver):
GCE doesn't support pause and cannot save system state and hence
we've implemented the closest functionality which is to poweroff the
instance.
:param instance: nova.objects.instance.Instance
"""
LOG.info("Pause instance %s" % instance.uuid)
@ -539,6 +536,7 @@ class GCEDriver(driver.ComputeDriver):
Since GCE doesn't support pause and cannot save system state, we
had implemented the closest functionality which is to poweroff the
instance. and powering on such an instance in this method.
:param instance: nova.objects.instance.Instance
"""
LOG.info("Unpause instance %s" % instance.uuid)
@ -550,6 +548,7 @@ class GCEDriver(driver.ComputeDriver):
GCE doesn't support suspend and cannot save system state and hence
we've implemented the closest functionality which is to poweroff the
instance.
:param instance: nova.objects.instance.Instance
"""
LOG.info("Suspending instance %s" % instance.uuid)
@ -560,6 +559,7 @@ class GCEDriver(driver.ComputeDriver):
Since GCE doesn't support resume and we cannot save system state,
we've implemented the closest functionality which is to power on the
instance.
:param instance: nova.objects.instance.Instance
"""
LOG.info("Resuming instance %s" % instance.uuid)
@ -568,7 +568,6 @@ class GCEDriver(driver.ComputeDriver):
def destroy(self, context, instance, network_info, block_device_info=None,
destroy_disks=True, migrate_data=None):
"""Destroy the specified instance from the Hypervisor.
If the instance is not found (for example if networking failed), this
function should still succeed. It's probably a good idea to log a
warning in that case.
@ -596,16 +595,15 @@ class GCEDriver(driver.ComputeDriver):
except HttpError:
# Sometimes instance may not exist in GCE, in that case we just
# allow deleting VM from openstack
LOG.error("Instance %s not found in GCE, removing from openstack." %
instance.uuid)
LOG.error("Instance {0} not found in GCE, removing from openstack."
.format(instance.uuid))
return
gceutils.wait_for_operation(compute, project, operation)
LOG.info("Destroy Complete %s" % instance.uuid)
def attach_volume(self, context, connection_info, instance, mountpoint,
disk_bus=None, device_type=None, encryption=None):
"""Attach the disk to the instance at mountpoint using info.
"""
"""Attach the disk to the instance at mountpoint using info."""
compute, project, zone = self.gce_svc, self.gce_project, self.gce_zone
gce_id = self._get_gce_id_from_instance(instance)
gce_volume = connection_info['data']
@ -619,8 +617,7 @@ class GCEDriver(driver.ComputeDriver):
def detach_volume(self, connection_info, instance, mountpoint,
encryption=None):
"""Detach the disk attached to the instance.
"""
"""Detach the disk attached to the instance."""
compute, project, zone = self.gce_svc, self.gce_project, self.gce_zone
gce_id = self._get_gce_id_from_instance(instance)
gce_volume = connection_info['data']
@ -633,8 +630,7 @@ class GCEDriver(driver.ComputeDriver):
def swap_volume(self, old_connection_info, new_connection_info, instance,
mountpoint, resize_to):
"""Replace the disk attached to the instance.
"""
"""Replace the disk attached to the instance."""
raise NotImplementedError()
def attach_interface(self, instance, image_meta, vif):
@ -645,6 +641,7 @@ class GCEDriver(driver.ComputeDriver):
def get_info(self, instance):
"""Get the current status of an instance, by name (not ID!)
:param instance: nova.objects.instance.Instance object
Returns a dict containing:
:state: the running state, one of the power_state codes
@ -697,13 +694,12 @@ class GCEDriver(driver.ComputeDriver):
"""Return bandwidth usage counters for each interface on each
running VM.
"""
bw = []
return bw
def get_all_volume_usage(self, context, compute_host_bdms):
"""Return usage info for volumes attached to vms on
a given host.
"""
"""Return usage info for volumes attached to vms on a given host."""
volusage = []
return volusage
@ -717,8 +713,7 @@ class GCEDriver(driver.ComputeDriver):
raise NotImplementedError()
def get_spice_console(self, instance):
""" Simple Protocol for Independent Computing Environments
"""
"""Simple Protocol for Independent Computing Environments"""
raise NotImplementedError()
def get_console_pool_info(self, console_type):
@ -728,16 +723,17 @@ class GCEDriver(driver.ComputeDriver):
raise NotImplementedError()
def get_available_resource(self, nodename):
"""Retrieve resource information.
Updates compute manager resource info on ComputeNode table.
This method is called when nova-compute launches and as part of
a periodic task that records results in the DB.
Without real hypervisor, pretend we have lots of disk and ram.
:param nodename:
node which the caller want to get resources from
a driver that manages only one node can safely ignore this
"""Retrieve resource information. Updates compute manager resource info
on ComputeNode table. This method is called when nova-compute launches
and as part of a periodic task that records results in the DB. Without
real hypervisor, pretend we have lots of disk and ram.
:param nodename: node which the caller want to get resources from
a driver that manages only one node can safely ignore this
:returns: Dictionary describing resources
"""
global _GCE_NODES
if nodename not in _GCE_NODES:
return {}
@ -789,6 +785,7 @@ class GCEDriver(driver.ComputeDriver):
network_info, image_meta, resize_instance,
block_device_info=None, power_on=True):
"""Completes a resize
:param migration: the migrate/resize information
:param instance: nova.objects.instance.Instance being migrated/resized
:param power_on: is True the instance should be powered on
@ -797,6 +794,7 @@ class GCEDriver(driver.ComputeDriver):
def confirm_migration(self, migration, instance, network_info):
"""Confirms a resize, destroying the source VM.
:param instance: nova.objects.instance.Instance
"""
raise NotImplementedError()
@ -810,6 +808,7 @@ class GCEDriver(driver.ComputeDriver):
def get_host_stats(self, refresh=False):
"""Return GCE Host Status of name, ram, disk, network."""
global _GCE_NODES
stats = []
for nodename in _GCE_NODES:
host_status = self.host_status_base.copy()
@ -836,6 +835,7 @@ class GCEDriver(driver.ComputeDriver):
"""Start/Stop host maintenance window. On start, it triggers
guest VMs evacuation.
"""
if not mode:
return 'off_maintenance'
return 'on_maintenance'
@ -859,6 +859,7 @@ class GCEDriver(driver.ComputeDriver):
return {'ip': '127.0.0.1', 'initiator': 'GCE', 'host': 'GCEhost'}
def get_available_nodes(self, refresh=False):
global _GCE_NODES
return _GCE_NODES
def instance_on_disk(self, instance):

View File

@ -1,24 +1,23 @@
# 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.
"""
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
import six
from oslo_log import log as logging
import time
from nova.i18n import _
from googleapiclient.discovery import build
from nova.i18n import _
from oauth2client.client import GoogleCredentials
from oslo_log import log as logging
from oslo_service import loopingcall
from oslo_utils import reflection
from six.moves import urllib
@ -63,7 +62,7 @@ class _FixedIntervalWithTimeoutLoopingCall(loopingcall.LoopingCallBase):
# definition _FixedIntervalWithTimeoutLoopingCall
if not hasattr(loopingcall, 'FixedIntervalWithTimeoutLoopingCall'):
loopingcall.FixedIntervalWithTimeoutLoopingCall = \
_FixedIntervalWithTimeoutLoopingCall
_FixedIntervalWithTimeoutLoopingCall
class GceOperationError(Exception):
@ -72,6 +71,7 @@ class GceOperationError(Exception):
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
@ -84,6 +84,7 @@ def list_instances(compute, project, zone):
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
@ -96,6 +97,7 @@ def get_instance(compute, project, zone, instance):
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
@ -109,6 +111,7 @@ def get_instance_metadata(compute, project, zone, instance):
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
@ -125,7 +128,8 @@ def get_instances_metadata_key(compute, project, zone, instance, key):
def get_external_ip(compute, project, zone, instance):
""" Return external IP of GCE instance return empty string otherwise
"""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
@ -144,6 +148,7 @@ def get_external_ip(compute, project, zone, instance):
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
@ -165,7 +170,7 @@ def set_instance_metadata(compute, project, zone, instance, items,
else:
metadata['items'] = items
LOG.info("Adding metadata %s" % (metadata))
# TODO: Add del operation if required
# TODO(del_operation): Add del operation if required
return compute.instances().setMetadata(project=project, zone=zone,
instance=instance,
body=metadata).execute()
@ -174,6 +179,7 @@ def set_instance_metadata(compute, project, zone, instance, items,
def create_instance(compute, project, zone, name, image_link, machine_link,
network_interfaces):
"""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
@ -214,6 +220,7 @@ def create_instance(compute, project, zone, name, image_link, machine_link,
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
@ -225,6 +232,7 @@ def delete_instance(compute, project, zone, name):
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
@ -236,6 +244,7 @@ def stop_instance(compute, project, zone, name):
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
@ -247,6 +256,7 @@ def start_instance(compute, project, zone, name):
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
@ -258,10 +268,12 @@ def reset_instance(compute, project, zone, name):
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 operation: object, Operation resource obtained by calling GCE asynchronous API
All GCE asynchronous API's return operation resource to followup there completion.
:param operation: object, Operation resource obtained by calling GCE
asynchronous 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 timeout: int, Absoulte time period(seconds) to monitor GCE operation
"""
@ -295,6 +307,7 @@ def wait_for_operation(compute, project, operation, interval=1, timeout=60):
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
"""
@ -305,6 +318,7 @@ def get_gce_service(service_key):
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
@ -323,6 +337,7 @@ def get_machines_info(compute, project, zone):
def get_images(compute, project):
"""Return public images info from GCE
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
"""
@ -336,6 +351,7 @@ def get_images(compute, project):
def get_image(compute, project, name):
"""Return public images info from GCE
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
"""
@ -345,6 +361,7 @@ def get_image(compute, project, name):
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
@ -357,6 +374,7 @@ def delete_image(compute, project, name):
def get_network(compute, project, name):
"""Return network info
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param name: string, GCE network name
@ -367,6 +385,7 @@ def get_network(compute, project, name):
def attach_disk(compute, project, zone, instance_name, disk_name, disk_link):
"""Attach disk to instance
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
@ -392,6 +411,7 @@ def attach_disk(compute, project, zone, instance_name, disk_name, disk_link):
def detach_disk(compute, project, zone, instance_name, disk_name):
"""Detach disk from instance
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
@ -406,8 +426,7 @@ def detach_disk(compute, project, zone, instance_name, disk_name):
def get_instance_boot_disk(compute, project, zone, instance):
"""Return boot disk info for instance
"""
"""Return boot disk info for instance"""
gce_instance = get_instance(compute, project, zone, instance)
for disk in gce_instance['disks']:
if disk['boot']:
@ -426,6 +445,7 @@ def get_instance_boot_disk(compute, project, zone, 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
@ -446,6 +466,7 @@ def create_disk(compute, project, zone, name, size):
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
@ -459,6 +480,7 @@ def delete_disk(compute, project, zone, name):
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
@ -471,6 +493,7 @@ def get_disk(compute, project, zone, name):
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
@ -486,6 +509,7 @@ def snapshot_disk(compute, project, zone, name, snapshot_name):
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
@ -497,6 +521,7 @@ def get_snapshot(compute, project, name):
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
@ -509,6 +534,7 @@ def delete_snapshot(compute, project, name):
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

View File

@ -95,7 +95,6 @@ check_results glance_store
check_results neutron
echo "==========================================================================================="
echo "Log files are in $DIRECTORY/. Please check log files for UNKNOWN status."
echo "Cinder results: ${results[cinder]}"
echo "Nova results: ${results[nova]}"
echo "Glance results: ${results[glance_store]}"

View File

@ -1,16 +1,15 @@
# 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.
"""
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 setuptools

View File

@ -1,2 +0,0 @@
hacking>=0.12.0,<0.13 # Apache-2.0
zuul

15
tox.ini
View File

@ -1,25 +1,24 @@
[tox]
envlist = py27,pep8
envlist = pep8,py27
skipsdist = True
minversion = 2.3.2
[testenv]
usedevelop = True
deps = -r{toxinidir}/test-requirements.txt
whitelist_externals = bash
[testenv:py27]
deps = zuul
commands =
zuul-cloner --cache-dir /opt/git git://git.openstack.org \
openstack/nova openstack/cinder \
openstack/neutron openstack/glance_store
bash run_tests.sh -wj
./run_tests.sh -wj
[testenv:pep8]
deps = hacking>=0.12.0,<0.13
commands = flake8 {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
enable-extensions = H106,H203
ignore = E123,E125
ignore = E123,E125,H404,H405
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,openstack