Add Glance Store Driver for GCE

1. Consists of Glance store Driver and mechanism to discover GCE images
and them to Glance catalog as Glance images
2. Pick up correct Glance flavor in instance spawn
3. Mechanism to populate nova flavors from GCE machine info

Implements: blueprint gce-support
Change-Id: Iab71206b760ed6f516484690030d9ecb6c891e68
Signed-off-by: Sanket <sanket@infracloud.io>
This commit is contained in:
Sanket 2017-04-14 23:36:58 +05:30
parent ccbbe69a8b
commit 9a11c06e3b
11 changed files with 809 additions and 11 deletions

View File

@ -15,6 +15,7 @@ Glance store driver: Handles glance image endpoint for AWS AMIs
[glance_store] [glance_store]
default_store = aws default_store = aws
stores = aws stores = aws
show_multiple_locations = true
[AWS] [AWS]
secret_key = <your aws secret access key> secret_key = <your aws secret access key>
access_key = <your aws access key> access_key = <your aws access key>

View File

@ -13,7 +13,8 @@
# limitations under the License. # limitations under the License.
''' '''
Run this script as: python create-glance-credentials.py <access-key> <secret-key> <region-name> 1. Source your Openstack RC file.
2. Run this script as: python create-glance-images-aws.py <access-key> <secret-key> <region-name>
''' '''
import boto3 import boto3

View File

