Add fake nova_v2 driver for functional test

This patch adds fake nova_v2 driver for functional test. Based on
this driver, a test case for cluster creating and deleting is also
added.

Change-Id: I5045b99b8109b2f9bcf07fea397d4c8ddb7b29fd
This commit is contained in:
yanyanhu 2015-08-24 04:36:10 -04:00
parent 1fe9113a21
commit 71045aef01
11 changed files with 396 additions and 12 deletions

View File

@ -0,0 +1,85 @@
# 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_serialization import jsonutils
def create_cluster(client, name, profile_id, desired_capacity,
min_size=0, max_size=-1, parent=None,
metadata={}, timeout=120):
rel_url = 'clusters'
status = [200]
data = {
'cluster': {
'name': name,
'profile_id': profile_id,
'desired_capacity': desired_capacity,
'min_size': min_size,
'max_size': max_size,
'parent': parent,
'metadata': metadata,
'timeout': timeout,
}
}
body = jsonutils.dumps(data)
resp = client.api_request('POST', rel_url, body=body,
resp_status=status)
cluster = resp.body['cluster']
return cluster
def get_cluster(client, cluster_id, ignore_missing=False):
rel_url = 'clusters/%(id)s' % {'id': cluster_id}
status = [200, 404] if ignore_missing else [200]
resp = client.api_request('GET', rel_url, resp_status=status)
return resp if ignore_missing else resp.body['cluster']
def list_clusters(client, **query):
rel_url = 'clusters'
status = [200]
resp = client.api_request('GET', rel_url, resp_status=status)
return resp.body['clusters']
def delete_cluster(client, cluster_id):
rel_url = 'clusters/%(id)s' % {'id': cluster_id}
status = [204]
client.api_request('DELETE', rel_url, resp_status=status)
return
def create_profile(client, name, profile_type, spec,
permission=None, metadata={}):
rel_url = 'profiles'
status = [200]
data = {
'profile': {
'name': name,
'type': profile_type,
'spec': spec,
'permission': permission,
'metadata': metadata,
}
}
body = jsonutils.dumps(data)
resp = client.api_request('POST', rel_url, body=body,
resp_status=status)
profile = resp.body['profile']
return profile
def delete_profile(client, profile_id):
rel_url = 'profiles/%(id)s' % {'id': profile_id}
status = [204]
client.api_request('DELETE', rel_url, resp_status=status)
return

View File

@ -123,7 +123,7 @@ class TestSenlinAPIClient(object):
return self.auth_token
def api_request(self, http_method, relative_url, body=None,
expected_resp_status=None, **kwargs):
resp_status=None, **kwargs):
token = self._authenticate()
endpoints = None
@ -151,15 +151,15 @@ class TestSenlinAPIClient(object):
headers['Content-Type'] = 'application/json'
kwargs['method'] = http_method
response = self.request(full_url, **kwargs)
response = self.request(full_url, body=body, **kwargs)
http_status = response.status_code
LOG.debug(_('request url %(url)s, status_code %(status)s, response '
'body %(body)s'
), {'url': relative_url, 'status': http_status,
'body': response._content})
if expected_resp_status:
if http_status not in expected_resp_status:
if resp_status:
if http_status not in resp_status:
raise SenlinApiException(message="Unexpected status code",
response=response)

View File

@ -0,0 +1,38 @@
# 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 senlin.drivers.openstack import ceilometer_v2
from senlin.drivers.openstack import heat_v1
from senlin.drivers.openstack import lbaas
from senlin.drivers.openstack import neutron_v2
from senlin.tests.functional.drivers.openstack import nova_v2
# Currently, only fake nova_v2 driver is supported
def ComputeClient(params):
return nova_v2.NovaClient(params)
def OrchestrationClient(params):
return heat_v1.HeatClient(params)
def NetworkClient(params):
return neutron_v2.NeutronClient(params)
def LoadBalancingClient(params):
return lbaas.LoadBalancerDriver(params)
def TelemetryClient(params):
return ceilometer_v2.CeilometerClient(params)

View File

