Added simple instances tests (CRUD)
Change-Id: Id4c9f7c4b18d6d2e3bd74c728d555f9513f08420
This commit is contained in:
parent
7f983ef92c
commit
3ecaef77b7
@ -59,6 +59,11 @@ now, 8-byte hashes are generated and returned for any ID to report.
|
||||
* GCE allows per-user SSH key specification, but Nova supports only one key.
|
||||
Solution: Nova GCE API just uses first key.
|
||||
|
||||
* Default Openstack flavors are available as machine types. GCE doesn't allow symbol '.' in machine type names,
|
||||
that's why GCE API plugin converts symbols '.' into '-' in 'get' requests (e.g. request of machine types converts
|
||||
the name 'm1.tiny' into m1-tiny) and vise versa in 'put/post/delete' requests (e.g. instance creation converts
|
||||
the name 'n1-standard-1' to 'n1.standard.1').
|
||||
|
||||
Authentication specifics
|
||||
========================
|
||||
|
||||
|
@ -171,11 +171,12 @@ function configure_gceapi {
|
||||
#-------------------------
|
||||
|
||||
iniset $GCEAPI_CONF_FILE DEFAULT region $REGION_NAME
|
||||
iniset $GCEAPI_CONF_FILE DEFAULT keystone_url "$OS_AUTH_URL"
|
||||
|
||||
iniset $GCEAPI_CONF_FILE DEFAULT admin_tenant_name $SERVICE_TENANT_NAME
|
||||
iniset $GCEAPI_CONF_FILE DEFAULT admin_user $GCEAPI_ADMIN_USER
|
||||
iniset $GCEAPI_CONF_FILE DEFAULT admin_password $SERVICE_PASSWORD
|
||||
iniset $GCEAPI_CONF_FILE DEFAULT identity_uri "http://${KEYSTONE_AUTH_HOST}:35357/v2.0"
|
||||
iniset $GCEAPI_CONF_FILE keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
|
||||
iniset $GCEAPI_CONF_FILE keystone_authtoken admin_user $GCEAPI_ADMIN_USER
|
||||
iniset $GCEAPI_CONF_FILE keystone_authtoken admin_password $SERVICE_PASSWORD
|
||||
iniset $GCEAPI_CONF_FILE keystone_authtoken identity_uri "$OS_AUTH_URL"
|
||||
|
||||
configure_gceapi_rpc_backend
|
||||
|
||||
|
74
doc/source/conf.py
Normal file
74
doc/source/conf.py
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import os
|
||||
import fileinput
|
||||
import fnmatch
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", ".."))
|
||||
|
||||
sys.path.insert(0, ROOT)
|
||||
sys.path.insert(0, BASE_DIR)
|
||||
|
||||
# This is required for ReadTheDocs.org, but isn't a bad idea anyway.
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'openstack_dashboard.settings'
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo',
|
||||
'sphinx.ext.viewcode']
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'gce-api'
|
||||
copyright = '2015, OpenStack Foundation'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
#html_theme_path = ["."]
|
||||
#html_theme = '_theme'
|
||||
#html_static_path = ['static']
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
|
||||
html_last_updated_fmt = os.popen(git_cmd).read()
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
'%s Documentation' % project,
|
||||
'OpenStack Foundation', 'manual'),
|
||||
]
|
1
doc/source/hacking.rst
Normal file
1
doc/source/hacking.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../HACKING.rst
|
22
doc/source/index.rst
Normal file
22
doc/source/index.rst
Normal file
@ -0,0 +1,22 @@
|
||||
OpenStack GCE API
|
||||
=====================
|
||||
|
||||
Support of GCE API for OpenStack.
|
||||
This project provides a standalone GCE API service that enables
|
||||
managing of OpenStack Nova service in a manner of Google Cloud Compute
|
||||
Engine.
|
||||
It uses port 8787 by default that could be changed via config file..
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
hacking
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
@ -40,7 +40,7 @@ gce_opts = [
|
||||
cfg.StrOpt('network_api',
|
||||
default="neutron",
|
||||
help='Name of network API. neutron(quantum) or nova'),
|
||||
cfg.StrOpt('keystone_gce_url',
|
||||
cfg.StrOpt('keystone_url',
|
||||
default='http://127.0.0.1:5000/v2.0',
|
||||
help='Keystone URL'),
|
||||
cfg.StrOpt('public_network',
|
||||
|
@ -11,7 +11,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.v2_0 import client as kc
|
||||
from keystoneclient import client as kc
|
||||
from novaclient import client as novaclient
|
||||
from novaclient import exceptions as nova_exception
|
||||
from oslo_config import cfg
|
||||
@ -52,7 +52,7 @@ _nova_api_version = None
|
||||
|
||||
def nova(context, service_type='compute'):
|
||||
args = {
|
||||
'auth_url': CONF.keystone_gce_url,
|
||||
'auth_url': CONF.keystone_url,
|
||||
'auth_token': context.auth_token,
|
||||
'bypass_url': url_for(context, service_type),
|
||||
}
|
||||
@ -67,7 +67,7 @@ def neutron(context):
|
||||
return None
|
||||
|
||||
args = {
|
||||
'auth_url': CONF.keystone_gce_url,
|
||||
'auth_url': CONF.keystone_url,
|
||||
'service_type': 'network',
|
||||
'token': context.auth_token,
|
||||
'endpoint_url': url_for(context, 'network'),
|
||||
@ -81,7 +81,7 @@ def glance(context):
|
||||
return None
|
||||
|
||||
args = {
|
||||
'auth_url': CONF.keystone_gce_url,
|
||||
'auth_url': CONF.keystone_url,
|
||||
'service_type': 'image',
|
||||
'token': context.auth_token,
|
||||
}
|
||||
@ -96,7 +96,7 @@ def cinder(context):
|
||||
|
||||
args = {
|
||||
'service_type': 'volume',
|
||||
'auth_url': CONF.keystone_gce_url,
|
||||
'auth_url': CONF.keystone_url,
|
||||
'username': None,
|
||||
'api_key': None,
|
||||
}
|
||||
@ -110,18 +110,24 @@ def cinder(context):
|
||||
|
||||
|
||||
def keystone(context):
|
||||
return kc.Client(
|
||||
c = kc.Client(
|
||||
token=context.auth_token,
|
||||
project_id=context.project_id,
|
||||
tenant_id=context.project_id,
|
||||
auth_url=CONF.keystone_gce_url)
|
||||
auth_url=CONF.keystone_url)
|
||||
if c.auth_ref is None:
|
||||
# Ver2 doesn't create session and performs
|
||||
# authentication automatically, but Ver3 does create session
|
||||
# if it's not provided and doesn't perform authentication.
|
||||
# TODO(use sessions)
|
||||
c.authenticate()
|
||||
return c
|
||||
|
||||
|
||||
def url_for(context, service_type):
|
||||
service_catalog = context.service_catalog
|
||||
if not service_catalog:
|
||||
catalog = keystone(context).service_catalog.catalog
|
||||
service_catalog = catalog['serviceCatalog']
|
||||
service_catalog = keystone(context).service_catalog.get_data()
|
||||
context.service_catalog = service_catalog
|
||||
return get_url_from_catalog(service_catalog, service_type)
|
||||
|
||||
|
@ -17,7 +17,7 @@ import os
|
||||
import threading
|
||||
import webob
|
||||
|
||||
from keystoneclient.v2_0 import client as keystone_client
|
||||
from keystoneclient import client as keystone_client
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
@ -25,7 +25,7 @@ from gceapi.api import clients
|
||||
from gceapi import wsgi_ext as openstack_wsgi
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
FLAGS = cfg.CONF
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class Controller(object):
|
||||
@ -40,12 +40,20 @@ class Controller(object):
|
||||
if key in self._files:
|
||||
return self._files[key]
|
||||
|
||||
tenant = FLAGS.keystone_authtoken["admin_tenant_name"]
|
||||
user = FLAGS.keystone_authtoken["admin_user"]
|
||||
password = FLAGS.keystone_authtoken["admin_password"]
|
||||
keystone = keystone_client.Client(username=user, password=password,
|
||||
tenant_name=tenant, auth_url=FLAGS.keystone_gce_url)
|
||||
catalog = keystone.service_catalog.catalog["serviceCatalog"]
|
||||
auth_data = {
|
||||
'project_name': CONF.keystone_authtoken['admin_tenant_name'],
|
||||
'username': CONF.keystone_authtoken['admin_user'],
|
||||
'password': CONF.keystone_authtoken['admin_password'],
|
||||
'auth_url': CONF.keystone_url,
|
||||
}
|
||||
keystone = keystone_client.Client(**auth_data)
|
||||
if keystone.auth_ref is None:
|
||||
# Ver2 doesn't create session and performs
|
||||
# authentication automatically, but Ver3 does create session
|
||||
# if it's not provided and doesn't perform authentication.
|
||||
# TODO(use sessions)
|
||||
keystone.authenticate()
|
||||
catalog = keystone.service_catalog.get_data()
|
||||
public_url = clients.get_url_from_catalog(catalog, "gceapi")
|
||||
if not public_url:
|
||||
public_url = req.host_url
|
||||
@ -66,7 +74,7 @@ class Controller(object):
|
||||
def _load_file(self, version):
|
||||
file = version + ".json"
|
||||
|
||||
protocol_dir = FLAGS.get("protocol_dir")
|
||||
protocol_dir = CONF.get("protocol_dir")
|
||||
if protocol_dir:
|
||||
file_name = os.path.join(protocol_dir, file)
|
||||
try:
|
||||
|
@ -393,7 +393,6 @@ class API(base_api.API):
|
||||
instance = context.operation_data.get("instance")
|
||||
progress = {"progress": int(100.0 * disk_device / full_count)}
|
||||
|
||||
disk_device = context.operation_data["disk_device"]
|
||||
disk = context.operation_data.get("disk")
|
||||
if disk:
|
||||
volume_id = disk["id"]
|
||||
@ -421,8 +420,14 @@ class API(base_api.API):
|
||||
body, scope=scope)
|
||||
disk["id"] = volume["id"]
|
||||
context.operation_data["disk"] = disk
|
||||
device_name = "vd" + string.ascii_lowercase[disk_device]
|
||||
# deviceName is optional parameter
|
||||
# use passed value if given, othewise generate new dev name
|
||||
device_name = disk.get("deviceName")
|
||||
if device_name is None:
|
||||
device_name = "vd" + string.ascii_lowercase[disk_device]
|
||||
disk["deviceName"] = device_name
|
||||
bdm[device_name] = disk["id"]
|
||||
|
||||
if "initializeParams" in disk:
|
||||
return progress
|
||||
disk_device += 1
|
||||
|
@ -17,17 +17,16 @@ import json
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from keystoneclient import client as keystone_client
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.v2_0 import client as keystone_client
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
import webob
|
||||
|
||||
from gceapi.i18n import _
|
||||
from gceapi import wsgi_ext as openstack_wsgi
|
||||
|
||||
FLAGS = cfg.CONF
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -150,11 +149,16 @@ class Controller(object):
|
||||
keystone = keystone_client.Client(
|
||||
username=username,
|
||||
password=password,
|
||||
auth_url=FLAGS.keystone_gce_url)
|
||||
token = keystone.auth_ref["token"]
|
||||
client.auth_token = token["id"]
|
||||
s = timeutils.parse_isotime(token["issued_at"])
|
||||
e = timeutils.parse_isotime(token["expires"])
|
||||
auth_url=CONF.keystone_url)
|
||||
if keystone.auth_ref is None:
|
||||
# Ver2 doesn't create session and performs
|
||||
# authentication automatically, but Ver3 does create session
|
||||
# if it's not provided and doesn't perform authentication.
|
||||
# TODO(use sessions)
|
||||
keystone.authenticate()
|
||||
client.auth_token = keystone.auth_token
|
||||
s = keystone.auth_ref.issued
|
||||
e = keystone.auth_ref.expires
|
||||
client.expires_in = (e - s).seconds
|
||||
except Exception as ex:
|
||||
return webob.exc.HTTPUnauthorized(ex)
|
||||
@ -201,7 +205,7 @@ class AuthProtocol(object):
|
||||
"""Filter for translating oauth token to keystone token."""
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.keystone_url = FLAGS.keystone_gce_url
|
||||
self.auth_url = CONF.keystone_url
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
auth_token = env.get("HTTP_AUTHORIZATION")
|
||||
@ -214,8 +218,15 @@ class AuthProtocol(object):
|
||||
token=auth_token.split()[1],
|
||||
tenant_name=project,
|
||||
force_new_token=True,
|
||||
auth_url=self.keystone_url)
|
||||
env["HTTP_X_AUTH_TOKEN"] = keystone.auth_ref["token"]["id"]
|
||||
auth_url=self.auth_url)
|
||||
if keystone.auth_ref is None:
|
||||
# Ver2 doesn't create session and performs
|
||||
# authentication automatically, but Ver3 does create session
|
||||
# if it's not provided and doesn't perform authentication.
|
||||
# TODO(use sessions)
|
||||
keystone.authenticate()
|
||||
scoped_token = keystone.auth_token
|
||||
env["HTTP_X_AUTH_TOKEN"] = scoped_token
|
||||
return self.app(env, start_response)
|
||||
except exceptions.Unauthorized:
|
||||
if project in INTERNAL_GCUTIL_PROJECTS:
|
||||
|
@ -20,6 +20,7 @@ from oslo_db.sqlalchemy import models
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import Column, Index, PrimaryKeyConstraint, String, Text
|
||||
|
||||
|
||||
BASE = declarative_base()
|
||||
|
||||
|
||||
@ -34,3 +35,10 @@ class Item(BASE, models.ModelBase):
|
||||
kind = Column(String(length=50))
|
||||
name = Column(String(length=63))
|
||||
data = Column(Text())
|
||||
|
||||
def save(self, session=None):
|
||||
from gceapi.db.sqlalchemy import api
|
||||
if session is None:
|
||||
session = api.get_session()
|
||||
|
||||
super(Item, self).save(session=session)
|
||||
|
@ -37,22 +37,27 @@ if [[ ! -f $TEST_CONFIG_DIR/$TEST_CONFIG ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# prepare flavors
|
||||
nova flavor-create --is-public True m1.gceapi 16 512 0 1
|
||||
|
||||
# create separate user/project
|
||||
project_name="project-$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 8)"
|
||||
eval $(openstack project create -f shell -c id $project_name)
|
||||
project_id=$id
|
||||
[[ -n "$project_id" ]] || { echo "Can't create project"; exit 1; }
|
||||
user_name="user-$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 8)"
|
||||
password='qwe123QWE'
|
||||
password='password'
|
||||
eval $(openstack user create "$user_name" --project "$project_id" --password "$password" --email "$user_name@example.com" -f shell -c id)
|
||||
user_id=$id
|
||||
[[ -n "$user_id" ]] || { echo "Can't create user"; exit 1; }
|
||||
# add 'Member' role for swift access
|
||||
role_id=$(openstack role show Member -c id -f value)
|
||||
openstack role add --project $project_id --user $user_id $role_id
|
||||
|
||||
# prepare flavors
|
||||
flavor_name="n1.standard.1"
|
||||
if [[ -z "$(nova flavor-list | grep $flavor_name)" ]]; then
|
||||
nova flavor-create --is-public True $flavor_name 16 512 0 1
|
||||
[[ "$?" -eq 0 ]] || { echo "Failed to prepare flavor"; exit 1; }
|
||||
fi
|
||||
|
||||
# create network
|
||||
if [[ -n $(openstack service list | grep neutron) ]]; then
|
||||
net_id=$(neutron net-create --tenant-id $project_id "private" | grep ' id ' | awk '{print $4}')
|
||||
@ -68,6 +73,19 @@ if [[ ! -f $TEST_CONFIG_DIR/$TEST_CONFIG ]]; then
|
||||
neutron router-gateway-set $router_id $public_net_id
|
||||
[[ "$?" -eq 0 ]] || { echo "router-gateway-set failed"; exit 1; }
|
||||
fi
|
||||
|
||||
#create image in raw format
|
||||
os_image_name="cirros-0.3.4-raw-image"
|
||||
if [[ -z "$(openstack image list | grep $os_image_name)" ]]; then
|
||||
image_name="cirros-0.3.4-x86_64-disk.img"
|
||||
cirros_image_url="http://download.cirros-cloud.net/0.3.4/$image_name"
|
||||
sudo rm -f /tmp/$image_name
|
||||
wget -nv -P /tmp $cirros_image_url
|
||||
[[ "$?" -eq 0 ]] || { echo "Failed to download image"; exit 1; }
|
||||
openstack image create --disk-format raw --container-format bare --public --file "/tmp/$image_name" $os_image_name
|
||||
[[ "$?" -eq 0 ]] || { echo "Failed to prepare image"; exit 1; }
|
||||
fi
|
||||
|
||||
export OS_PROJECT_NAME=$project_name
|
||||
export OS_TENANT_NAME=$project_name
|
||||
export OS_USERNAME=$user_name
|
||||
@ -76,7 +94,8 @@ if [[ ! -f $TEST_CONFIG_DIR/$TEST_CONFIG ]]; then
|
||||
sudo bash -c "cat > $TEST_CONFIG_DIR/$TEST_CONFIG <<EOF
|
||||
[gce]
|
||||
# Generic options
|
||||
build_interval=${TIMEOUT:-180}
|
||||
build_timeout=${TIMEOUT:-180}
|
||||
build_interval=1
|
||||
|
||||
# GCE API schema
|
||||
schema=${GCE_SCHEMA:-'etc/gceapi/protocols/v1.json'}
|
||||
@ -99,12 +118,16 @@ discovery_url=${GCE_DISCOVERY_URL:-'/discovery/v1/apis/{api}/{apiVersion}/rest'}
|
||||
project_id=${OS_PROJECT_NAME}
|
||||
zone=${ZONE:-'nova'}
|
||||
region=${REGION:-'RegionOne'}
|
||||
# convert flavor name: becase GCE dowsn't allows '.' and converts '-' into '.'
|
||||
machine_type=${flavor_name//\./-}
|
||||
image=${os_image_name}
|
||||
EOF"
|
||||
fi
|
||||
|
||||
sudo pip install -r test-requirements.txt
|
||||
sudo pip install google-api-python-client
|
||||
sudo OS_STDOUT_CAPTURE=-1 OS_STDERR_CAPTURE=-1 OS_TEST_TIMEOUT=500 OS_TEST_LOCK_PATH=${TMPDIR:-'/tmp'} \
|
||||
python -m subunit.run discover -t ./ ./gceapi/tests/functional | subunit-2to1 | tools/colorizer.py
|
||||
python -m subunit.run discover -t ./ ./gceapi/tests/functional/api | subunit-2to1 | tools/colorizer.py
|
||||
RETVAL=$?
|
||||
|
||||
# Here can be some commands for log archiving, etc...
|
||||
@ -120,8 +143,9 @@ export OS_PROJECT_NAME=$OLD_OS_PROJECT_NAME
|
||||
export OS_TENANT_NAME=$OLD_OS_PROJECT_NAME
|
||||
export OS_USERNAME=$OLD_OS_USERNAME
|
||||
export OS_PASSWORD=$OLD_OS_PASSWORD
|
||||
openstack server list --all-projects
|
||||
openstack flavor list
|
||||
openstack image list
|
||||
openstack server list --all-projects
|
||||
openstack volume list --all-projects
|
||||
cinder snapshot-list --all-tenants
|
||||
|
||||
|
232
gceapi/tests/functional/api/test_instances.py
Normal file
232
gceapi/tests/functional/api/test_instances.py
Normal file
@ -0,0 +1,232 @@
|
||||
# Copyright 2015 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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 string import Template
|
||||
|
||||
from json import dumps
|
||||
from json import loads
|
||||
|
||||
from gceapi.tests.functional import test_base
|
||||
|
||||
|
||||
BASE_COMPUTE_URL = '{address}/compute/v1'
|
||||
CREATE_INSTANCE_TEMPLATE = {
|
||||
"name": "${instance}",
|
||||
"description": "Testing instance",
|
||||
"machineType": "zones/${zone}/machineTypes/${machine_type}",
|
||||
"disks": [
|
||||
{
|
||||
"boot": True,
|
||||
"autoDelete": True,
|
||||
"initializeParams": {
|
||||
"sourceImage": "projects/${image}",
|
||||
}
|
||||
}
|
||||
],
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"network": "global/networks/${network}",
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"items": [
|
||||
{
|
||||
"key": "test_metadata_key",
|
||||
"value": "test_metadata_value"
|
||||
},
|
||||
{
|
||||
"key": "startup-script",
|
||||
"value": "echo Test startup script"
|
||||
}
|
||||
]
|
||||
},
|
||||
"serviceAccounts": [
|
||||
{
|
||||
"email": "default",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/cloud.useraccounts.readonly",
|
||||
"https://www.googleapis.com/auth/devstorage.read_only",
|
||||
"https://www.googleapis.com/auth/logging.write",
|
||||
"https://www.googleapis.com/auth/monitoring.write"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
CREATE_NETWORK_TEMPLATE = {
|
||||
"name": "${name}",
|
||||
"IPv4Range": "10.240.0.0/16",
|
||||
"description": "testing network ${name}",
|
||||
"gatewayIPv4": "10.240.0.1"
|
||||
}
|
||||
|
||||
|
||||
def _insert_json_parameters(obj, **kwargs):
|
||||
s = dumps(obj)
|
||||
t = Template(s)
|
||||
s = t.substitute(**kwargs)
|
||||
return loads(s)
|
||||
|
||||
|
||||
def _prepare_instace_insert_parameters(**kwargs):
|
||||
return _insert_json_parameters(CREATE_INSTANCE_TEMPLATE, **kwargs)
|
||||
|
||||
|
||||
def _prepare_network_create_parameters(**kwargs):
|
||||
return _insert_json_parameters(CREATE_NETWORK_TEMPLATE, **kwargs)
|
||||
|
||||
|
||||
class TestIntancesBase(test_base.GCETestCase):
|
||||
@property
|
||||
def instances(self):
|
||||
res = self.api.compute.instances()
|
||||
self.assertIsNotNone(
|
||||
res,
|
||||
'Null instances object, api is not built properly')
|
||||
return res
|
||||
|
||||
@property
|
||||
def networks(self):
|
||||
res = self.api.compute.networks()
|
||||
self.assertIsNotNone(
|
||||
res,
|
||||
'Null networks object, api is not built properly')
|
||||
return res
|
||||
|
||||
def setUp(self):
|
||||
super(TestIntancesBase, self).setUp()
|
||||
self._instance_name = self.getUniqueString('testinst')
|
||||
self._network_name = self.getUniqueString('testnet')
|
||||
|
||||
def _create_network(self):
|
||||
cfg = self.cfg
|
||||
project_id = cfg.project_id
|
||||
network = self._network_name
|
||||
kw = {
|
||||
'name': network,
|
||||
}
|
||||
config = _prepare_network_create_parameters(**kw)
|
||||
self.trace('Crete network with options {}'.format(config))
|
||||
request = self.networks.insert(
|
||||
project=project_id,
|
||||
body=config)
|
||||
result = self._execute_async_request(request, project_id)
|
||||
self.api.validate_schema(value=result, schema_name='Operation')
|
||||
return result
|
||||
|
||||
def _delete_network(self):
|
||||
cfg = self.cfg
|
||||
project_id = cfg.project_id
|
||||
network = self._network_name
|
||||
self.trace(
|
||||
'Delete network: project_id={} network={}'.
|
||||
format(project_id, network))
|
||||
request = self.networks.delete(
|
||||
project=project_id,
|
||||
network=network)
|
||||
result = self._execute_async_request(request, project_id)
|
||||
self.api.validate_schema(value=result, schema_name='Operation')
|
||||
return result
|
||||
|
||||
def _create_instance(self):
|
||||
cfg = self.cfg
|
||||
project_id = cfg.project_id
|
||||
zone = cfg.zone
|
||||
kw = {
|
||||
'zone': zone,
|
||||
'instance': self._instance_name,
|
||||
'machine_type': cfg.machine_type,
|
||||
'image': cfg.image,
|
||||
'network': self._network_name,
|
||||
}
|
||||
config = _prepare_instace_insert_parameters(**kw)
|
||||
self.trace('Crete instance with options {}'.format(config))
|
||||
request = self.instances.insert(
|
||||
project=project_id,
|
||||
zone=zone,
|
||||
body=config)
|
||||
result = self._execute_async_request(request, project_id, zone=zone)
|
||||
self.api.validate_schema(value=result, schema_name='Operation')
|
||||
return result
|
||||
|
||||
def _delete_instance(self):
|
||||
cfg = self.cfg
|
||||
project_id = cfg.project_id
|
||||
zone = cfg.zone
|
||||
instance = self._instance_name
|
||||
self.trace(
|
||||
'Delete instance: project_id={} zone={} instance {}'.
|
||||
format(project_id, zone, instance))
|
||||
request = self.instances.delete(
|
||||
project=project_id,
|
||||
zone=zone,
|
||||
instance=instance)
|
||||
result = self._execute_async_request(request, project_id, zone=zone)
|
||||
self.api.validate_schema(value=result, schema_name='Operation')
|
||||
return result
|
||||
|
||||
def _list(self):
|
||||
project_id = self.cfg.project_id
|
||||
zone = self.cfg.zone
|
||||
self.trace(
|
||||
'List instances: project_id={} zone={}'.format(project_id, zone))
|
||||
request = self.instances.list(project=project_id, zone=zone)
|
||||
self._trace_request(request)
|
||||
result = request.execute()
|
||||
self.trace('Instances: {}'.format(result))
|
||||
self.api.validate_schema(value=result, schema_name='InstanceList')
|
||||
self.assertFind(self._instance_name, result)
|
||||
return result
|
||||
|
||||
def _get(self):
|
||||
project_id = self.cfg.project_id
|
||||
zone = self.cfg.zone
|
||||
instance = self._instance_name
|
||||
self.trace(
|
||||
'Get instance: project_id={} zone={} instance={}'.
|
||||
format(project_id, zone, instance))
|
||||
request = self.instances.get(
|
||||
project=project_id,
|
||||
zone=zone,
|
||||
instance=instance)
|
||||
result = request.execute()
|
||||
self.trace('Instance: {}'.format(result))
|
||||
self.api.validate_schema(value=result, schema_name='Instance')
|
||||
return result
|
||||
|
||||
|
||||
class TestIntancesCRUD(TestIntancesBase):
|
||||
def _create(self):
|
||||
self._create_network()
|
||||
self._create_instance()
|
||||
|
||||
def _read(self):
|
||||
self._get()
|
||||
self._list()
|
||||
|
||||
def _update(self):
|
||||
#TODO(to impl simple update cases)
|
||||
pass
|
||||
|
||||
def _delete(self):
|
||||
self._delete_instance()
|
||||
self._delete_network()
|
||||
|
||||
def test_crud(self):
|
||||
self._create()
|
||||
self._read()
|
||||
self._update()
|
||||
self._delete()
|
@ -14,6 +14,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
from gceapi.tests.functional import test_base
|
||||
|
||||
@ -33,6 +34,8 @@ class TestRegions(test_base.GCETestCase):
|
||||
'Null regions object, api is not built properly')
|
||||
return res
|
||||
|
||||
# TODO(alexey-mr): Google allows [a-z](?:[-a-z0-9]{0,61}[a-z0-9])?
|
||||
@unittest.skip("Skip test for now: google dosnt't allow name RegionOne")
|
||||
def test_describe(self):
|
||||
project_id = self.cfg.project_id
|
||||
region = self.cfg.region
|
||||
|
@ -193,6 +193,8 @@ class GCESmokeTestCase(testtools.TestCase):
|
||||
if GCESmokeTestCase.failed:
|
||||
raise unittest.SkipTest("Skipped by previous exception")
|
||||
super(GCESmokeTestCase, self).setUp()
|
||||
self.skipTest('Not to run in gating. It is just an old example and '
|
||||
'will be remove removed in future')
|
||||
|
||||
def wait_for_operation(self, body, operation, status):
|
||||
self.assertEqual("compute#operation", body["kind"])
|
||||
|
@ -40,7 +40,7 @@ OPTIONS = [
|
||||
default='demo',
|
||||
help='User name'),
|
||||
cfg.StrOpt('password',
|
||||
default='qwe123QWE',
|
||||
default='password',
|
||||
help='User password'),
|
||||
cfg.StrOpt('auth_url',
|
||||
default='http://localhost:5000/v2.0/',
|
||||
@ -81,6 +81,13 @@ OPTIONS = [
|
||||
cfg.StrOpt('region',
|
||||
default='RegionOne',
|
||||
help='GCE Region for testing'),
|
||||
|
||||
cfg.StrOpt('machine_type',
|
||||
default='n1-standard-1',
|
||||
help='Machine type - a type of instance ot be created'),
|
||||
cfg.StrOpt('image',
|
||||
default='debian-cloud/global/images/debian-7-wheezy-v20150929',
|
||||
help='Image to create instances'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -32,6 +32,13 @@ class CredentialsProvider(object):
|
||||
return GoogleCredentials.get_application_default()
|
||||
|
||||
def _get_token_crenetials(self):
|
||||
client = self._create_keystone_client()
|
||||
token = client.auth_token
|
||||
self._trace('Created token {}'.format(token))
|
||||
return AccessTokenCredentials(access_token=token,
|
||||
user_agent='GCE test')
|
||||
|
||||
def _create_keystone_client(self):
|
||||
cfg = self._supp.cfg
|
||||
auth_data = {
|
||||
'username': cfg.username,
|
||||
@ -39,14 +46,15 @@ class CredentialsProvider(object):
|
||||
'tenant_name': cfg.project_id,
|
||||
'auth_url': cfg.auth_url
|
||||
}
|
||||
self._trace('Auth data {}'.format(auth_data))
|
||||
self._trace('Create keystone client, auth_data={}'.format(auth_data))
|
||||
client = KeystoneClient(**auth_data)
|
||||
if not client.authenticate():
|
||||
raise Exception('Failed to authenticate user')
|
||||
token = client.auth_token
|
||||
self._trace('Created token {}'.format(token))
|
||||
return AccessTokenCredentials(access_token=token,
|
||||
user_agent='GCE test')
|
||||
return client
|
||||
|
||||
@property
|
||||
def keystone_client(self):
|
||||
return self._create_keystone_client()
|
||||
|
||||
@property
|
||||
def credentials(self):
|
||||
|
@ -14,6 +14,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.schema import Schemas
|
||||
@ -36,8 +37,7 @@ class TestSupp(object):
|
||||
return self._cfg
|
||||
|
||||
def trace(self, *args, **kwargs):
|
||||
print(args, kwargs)
|
||||
self._log.trace(*args, **kwargs)
|
||||
self._log.debug(*args, **kwargs)
|
||||
|
||||
|
||||
class LocalRefResolver(RefResolver):
|
||||
@ -108,6 +108,15 @@ class GCEApi(object):
|
||||
assert(self._compute is not None)
|
||||
return self._compute
|
||||
|
||||
@property
|
||||
def base_url(self):
|
||||
cfg = self._supp.cfg
|
||||
return '{}://{}:{}'.format(
|
||||
cfg.protocol,
|
||||
cfg.host,
|
||||
cfg.port
|
||||
)
|
||||
|
||||
def validate_schema(self, value, schema_name):
|
||||
schema = self._schema.get(schema_name)
|
||||
validate(value, schema, resolver=self._scheme_ref_resolver)
|
||||
@ -135,13 +144,59 @@ class GCETestCase(base.BaseTestCase):
|
||||
super(GCETestCase, cls).setUpClass()
|
||||
|
||||
def assertFind(self, item, items_list):
|
||||
found = False
|
||||
items = items_list['items']
|
||||
for i in items:
|
||||
if i['name'] == item:
|
||||
found = True
|
||||
break
|
||||
|
||||
self.assertTrue(
|
||||
found,
|
||||
key = 'items'
|
||||
items = []
|
||||
if key in items_list:
|
||||
items = items_list[key]
|
||||
for i in items:
|
||||
if i['name'] == item:
|
||||
return
|
||||
self.fail(
|
||||
'There is no required item {} in the list {}'.format(item, items))
|
||||
|
||||
def _trace_request(self, r):
|
||||
self.trace('Request: {}'.format(r.to_json()))
|
||||
|
||||
def _get_operations_request(self, name, project, zone):
|
||||
if zone is not None:
|
||||
return self.api.compute.zoneOperations().get(
|
||||
project=project,
|
||||
zone=zone,
|
||||
operation=name)
|
||||
return self.api.compute.globalOperations().get(
|
||||
project=project,
|
||||
operation=name)
|
||||
|
||||
def _execute_async_request(self, request, project, zone=None):
|
||||
self._trace_request(request)
|
||||
operation = request.execute()
|
||||
name = operation['name']
|
||||
self.trace('Waiting for operation {} to finish...'.format(name))
|
||||
begin = time.time()
|
||||
timeout = self._supp.cfg.build_timeout
|
||||
while time.time() - begin < timeout:
|
||||
result = self._get_operations_request(
|
||||
name, project, zone).execute()
|
||||
if result['status'] == 'DONE':
|
||||
if 'error' in result:
|
||||
self.fail('Request {} failed with error {}'. format(
|
||||
name, result['error']))
|
||||
else:
|
||||
self.trace("Request {} done successfully".format(name))
|
||||
return result
|
||||
time.sleep(1)
|
||||
|
||||
self.fail('Request {} failed with timeout {}'.format(name, timeout))
|
||||
|
||||
|
||||
def safe_call(method):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return method(self, *args, **kwargs)
|
||||
except Exception as err:
|
||||
self.trace('Exception {}'.format(err))
|
||||
import traceback
|
||||
bt = traceback.format_exc()
|
||||
self.trace('Exception back trace {}'.format(bt))
|
||||
return None
|
||||
return wrapper
|
||||
|
@ -15,14 +15,15 @@
|
||||
# under the License.
|
||||
|
||||
import itertools
|
||||
|
||||
import netaddr
|
||||
from oslo_log import log as logging
|
||||
import testtools
|
||||
|
||||
from oslo_log import log as logging
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import config
|
||||
import tempest.thirdparty.gce.base as base_gce
|
||||
|
||||
from gceapi.tests.functional import base as base_gce
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger("tempest.thirdparty.gce")
|
||||
|
@ -17,7 +17,7 @@ import uuid
|
||||
|
||||
from cinderclient import client as cinderclient
|
||||
from glanceclient import client as glanceclient
|
||||
from keystoneclient.v2_0 import client as kc
|
||||
from keystoneclient import client as kc
|
||||
from neutronclient.v2_0 import client as neutronclient
|
||||
from novaclient import client as novaclient
|
||||
from oslo_utils import timeutils
|
||||
|
@ -29,6 +29,10 @@ class FakeTenants(object):
|
||||
return FAKE_PROJECTS
|
||||
|
||||
|
||||
class FakeAccessInfo(object):
|
||||
pass
|
||||
|
||||
|
||||
class FakeKeystoneClient(object):
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
@ -36,3 +40,7 @@ class FakeKeystoneClient(object):
|
||||
@property
|
||||
def tenants(self):
|
||||
return FakeTenants()
|
||||
|
||||
@property
|
||||
def auth_ref(self):
|
||||
return FakeAccessInfo()
|
||||
|
14
install.sh
14
install.sh
@ -210,32 +210,24 @@ if [ ! -s $APIPASTE_FILE ]; then
|
||||
fi
|
||||
sudo cp -nR etc/gceapi/protocols $CONF_DIR
|
||||
|
||||
AUTH_HOST=${OS_AUTH_URL#*//}
|
||||
AUTH_HOST=${AUTH_HOST%:*}
|
||||
AUTH_CACHE_DIR=${AUTH_CACHE_DIR:-/var/cache/gceapi}
|
||||
AUTH_PORT=`keystone catalog|grep -A 9 identity|grep adminURL|awk '{print $4}'`
|
||||
AUTH_PORT=${AUTH_PORT##*:}
|
||||
AUTH_PORT=${AUTH_PORT%%/*}
|
||||
AUTH_PROTO=${OS_AUTH_URL%%:*}
|
||||
PUBLIC_URL=${OS_AUTH_URL%:*}:8787/
|
||||
|
||||
#update default config with some values
|
||||
iniset $CONF_FILE DEFAULT api_paste_config $APIPASTE_FILE
|
||||
iniset $CONF_FILE DEFAULT logging_context_format_string "%(asctime)s.%(msecs)03d %(levelname)s %(name)s [%(request_id)s %(user_name)s %(project_name)s] %(instance)s%(message)s"
|
||||
iniset $CONF_FILE DEFAULT verbose True
|
||||
iniset $CONF_FILE DEFAULT keystone_gce_url "$OS_AUTH_URL"
|
||||
iniset $CONF_FILE DEFAULT network_api "$NETWORK_API"
|
||||
iniset $CONF_FILE DEFAULT region "$REGION"
|
||||
iniset $CONF_FILE DEFAULT protocol_dir $CONF_DIR/protocols
|
||||
iniset $CONF_FILE DEFAULT protocol_dir "$CONF_DIR/protocols"
|
||||
iniset $CONF_FILE DEFAULT keystone_url "$OS_AUTH_URL"
|
||||
iniset $CONF_FILE database connection "$CONNECTION"
|
||||
|
||||
iniset $CONF_FILE keystone_authtoken signing_dir $SIGNING_DIR
|
||||
iniset $CONF_FILE keystone_authtoken auth_host $AUTH_HOST
|
||||
iniset $CONF_FILE keystone_authtoken admin_user $SERVICE_USERNAME
|
||||
iniset $CONF_FILE keystone_authtoken admin_password $SERVICE_PASSWORD
|
||||
iniset $CONF_FILE keystone_authtoken admin_tenant_name $SERVICE_TENANT
|
||||
iniset $CONF_FILE keystone_authtoken auth_protocol $AUTH_PROTO
|
||||
iniset $CONF_FILE keystone_authtoken auth_port $AUTH_PORT
|
||||
iniset $CONF_FILE keystone_authtoken identity_uri "$OS_AUTH_URL"
|
||||
|
||||
|
||||
#init cache dir
|
||||
|
Loading…
x
Reference in New Issue
Block a user