@ -0,0 +1,216 @@
# 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>
'''
import hashlib
import os
import requests
import sys
import uuid
import keystoneauth1
import gceutils
from keystoneauth1 import loading, session
from keystoneclient import client
from six.moves import urllib
def get_env_param(env_name):
if env_name in os.environ:
return os.environ[env_name]
raise Exception("%s environment variable not set." % env_name)
def get_keystone_session(
auth_url=get_env_param('OS_AUTH_URL'),
project_name=os.environ.get('OS_PROJECT_NAME'),
tenant_name=os.environ.get('OS_TENANT_NAME'),
project_domain_name=os.environ.get('OS_PROJECT_DOMAIN_NAME',
'default'), # noqa
username=get_env_param('OS_USERNAME'),
user_domain_name=os.environ.get('OS_USER_DOMAIN_NAME', 'default'),
password=get_env_param('OS_PASSWORD')):
if not project_name:
if not tenant_name:
raise Exception(
"Either OS_PROJECT_NAME or OS_TENANT_NAME is required.")
project_name = tenant_name
loader = loading.get_plugin_loader('password')
auth = loader.load_from_options(
auth_url=auth_url, project_name=project_name,
project_domain_name=project_domain_name, username=username,
user_domain_name=user_domain_name, password=password)
sess = session.Session(auth=auth)
return sess
class GceImages(object):
# Identified by referring,
# 1. https://console.cloud.google.com/compute/images
# 2. https://cloud.google.com/compute/docs/images#os-compute-support
GC_PUBLIC_PROJECTS = [
'cos-cloud', 'centos-cloud', 'debian-cloud', 'rhel-cloud',
'suse-cloud', 'ubuntu-os-cloud', 'windows-cloud', 'coreos-cloud',
'windows-sql-cloud'
]
def __init__(self, service_key_path):
self.gce_svc = gceutils.get_gce_service(service_key_path)
self.img_kind = {
'RAW': 'raw',
}
self.glance_client = RestClient()
def get_all_public_images(self):
images = []
for project in self.GC_PUBLIC_PROJECTS:
images.extend(gceutils.get_images(self.gce_svc, project))
return images
def register_gce_images(self):
for image in self.get_all_public_images():
self.create_image(self._gce_to_ostack_formatter(image))
def _get_project(self, gce_link):
# Sample GCE link path:
# https://<domain>/compute/v1/projects/<project>/global/images/<image>
parsed_link = urllib.parse.urlparse(gce_link)
project = parsed_link.path.strip('/').split('/')[3]
return project
def create_image(self, img_data):
"""
Create an OpenStack image.
:param img_data: dict -- Describes GCE Image
:returns: dict -- Response from REST call
:raises: requests.HTTPError
"""
glance_id = img_data['id']
gce_id = img_data['name']
print("Creating image: {0}".format(gce_id))
gce_link = img_data['gce_link']
gce_project = self._get_project(gce_link)
img_props = {
'locations': [{
'url':
'gce://%s/%s/%s' % (gce_project, gce_id, glance_id),
'metadata': {
'gce_link': gce_link
}
}]
}
try:
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)
print("Created image: {0}".format(gce_id))
except keystoneauth1.exceptions.http.Conflict:
# 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.
:param imageid: int -- The Ostack image UUID
:param props: dict -- Image properties to update
"""
if not props:
return
patch_body = []
for name, value in props.iteritems():
patch_body.append({
'op': 'replace',
'path': '/%s' % name,
'value': value
})
resp = self.glance_client.request('PATCH', '/v2/images/%s' % imageid,
json=patch_body)
resp.raise_for_status()
def _get_image_uuid(self, gce_id):
md = hashlib.md5()
md.update(gce_id)
return str(uuid.UUID(bytes=md.digest()))
def _gce_to_ostack_formatter(self, gce_img_data):
"""
Converts GCE img data to Openstack img data format.
:param img(dict): gce img data
:return(dict): ostack img data
"""
return {
'id': self._get_image_uuid(gce_img_data['id']),
'name': gce_img_data['name'],
'container_format': 'bare',
'disk_format': self.img_kind[gce_img_data['sourceType']],
'visibility': 'public',
'gce_image_id': gce_img_data['id'],
'gce_size': gce_img_data['diskSizeGb'],
'gce_link': gce_img_data['selfLink']
}
class RestClient(object):
def __init__(self):
auth_url = get_env_param('OS_AUTH_URL')
if auth_url.find('v2.0') > 0:
auth_url = auth_url.replace('v2.0', 'v3')
self.auth_url = auth_url
self.region_name = get_env_param('OS_REGION_NAME')
self.sess = get_keystone_session(auth_url=self.auth_url)
self.glance_endpoint = self.get_glance_endpoint()
def get_glance_endpoint(self):
self.ksclient = client.Client(auth_url=self.auth_url,
session=self.sess)
glance_service_id = self.ksclient.services.list(name='glance')[0].id
glance_url = self.ksclient.endpoints.list(
service=glance_service_id, interface='public', enabled=True,
region=self.region_name)[0].url
return glance_url
def request(self, method, path, **kwargs):
"""
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'] = '/'.join(
['application', 'openstack-images-v2.1-json-patch'])
resp = requests.request(method, url, headers=headers, **kwargs)
else:
resp = self.sess.request(url, method, headers=headers, **kwargs)
resp.raise_for_status()
return resp
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Usage: {0} <service_key_path>'.format(sys.argv[0]))
sys.exit(1)
gce_images = GceImages(sys.argv[1])
gce_images.register_gce_images()

305
glance/gce/gceutils.py Normal file
View File

@ -0,0 +1,305 @@
# 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 six
import time
from oslo_log import log as logging
from googleapiclient.discovery import build
from oauth2client.client import GoogleCredentials
LOG = logging.getLogger(__name__)
def list_instances(compute, project, zone):
"""Returns list of GCE instance resources for specified project
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
"""
result = compute.instances().list(project=project, zone=zone).execute()
if 'items' not in result:
return []
return result['items']
def get_instance(compute, project, zone, instance):
"""Get GCE instance information
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param instance: string, Name of the GCE instance resource
"""
result = compute.instances().get(project=project, zone=zone,
instance=instance).execute()
return result
def get_instance_metadata(compute, project, zone, instance):
"""Returns specified instance's metadata
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param instance: string or instance resource, Name of the GCE instance
resource or GCE instance resource
"""
if isinstance(instance, six.string_types):
instance = get_instance(compute, project, zone, instance)
return instance['metadata']
def get_instances_metadata_key(compute, project, zone, instance, key):
"""Returns particular key information for specified instance
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param instance: string or instance resource, Name of the GCE instance
resource or GCE instance resource
:param key: string, Key to retrieved from the instance metadata
"""
metadata = get_instance_metadata(compute, project, zone, instance)
if 'items' in metadata:
for item in metadata['items']:
if item['key'] == key:
return item['value']
return None
def get_external_ip(compute, project, zone, instance):
""" Return external IP of GCE instance return empty string otherwise
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param instance: string or instance resource, Name of the GCE instance
resource or GCE instance resource
"""
if isinstance(instance, six.string_types):
instance = get_instance(compute, project, zone, instance)
for interface in instance.get('networkInterfaces', []):
for config in interface.get('accessConfigs', []):
if config['type'] == 'ONE_TO_ONE_NAT' and 'natIP' in config:
return config['natIP']
return ''
def set_instance_metadata(compute, project, zone, instance, items,
operation='add'):
"""Perform specified operation on GCE instance metadata
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param instance: string or instance resource, Name of the GCE instance
resource or GCE instance resource
:param items: list, List of items where each item is dictionary having
'key' and 'value' as its members
Refer following sample list,
[ {'key': 'openstack_id', 'value': '1224555'}, ]
:param operation: string, Operation to perform on instance metadata
"""
if not isinstance(items, list):
raise TypeError(
"set_instance_metadata: items should be instance of list")
metadata = get_instance_metadata(compute, project, zone, instance)
if operation == 'add':
if 'items' in metadata:
metadata['items'].extend(items)
else:
metadata['items'] = items
LOG.info("Adding metadata %s" % (metadata, ))
# TODO: Add del operation if required
return compute.instances().setMetadata(project=project, zone=zone,
instance=instance,
body=metadata).execute()
def create_instance(compute, project, zone, name, image_link, machine_link):
"""Create GCE instance
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param name: string, Name of instance to be launched
:param image_link: url, GCE Image link for instance launch
:param machine_link: url, GCE Machine link for instance launch
"""
# source_disk_image = "projects/%s/global/images/%s" % (
# "debian-cloud", "debian-8-jessie-v20170327")
# machine_link = "zones/%s/machineTypes/n1-standard-1" % zone
LOG.info("Launching instance %s with image %s and machine %s" %
(name, image_link, machine_link))
config = {
'kind':
'compute#instance',
'name':
name,
'machineType':
machine_link,
# Specify the boot disk and the image to use as a source.
'disks': [{
'boot': True,
'autoDelete': True,
'initializeParams': {
'sourceImage': image_link,
}
}],
# Specify a network interface with NAT to access the public
# internet.
'networkInterfaces': [{
'network':
'global/networks/default',
'accessConfigs': [{
'type': 'ONE_TO_ONE_NAT',
'name': 'External NAT'
}]
}],
# Allow the instance to access cloud storage and logging.
'serviceAccounts': [{
'email':
'default',
'scopes': [
'https://www.googleapis.com/auth/devstorage.full_control',
'https://www.googleapis.com/auth/logging.write',
'https://www.googleapis.com/auth/compute'
]
}],
}
return compute.instances().insert(project=project, zone=zone,
body=config).execute()
def delete_instance(compute, project, zone, name):
"""Delete GCE instance
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param name: string, Name of the GCE instance
"""
return compute.instances().delete(project=project, zone=zone,
instance=name).execute()
def stop_instance(compute, project, zone, name):
"""Stop GCE instance
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param name: string, Name of the GCE instance
"""
return compute.instances().stop(project=project, zone=zone,
instance=name).execute()
def start_instance(compute, project, zone, name):
"""Start GCE instance
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param name: string, Name of the GCE instance
"""
return compute.instances().start(project=project, zone=zone,
instance=name).execute()
def reset_instance(compute, project, zone, name):
"""Hard reset GCE instance
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param name: string, Name of the GCE instance
"""
return compute.instances().reset(project=project, zone=zone,
instance=name).execute()
def wait_for_operation(compute, project, zone, operation, interval=1,
timeout=60):
"""Wait for GCE operation to complete, raise error if operation failure
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
:param operation: object, Operation resource obtained by calling GCE API
:param interval: int, Time period(seconds) between two GCE operation checks
:param timeout: int, Absoulte time period(seconds) to monitor GCE operation
"""
operation_name = operation['name']
if interval < 1:
raise ValueError("wait_for_operation: Interval should be positive")
iterations = timeout / interval
for i in range(iterations):
result = compute.zoneOperations().get(
project=project, zone=zone, operation=operation_name).execute()
if result['status'] == 'DONE':
LOG.info("Operation %s status is %s" % (operation_name,
result['status']))
if 'error' in result:
raise Exception(result['error'])
return result
time.sleep(interval)
raise Exception(
"wait_for_operation: Operation %s failed to perform in timeout %s" %
(operation_name, timeout))
def get_gce_service(service_key):
"""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
"""
credentials = GoogleCredentials.from_stream(service_key)
service = build('compute', 'v1', credentials=credentials)
return service
def get_machines_info(compute, project, zone):
"""Return machine type info from GCE
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
:param zone: string, GCE Name of zone
"""
response = compute.machineTypes().list(project=project,
zone=zone).execute()
GCE_MAP = {
machine_type['name']: {
'memory_mb': machine_type['memoryMb'],
'vcpus': machine_type['guestCpus']
}
for machine_type in response['items']
}
return GCE_MAP
def get_images(compute, project):
"""Return public images info from GCE
:param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id
"""
response = compute.images().list(project=project,
filter="status eq READY").execute()
if 'items' not in response:
return []
imgs = filter(lambda img: 'deprecated' not in img, response['items'])
return imgs
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
"""
result = compute.images().get(project=project, image=name).execute()
return result

View File

@ -0,0 +1 @@
google-api-python-client

View File

@ -0,0 +1,160 @@
# 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
import gceutils
import glance_store.driver
import glance_store.location
from glance_store import capabilities, exceptions
from glance_store.i18n import _
from oslo_config import cfg
from oslo_utils import units
from six.moves import urllib
LOG = logging.getLogger(__name__)
MAX_REDIRECTS = 5
STORE_SCHEME = 'gce'
gce_group = cfg.OptGroup(name='GCE',
title='Options to connect to Google cloud')
gce_opts = [
cfg.StrOpt('service_key_path', help='Service key of GCE account',
secret=True),
cfg.StrOpt('zone', help='GCE region'),
cfg.StrOpt('project_id', help='GCE project id'),
]
class StoreLocation(glance_store.location.StoreLocation):
"""Class describing GCE URI."""
def __init__(self, store_specs, conf):
super(StoreLocation, self).__init__(store_specs, conf)
def process_specs(self):
self.scheme = self.specs.get('scheme', STORE_SCHEME)
self.gce_project = self.specs.get('gce_project')
self.gce_id = self.specs.get('gce_id')
self.glance_id = self.specs.get('glance_id')
def get_uri(self):
return "{0}://{1}/{2}/{3}".format(self.scheme, self.gce_project,
self.gce_id, self.glance_id)
def parse_uri(self, uri):
"""Parse URLs based on GCE scheme """
LOG.debug('Parse uri %s' % (uri, ))
if not uri.startswith('%s://' % STORE_SCHEME):
reason = (_("URI %(uri)s must start with %(scheme)s://") % {
'uri': uri,
'scheme': STORE_SCHEME
})
LOG.error(reason)
raise exceptions.BadStoreUri(message=reason)
pieces = urllib.parse.urlparse(uri)
self.scheme = pieces.scheme
gce_project = pieces.netloc
gce_id, glance_id = pieces.path.strip('/').split('/')
parse_params = (gce_project, gce_id, glance_id)
if not all([parse_params]):
raise exceptions.BadStoreUri(uri=uri)
self.gce_project, self.gce_id, self.glance_id = parse_params
class Store(glance_store.driver.Store):
"""An implementation of the HTTP(S) Backend Adapter"""
_CAPABILITIES = (capabilities.BitMasks.RW_ACCESS |
capabilities.BitMasks.DRIVER_REUSABLE)
def __init__(self, conf):
super(Store, self).__init__(conf)
conf.register_group(gce_group)
conf.register_opts(gce_opts, group=gce_group)
self.gce_zone = conf.GCE.zone
self.gce_project = conf.GCE.project_id
self.gce_svc_key = conf.GCE.service_key_path
self.gce_svc = gceutils.get_gce_service(self.gce_svc_key)
LOG.info('Initialized GCE Glance Store driver')
def get_schemes(self):
"""
:retval tuple: containing valid scheme names to
associate with this store driver
"""
return ('gce', )
@capabilities.check
def get(self, location, offset=0, chunk_size=None, context=None):
"""
Takes a `glance_store.location.Location` object that indicates
where to find the image file, and returns a tuple of generator
(for reading the image file) and image_size
:param location `glance_store.location.Location` object, supplied
from glance_store.location.get_location_from_uri()
"""
yield ('gce://generic', self.get_size(location, context))
def get_size(self, location, context=None):
"""
Takes a `glance_store.location.Location` object that indicates
where to find the image file, and returns the size
:param location `glance_store.location.Location` object, supplied
from glance_store.location.get_location_from_uri()
:retval int: size of image file in bytes
"""
img_data = gceutils.get_image(self.gce_svc,
location.store_location.gce_project,
location.store_location.gce_id)
img_size = int(img_data['diskSizeGb']) * units.Gi
return img_size
@capabilities.check
def add(self, image_id, image_file, image_size, context=None,
verifier=None):
"""
Stores an image file with supplied identifier to the backend
storage system and returns a tuple containing information
about the stored image.
:param image_id: The opaque image identifier
:param image_file: The image data to write, as a file-like object
:param image_size: The size of the image data to write, in bytes
:retval: tuple of URL in backing store, bytes written, checksum
and a dictionary with storage system specific information
:raises: `glance_store.exceptions.Duplicate` if the image already
existed
"""
# Adding images is not suppported yet
raise NotImplementedError
@capabilities.check
def delete(self, location, context=None):
"""Takes a `glance_store.location.Location` object that indicates
where to find the image file to delete
:param location: `glance_store.location.Location` object, supplied
from glance_store.location.get_location_from_uri()
:raises NotFound if image does not exist
"""
# This method works for GCE public images as we just need to delete
# entry from glance catalog.
# For Private images we will need extra handling here.
LOG.info("Delete image %s" % location.get_store_uri())

View File

@ -1 +0,0 @@
boto3

View File

@ -0,0 +1,84 @@
# 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>
'''
import os
import sys
import gceutils
from novaclient import client as nova_client
from keystoneauth1 import loading, session
def get_env_param(env_name):
if env_name in os.environ:
return os.environ[env_name]
raise Exception("%s environment variable not set." % env_name)
def get_keystone_session(
auth_url=get_env_param('OS_AUTH_URL'),
project_name=os.environ.get('OS_PROJECT_NAME'),
tenant_name=os.environ.get('OS_TENANT_NAME'),
project_domain_name=os.environ.get('OS_PROJECT_DOMAIN_NAME',
'default'), # noqa
username=get_env_param('OS_USERNAME'),
user_domain_name=os.environ.get('OS_USER_DOMAIN_NAME', 'default'),
password=get_env_param('OS_PASSWORD')):
if not project_name:
if not tenant_name:
raise Exception("Either OS_PROJECT_NAME or OS_TENANT_NAME is required.")
project_name = tenant_name
loader = loading.get_plugin_loader('password')
auth = loader.load_from_options(
auth_url=auth_url, project_name=project_name,
project_domain_name=project_domain_name, username=username,
user_domain_name=user_domain_name, password=password)
sess = session.Session(auth=auth)
return sess
class GceFlavors(object):
def __init__(self, service_key_path, project, zone):
self.gce_svc = gceutils.get_gce_service(service_key_path)
self.project = project
self.zone = zone
auth_url = get_env_param('OS_AUTH_URL')
if auth_url.find('v2.0') > 0:
auth_url = auth_url.replace('v2.0', 'v3')
self.auth_url = auth_url
self.sess = get_keystone_session(auth_url=self.auth_url)
self.nova_client = nova_client.Client('2', session=self.sess)
def register_gce_flavors(self):
flavors = gceutils.get_machines_info(self.gce_svc, self.project,
self.zone)
for flavor_name, flavor_info in flavors.iteritems():
self.nova_client.flavors.create(
flavor_name, flavor_info['memory_mb'], flavor_info['vcpus'], 0)
print("Registered flavor %s" % flavor_name)
if __name__ == '__main__':
if len(sys.argv) != 4:
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