@ -0,0 +1,147 @@
# 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 senlin.drivers import base
from senlin.tests.functional.drivers.openstack import sdk
class NovaClient(base.DriverBase):
'''Fake Nova V2 driver for functional test.'''
def __init__(self, ctx):
self.fake_flavor = {
"OS-FLV-DISABLED:disabled": False,
"disk": 1,
"OS-FLV-EXT-DATA:ephemeral": 0,
"os-flavor-access:is_public": True,
"id": "1",
"links": [],
"name": "m1.tiny",
"ram": 512,
"swap": "",
"vcpus": 1
}
self.fake_image = {
"created": "2015-01-01T01:02:03Z",
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [],
"metadata": {
"architecture": "x86_64",
"auto_disk_config": "True",
"kernel_id": "nokernel",
"ramdisk_id": "nokernel"
},
"minDisk": 0,
"minRam": 0,
"name": "cirros-0.3.2-x86_64-uec",
"progress": 100,
"status": "ACTIVE",
"updated": "2011-01-01T01:02:03Z"
}
self.fake_server_create = {
"id": "893c7791-f1df-4c3d-8383-3caae9656c62",
"name": "new-server-test",
"imageRef": "http://localhost/openstack/images/test-image",
"flavorRef": "http://localhost/openstack/flavors/1",
"metadata": {
"My Server Name": "Apache1"
},
"personality": [
{
"path": "/etc/banner.txt",
"contents": "personality-content"
}
],
"block_device_mapping_v2": [
{
"device_name": "/dev/sdb1",
"source_type": "blank",
"destination_type": "local",
"delete_on_termination": "True",
"guest_format": "swap",
"boot_index": "-1"
},
{
"device_name": "/dev/sda1",
"source_type": "volume",
"destination_type": "volume",
"uuid": "fake-volume-id-1",
"boot_index": "0"
}
]
}
self.fake_server_get = {
# Note: The name of some attrs are defined as following to keep
# compatible with the resource definition in openstacksdk. But
# the real name of these attrs returned by Nova API could be
# different, e.g. the name of 'access_ipv4' attribute is actually
# 'accessIPv4' in server_get API response.
"name": "new-server-test",
"id": "893c7791-f1df-4c3d-8383-3caae9656c62",
"access_ipv4": "192.168.0.3",
"access_ipv6": "fe80::ac0e:2aff:fe87:5911",
"addresses": {
"private": [
{
"addr": "192.168.0.3",
"version": 4
}
]
},
"created_at": "2015-08-18T21:11:09Z",
"updated_at": "2012-08-20T21:11:09Z",
"flavor": {
"id": "1",
"links": []
},
"host_id": "65201c14a29663e06d0748e561207d998b343",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": []
},
"links": [],
"metadata": {
"My Server Name": "Apache1"
},
"progress": 0,
"status": "ACTIVE",
"project_id": "openstack",
"user_id": "fake"
}
def flavor_find(self, name_or_id, ignore_missing=False):
return sdk.FakeResourceObject(self.fake_flavor)
def image_get_by_name(self, name_or_id, ignore_missing=False):
return sdk.FakeResourceObject(self.fake_image)
def server_create(self, **attrs):
return sdk.FakeResourceObject(self.fake_server_create)
def server_get(self, value):
return sdk.FakeResourceObject(self.fake_server_get)
def wait_for_server(self, value, timeout=None):
return
def wait_for_server_delete(self, value, timeout=None):
return
def server_update(self, value, **attrs):
self.fake_server_get.update(attrs)
return sdk.FakeResourceObject(self.fake_server_get)
def server_delete(self, value, ignore_missing=True):
return

View File

@ -0,0 +1,18 @@
# 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.
class FakeResourceObject(object):
'''Generate a fake SDK resource object based on given dictionary'''
def __init__(self, params):
for key in params:
setattr(self, key, params[key])

View File

