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:
parent
1fe9113a21
commit
71045aef01
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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])
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.')
|
||||
|
|
|
@ -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'}]
|
||||
|
|
Loading…
Reference in New Issue