@ -193,8 +193,11 @@ class GCEDriver(driver.ComputeDriver):
instance_name = instance.name instance_name = instance.name
LOG.info("Creating instance %s as %s on GCE." % (instance.display_name, LOG.info("Creating instance %s as %s on GCE." % (instance.display_name,
instance.name)) instance.name))
operation = gceutils.create_instance(compute, project, zone, image_link = instance.system_metadata['image_gce_link']
instance_name) flavor_name = instance.flavor.name
flavor_link = "zones/%s/machineTypes/%s" % (self.gce_zone, flavor_name)
operation = gceutils.create_instance(
compute, project, zone, instance_name, image_link, flavor_link)
gceutils.wait_for_operation(compute, project, zone, operation) gceutils.wait_for_operation(compute, project, zone, operation)
gce_instance = gceutils.get_instance(compute, project, zone, gce_instance = gceutils.get_instance(compute, project, zone,
instance_name) instance_name)
@ -446,7 +449,7 @@ class GCEDriver(driver.ComputeDriver):
power_state = GCE_STATE_MAP[gce_instance['status']] power_state = GCE_STATE_MAP[gce_instance['status']]
# TODO: Get correct flavor info # TODO: Get correct flavor info
gce_flavor = self.gce_flavor_info['n1-standard-1'] gce_flavor = self.gce_flavor_info[instance.flavor.name]
memory_mb = gce_flavor['memory_mb'] memory_mb = gce_flavor['memory_mb']
vcpus = gce_flavor['vcpus'] vcpus = gce_flavor['vcpus']