@ -18,7 +18,8 @@ export DEST=${DEST:-/opt/stack/new}
export DEVSTACK_DIR=$DEST/devstack
export SENLIN_DIR=$DEST/senlin
# TODO(Yanyan Hu): Do more preparation work which is necessary
# Do some preparation work before starting functional test
sudo -E $SENLIN_DIR/senlin/tests/functional/prepare_test_env.sh
# Run functional test
source $DEVSTACK_DIR/openrc admin admin

View File

@ -0,0 +1,37 @@
#!/bin/bash -xe
# 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.
# This script is executed inside post_test_hook function in devstack gate.
export DEST=${DEST:-/opt/stack/new}
export SENLIN_CONF=/etc/senlin/senlin.conf
source $DEST/devstack/inc/ini-config
# Send SIGHUP to service
function sighup_proc()
{
NAME=$1
ID=`ps -ef | grep "$NAME" | grep -v "$0" | grep -v "grep" | awk '{print $2}'`
for id in $ID
do
kill -1 $id
echo "sighup $id"
done
}
# Switch cloud_backend to openstack_test
iniset $SENLIN_CONF DEFAULT cloud_backend openstack_test
sighup_proc senlin-engine
sleep 10

View File

@ -10,20 +10,78 @@
# License for the specific language governing permissions and limitations
# under the License.
import time
from oslo_log import log as logging
from senlin.tests.functional import api as test_api
from senlin.tests.functional import base
LOG = logging.getLogger(__name__)
class TestCluster(base.SenlinFunctionalTest):
def setUp(self):
super(TestCluster, self).setUp()
# Create profile
test_nova_spec = {
"flavor": 1,
"name": "new-server-test",
"image": "cirros-0.3.2-x86_64-uec"
}
self.profile = test_api.create_profile(self.client, 'test-profile',
'os.nova.server',
test_nova_spec)
def tearDown(self):
# Delete profile
test_api.delete_profile(self.client, self.profile['id'])
super(TestCluster, self).tearDown()
def test_get_clusters(self):
# Check that listing clusters works.
rel_url = 'clusters'
status = [200]
resp = self.client.api_request('GET', rel_url,
expected_resp_status=status)
clusters = resp.body['clusters']
clusters = test_api.list_clusters(self.client)
self.assertEqual([], clusters)
def test_cluster_create_delete(self):
# Create cluster
desired_capacity = 2
min_size = 1
max_size = 3
cluster = test_api.create_cluster(self.client, 'test-cluster',
self.profile['id'], desired_capacity,
min_size, max_size)
# Wait and verify cluster creation result
# TODO(Yanyan Hu): Put timeout option into test configure file
timeout = 60
ready = False
while timeout > 0:
cluster = test_api.get_cluster(self.client, cluster['id'])
if cluster['status'] == 'ACTIVE':
ready = True
break
time.sleep(5)
timeout -= 5
if not ready:
raise Exception('Cluster creation timeout.')
self.assertEqual('test-cluster', cluster['name'])
self.assertEqual(desired_capacity, cluster['desired_capacity'])
self.assertEqual(min_size, cluster['min_size'])
self.assertEqual(max_size, cluster['max_size'])
self.assertEqual(desired_capacity, len(cluster['nodes']))
# Delete cluster
test_api.delete_cluster(self.client, cluster['id'])
timeout = 60
ready = False
while timeout > 0:
res = test_api.get_cluster(self.client, cluster['id'], True)
if res.status == 404:
ready = True
break
time.sleep(5)
timeout -= 5
if not ready:
raise Exception('Cluster deletion timeout.')

View File

@ -19,8 +19,7 @@ class TestProfileType(base.SenlinFunctionalTest):
# Check that listing profile types works.
rel_url = 'profile_types'
status = [200]
resp = self.client.api_request('GET', rel_url,
expected_resp_status=status)
resp = self.client.api_request('GET', rel_url, resp_status=status)
profile_types = resp.body['profile_types']
expected_profile_types = [{'name': 'os.nova.server'},
{'name': 'os.heat.stack'}]

View File

@ -47,6 +47,7 @@ senlin.policies =
senlin.drivers =
openstack = senlin.drivers.openstack
openstack_test = senlin.tests.functional.drivers.openstack
[global]
setup-hooks =