View File

@ -29,6 +29,8 @@ def list_instances(compute, project, zone):
:param zone: string, GCE Name of zone :param zone: string, GCE Name of zone
""" """
result = compute.instances().list(project=project, zone=zone).execute() result = compute.instances().list(project=project, zone=zone).execute()
if 'items' not in result:
return []
return result['items'] return result['items']
@ -121,16 +123,20 @@ def set_instance_metadata(compute, project, zone, instance, items,
body=metadata).execute() body=metadata).execute()
def create_instance(compute, project, zone, name): def create_instance(compute, project, zone, name, image_link, machine_link):
"""Create GCE instance """Create GCE instance
:param compute: GCE compute resource object using googleapiclient.discovery :param compute: GCE compute resource object using googleapiclient.discovery
:param project: string, GCE Project Id :param project: string, GCE Project Id
:param zone: string, GCE Name of zone :param zone: string, GCE Name of zone
:param name: string, Name of instance to be launched :param name: string, Name of instance to be launched
:param image_link: url, GCE Image link for instance launch
:param machine_link: url, GCE Machine link for instance launch
""" """
source_disk_image = "projects/%s/global/images/%s" % ( # source_disk_image = "projects/%s/global/images/%s" % (
"debian-cloud", "debian-8-jessie-v20170327") # "debian-cloud", "debian-8-jessie-v20170327")
machine_type = "zones/%s/machineTypes/n1-standard-1" % zone # machine_link = "zones/%s/machineTypes/n1-standard-1" % zone
LOG.info("Launching instance %s with image %s and machine %s" %
(name, image_link, machine_link))
config = { config = {
'kind': 'kind':
@ -138,14 +144,14 @@ def create_instance(compute, project, zone, name):
'name': 'name':
name, name,
'machineType': 'machineType':
machine_type, machine_link,
# Specify the boot disk and the image to use as a source. # Specify the boot disk and the image to use as a source.
'disks': [{ 'disks': [{
'boot': True, 'boot': True,
'autoDelete': True, 'autoDelete': True,
'initializeParams': { 'initializeParams': {
'sourceImage': source_disk_image, 'sourceImage': image_link,
} }
}], }],
@ -275,3 +281,25 @@ def get_machines_info(compute, project, zone):
for machine_type in response['items'] for machine_type in response['items']
} }
return GCE_MAP return GCE_MAP
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
"""
response = compute.images().list(project=project,
filter="status eq READY").execute()
if 'items' not in response:
return []
imgs = filter(lambda img: 'deprecated' not in img, response['items'])
return imgs
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
"""
result = compute.images().get(project=project, image=name).execute()
return result