From a4a3a9ff11834570adb89a7c875a319d7126664d Mon Sep 17 00:00:00 2001 From: zhiyuan_cai Date: Tue, 11 Apr 2017 17:13:54 +0800 Subject: [PATCH] Basic smoke test script 1. What is the problem? Multi-region test has been added to our check/gate jobs, but the test just installs Tricircle via DevStack and doesn't provision any resources like network/subnet/router/server, so Tricircle functionality is not tested. 2. What is the solution to the problem? Add a script in the test to create a basic network topology via central Neutron and check if local resources are correctly created. In the topology, two tenant networks are connected by a router, an external network is attached to the router. We boot one server in each tenant network and associate a floating IP to one of the server. This patch also fixes a problem brought by (1) Eliminate lookup of "resource extend" funcs by name https://github.com/openstack/neutron/commit/92372b982f2cc9f7cda8ac3f3b3e78e46201720f (2) Defer service_plugins configuration https://github.com/openstack-dev/devstack/commit/a8204752e32ff619aa4d94409d7427bee0c50864 We can put these changes in a standalone patch, but let's first put them here to test by this smoke test. 3. What features need to be implemented to the Tricircle to realize the solution? Tricircle functionality can be tested. Change-Id: Ib364a96fe4c3b9b635e5fac979c7c1cba2aaefc9 --- devstack/local.conf.node_1.sample | 1 + devstack/local.conf.node_2.sample | 1 + devstack/plugin.sh | 6 +- tricircle/common/client.py | 4 +- tricircle/network/central_plugin.py | 6 +- tricircle/tempestplugin/gate_hook.sh | 14 ++ tricircle/tempestplugin/post_test_hook.sh | 12 +- tricircle/tempestplugin/smoke_test.sh | 155 ++++++++++++++++++ .../tempestplugin/smoke_test_validation.py | 75 +++++++++ 9 files changed, 257 insertions(+), 17 deletions(-) create mode 100644 tricircle/tempestplugin/smoke_test.sh create mode 100644 tricircle/tempestplugin/smoke_test_validation.py diff --git a/devstack/local.conf.node_1.sample b/devstack/local.conf.node_1.sample index b389f3fa..af76a84f 100644 --- a/devstack/local.conf.node_1.sample +++ b/devstack/local.conf.node_1.sample @@ -30,6 +30,7 @@ Q_ML2_PLUGIN_VLAN_TYPE_OPTIONS=(network_vlan_ranges=bridge:2001:3000,extern:3001 Q_ML2_PLUGIN_VXLAN_TYPE_OPTIONS=(vni_ranges=1001:2000) Q_ML2_PLUGIN_FLAT_TYPE_OPTIONS=(flat_networks=bridge,extern) OVS_BRIDGE_MAPPINGS=bridge:br-vlan +ML2_L3_PLUGIN=tricircle.network.local_l3_plugin.TricircleL3Plugin # Specify Central Region name # CENTRAL_REGION_NAME=CentralRegion diff --git a/devstack/local.conf.node_2.sample b/devstack/local.conf.node_2.sample index 1ebffbdf..8082dc4e 100644 --- a/devstack/local.conf.node_2.sample +++ b/devstack/local.conf.node_2.sample @@ -34,6 +34,7 @@ Q_ML2_PLUGIN_VLAN_TYPE_OPTIONS=(network_vlan_ranges=bridge:2001:3000,extern:3001 Q_ML2_PLUGIN_VXLAN_TYPE_OPTIONS=(vni_ranges=1001:2000) Q_ML2_PLUGIN_FLAT_TYPE_OPTIONS=(flat_networks=bridge,extern) OVS_BRIDGE_MAPPINGS=bridge:br-vlan,extern:br-ext +ML2_L3_PLUGIN=tricircle.network.local_l3_plugin.TricircleL3Plugin # Specify Central Region name # CENTRAL_REGION_NAME=CentralRegion diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 030a9f0c..19be6fd5 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -64,10 +64,9 @@ function init_common_tricircle_conf { function init_local_neutron_conf { iniset $NEUTRON_CONF DEFAULT core_plugin tricircle.network.local_plugin.TricirclePlugin - iniset $NEUTRON_CONF DEFAULT service_plugins tricircle.network.local_l3_plugin.TricircleL3Plugin - iniset $NEUTRON_CONF client auth_url http://$KEYSTONE_SERVICE_HOST:5000/v3 - iniset $NEUTRON_CONF client identity_url http://$KEYSTONE_SERVICE_HOST:35357/v3 + iniset $NEUTRON_CONF client auth_url http://$KEYSTONE_SERVICE_HOST/identity/v3 + iniset $NEUTRON_CONF client identity_url http://$KEYSTONE_SERVICE_HOST/identity_admin/v3 iniset $NEUTRON_CONF client admin_username admin iniset $NEUTRON_CONF client admin_password $ADMIN_PASSWORD iniset $NEUTRON_CONF client admin_tenant demo @@ -162,6 +161,7 @@ function configure_tricircle_api_wsgi { sudo cp $TRICIRCLE_API_APACHE_TEMPLATE $tricircle_api_apache_conf sudo sed -e " + s|%TRICIRCLE_BIN%|$tricircle_bin_dir|g; s|%PUBLICPORT%|$TRICIRCLE_API_PORT|g; s|%APACHE_NAME%|$APACHE_NAME|g; s|%PUBLICWSGI%|$tricircle_bin_dir/tricircle-api-wsgi|g; diff --git a/tricircle/common/client.py b/tricircle/common/client.py index 803f6d7d..25fdc8b1 100644 --- a/tricircle/common/client.py +++ b/tricircle/common/client.py @@ -35,10 +35,10 @@ from tricircle.db import models client_opts = [ cfg.StrOpt('auth_url', - default='http://127.0.0.1:5000/v3', + default='http://127.0.0.1/identity/v3', help='keystone authorization url'), cfg.StrOpt('identity_url', - default='http://127.0.0.1:35357/v3', + default='http://127.0.0.1/identity_admin/v3', help='keystone service url'), cfg.BoolOpt('auto_refresh_endpoint', default=False, diff --git a/tricircle/network/central_plugin.py b/tricircle/network/central_plugin.py index c9a5d318..78a2d56f 100644 --- a/tricircle/network/central_plugin.py +++ b/tricircle/network/central_plugin.py @@ -28,9 +28,9 @@ from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources import neutron.common.exceptions as ml2_exceptions +from neutron.db import _resource_extend as resource_extend from neutron.db import api as q_db_api from neutron.db.availability_zone import router as router_az -from neutron.db import common_db_mixin from neutron.db import db_base_plugin_v2 from neutron.db import external_net_db from neutron.db import extradhcpopt_db @@ -218,13 +218,11 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, availability_zone=diff.pop()) @staticmethod + @resource_extend.extends([attributes.NETWORKS]) def _extend_availability_zone(net_res, net_db): net_res[az_ext.AZ_HINTS] = az_ext.convert_az_string_to_list( net_db[az_ext.AZ_HINTS]) - common_db_mixin.CommonDbMixin.register_dict_extend_funcs( - attributes.NETWORKS, ['_extend_availability_zone']) - @staticmethod def _ensure_az_set_for_external_network(context, req_data): external = req_data.get(external_net.EXTERNAL) diff --git a/tricircle/tempestplugin/gate_hook.sh b/tricircle/tempestplugin/gate_hook.sh index d957a2f0..89e3678b 100755 --- a/tricircle/tempestplugin/gate_hook.sh +++ b/tricircle/tempestplugin/gate_hook.sh @@ -40,11 +40,23 @@ function _setup_tricircle_multinode { ENABLE_TRICIRCLE="enable_plugin tricircle https://git.openstack.org/openstack/tricircle/" + # Configure primary node export DEVSTACK_LOCAL_CONFIG="$ENABLE_TRICIRCLE" export DEVSTACK_LOCAL_CONFIG+=$'\n'"TRICIRCLE_START_SERVICES=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"REGION_NAME=RegionOne" export DEVSTACK_LOCAL_CONFIG+=$'\n'"HOST_IP=$PRIMARY_NODE_IP" + ML2_CONFIG=$'\n'"ML2_L3_PLUGIN=tricircle.network.local_l3_plugin.TricircleL3Plugin" + ML2_CONFIG+=$'\n'"[[post-config|/"'$Q_PLUGIN_CONF_FILE]]' + ML2_CONFIG+=$'\n'"[ml2]" + ML2_CONFIG+=$'\n'"mechanism_drivers = openvswitch,linuxbridge,l2population" + ML2_CONFIG+=$'\n'"[agent]" + ML2_CONFIG+=$'\n'"tunnel_types=vxlan" + ML2_CONFIG+=$'\n'"l2_population=True" + + export DEVSTACK_LOCAL_CONFIG+=$ML2_CONFIG + + # Configure sub-node export DEVSTACK_SUBNODE_CONFIG="$ENABLE_TRICIRCLE" export DEVSTACK_SUBNODE_CONFIG+=$'\n'"TRICIRCLE_START_SERVICES=False" export DEVSTACK_SUBNODE_CONFIG+=$'\n'"REGION_NAME=RegionTwo" @@ -59,6 +71,8 @@ function _setup_tricircle_multinode { export DEVSTACK_SUBNODE_CONFIG+=$'\n'"DATABASE_HOST=$SUBNODE_IP" export DEVSTACK_SUBNODE_CONFIG+=$'\n'"GLANCE_HOSTPORT=$SUBNODE_IP:9292" export DEVSTACK_SUBNODE_CONFIG+=$'\n'"Q_HOST=$SUBNODE_IP" + + export DEVSTACK_SUBNODE_CONFIG+=$ML2_CONFIG } if [ "$DEVSTACK_GATE_TOPOLOGY" == "multinode" ]; then diff --git a/tricircle/tempestplugin/post_test_hook.sh b/tricircle/tempestplugin/post_test_hook.sh index cef6346b..800ade88 100755 --- a/tricircle/tempestplugin/post_test_hook.sh +++ b/tricircle/tempestplugin/post_test_hook.sh @@ -94,11 +94,7 @@ iniset $TEMPEST_CONF volume-feature-enabled api_v1 false iniset $TEMPEST_CONF validation connect_method fixed - -# Run the Network Tempest tests -cd $TRICIRCLE_TEMPEST_PLUGIN_DIR -sudo BASE=$BASE ./tempest_network.sh - -# Run the Scenario Tempest tests -# cd $TRICIRCLE_TEMPEST_PLUGIN_DIR -# sudo BASE=$BASE ./tempest_scenario.sh +if [ "$DEVSTACK_GATE_TOPOLOGY" == "multinode" ]; then + cd $TRICIRCLE_TEMPEST_PLUGIN_DIR + sudo BASE=$BASE bash smoke_test.sh +fi diff --git a/tricircle/tempestplugin/smoke_test.sh b/tricircle/tempestplugin/smoke_test.sh new file mode 100644 index 00000000..ccadd289 --- /dev/null +++ b/tricircle/tempestplugin/smoke_test.sh @@ -0,0 +1,155 @@ +#!/bin/bash -xe + +DEST=$BASE/new +DEVSTACK_DIR=$DEST/devstack +source $DEVSTACK_DIR/openrc admin demo +unset OS_REGION_NAME + +openstacktop="openstack --os-region-name CentralRegion" +openstackpod1="openstack --os-region-name RegionOne" +openstackpod2="openstack --os-region-name RegionTwo" + +echo list networks before running +$openstacktop network list + +echo create external network +$openstacktop network create --external --provider-network-type vlan \ + --provider-physical-network extern --availability-zone-hint RegionTwo ext-net + +echo show networks after running +for id in $($openstacktop network list -c ID -f value) + do $openstacktop network show $id +done + +echo create external subnet +$openstacktop subnet create --subnet-range 163.3.124.0/24 --network ext-net \ + --no-dhcp ext-subnet + +echo create router +router_id=$($openstacktop router create router -c id -f value) + +echo attach router to external network +$openstacktop router set --external-gateway ext-net router + +echo create network1 +$openstacktop network create net1 + +echo create subnet1 +$openstacktop subnet create --subnet-range 10.0.1.0/24 --network net1 subnet1 + +echo create port1 +port1_id=$($openstacktop port create --network net1 port1 -c id -f value) + +echo attach subnet1 to router +$openstacktop router add subnet router subnet1 + +echo associate floating ip to port1 +$openstacktop floating ip create --port $port1_id ext-net -c id -f value + +image1_id=$($openstackpod1 image list -c ID -f value) + +echo create server1 +$openstackpod1 server create --flavor 1 --image $image1_id --nic port-id=$port1_id vm1 + +echo create network2 +net2_id=$($openstacktop network create net2 -c id -f value) + +echo create subnet2 +$openstacktop subnet create --subnet-range 10.0.2.0/24 --network net2 subnet2 + +image2_id=$($openstackpod2 image list -c ID -f value) + +echo create server2 +$openstackpod2 server create --flavor 1 --image $image2_id --nic net-id=$net2_id vm2 + +echo attach subnet2 to router +$openstacktop router add subnet router subnet2 + +sleep 20 + +TOP_DIR=$DEVSTACK_DIR +source $DEVSTACK_DIR/stackrc +source $DEVSTACK_DIR/inc/meta-config +extract_localrc_section $TOP_DIR/local.conf $TOP_DIR/localrc $TOP_DIR/.localrc.auto +source $DEVSTACK_DIR/functions-common +source $DEVSTACK_DIR/lib/database +initialize_database_backends + +if [ "$DATABASE_TYPE" == "mysql" ]; then + for i in $(seq 1 11); do + if [ $i == 11 ]; then + # we check fail job at the end to give fail job a chance to redo + fail_result=$(mysql -u$DATABASE_USER -p$DATABASE_PASSWORD -h$DATABASE_HOST -Dtricircle -e 'SELECT COUNT(*) FROM async_jobs WHERE status = "0_Fail"') + fail_count=$(echo $fail_result | grep -o "[0-9]\{1,\}") + if [ $fail_count -ne 0 ]; then + echo "Listing fail job" + mysql -u$DATABASE_USER -p$DATABASE_PASSWORD -h$DATABASE_HOST -Dtricircle -e 'SELECT * FROM async_jobs WHERE status = "0_Fail";' + die $LINENO "Smoke test fails, $fail_count job fail" + fi + die $LINENO "Smoke test fails, exceed max wait time for job" + fi + full_result=$(mysql -u$DATABASE_USER -p$DATABASE_PASSWORD -h$DATABASE_HOST -Dtricircle -e 'SELECT COUNT(*) FROM async_jobs;') + full_count=$(echo $full_result | grep -o "[0-9]\{1,\}") + if [ $full_count -ne 0 ]; then + echo "Wait for job to finish" + sleep 5 + else + break + fi + done +else + for i in $(seq 1 11); do + if [ $i == 11 ]; then + # we check fail job at the end to give fail job a chance to redo + fail_result=$(psql -h$DATABASE_HOST -U$DATABASE_USER -dtricircle -c 'SELECT COUNT(*) FROM async_jobs WHERE status = "0_Fail"') + fail_count=$(echo $fail_result | grep -o "[0-9]\{1,\}") + if [ $fail_count -ne 0 ]; then + echo "Listing fail job" + psql -h$DATABASE_HOST -U$DATABASE_USER -dtricircle -c 'SELECT * FROM async_jobs WHERE status = "0_Fail";' + die $LINENO "Smoke test fails, $fail_count job fail" + fi + die $LINENO "Smoke test fails, exceed max wait time for job" + fi + full_result=$(psql -h$DATABASE_HOST -U$DATABASE_USER -dtricircle -c 'SELECT COUNT(*) FROM async_jobs;') + full_count=$(echo $full_result | grep -o "[0-9]\{1,\}") + if [ $full_count -ne 0 ]; then + echo "Wait for job to finish" + sleep 5 + else + break + fi + done +fi + +$openstackpod1 server list -f json | python smoke_test_validation.py server 1 +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in server of RegionOne" +fi +$openstackpod2 server list -f json | python smoke_test_validation.py server 2 +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in server of RegionTwo" +fi +$openstackpod1 subnet list -f json | python smoke_test_validation.py subnet 1 +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in subnet of RegionOne" +fi +$openstackpod2 subnet list -f json | python smoke_test_validation.py subnet 2 +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in subnet of RegionTwo" +fi +$openstackpod1 port list --router $router_id -f json | python smoke_test_validation.py router_port 1 +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in router port of RegionOne" +fi +$openstackpod2 port list --router $router_id -f json | python smoke_test_validation.py router_port 2 +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in router port of RegionTwo" +fi +$openstackpod1 router show $router_id -c routes -f json | python smoke_test_validation.py router 1 +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in router of RegionOne" +fi +$openstackpod2 router show $router_id -c routes -f json | python smoke_test_validation.py router 2 +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in router of RegionTwo" +fi diff --git a/tricircle/tempestplugin/smoke_test_validation.py b/tricircle/tempestplugin/smoke_test_validation.py new file mode 100644 index 00000000..fbef968a --- /dev/null +++ b/tricircle/tempestplugin/smoke_test_validation.py @@ -0,0 +1,75 @@ +# Copyright 2017 Huawei Technologies Co., Ltd. +# 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. + + +import json +import sys + + +class ContainedString(object): + def __init__(self, txt): + self.content = txt + + def __eq__(self, other): + return other.find(self.content) != -1 + + def __ne__(self, other): + return other.find(self.content) == -1 + + +CONDITIONS = { + '1': {'server': [{'Name': 'vm1', 'Status': 'ACTIVE'}], + 'subnet': [{'Subnet': '100.0.0.0/24'}, {'Subnet': '10.0.1.0/24'}], + 'router_port': [{'Fixed IP Addresses': ContainedString('10.0.1')}, + {'Fixed IP Addresses': ContainedString('100.0.0')}], + 'router': [ + {'routes': ContainedString( + "destination='0.0.0.0/0', gateway='100.0.0.1'")}, + {'routes': ContainedString("destination='10.0.2")}]}, + '2': {'server': [{'Name': 'vm2', 'Status': 'ACTIVE'}], + 'subnet': [{'Subnet': '100.0.0.0/24'}, {'Subnet': '10.0.1.0/24'}, + {'Subnet': '10.0.2.0/24'}, {'Subnet': '163.3.124.0/24'}], + 'router_port': [{'Fixed IP Addresses': ContainedString('10.0.2')}, + {'Fixed IP Addresses': ContainedString('100.0.0')}], + 'router': [ + {'routes': ContainedString( + "destination='0.0.0.0/0', gateway='100.0.0.1'")}, + {'routes': ContainedString("destination='10.0.1")}]} +} + + +def validate_condition(result, condition): + if not isinstance(result, list): + result = [result] + for res in result: + if all(res[key] == value for (key, value) in condition.items()): + return True + return False + + +def validate_result(result, region, res_type): + for condition in CONDITIONS[region][res_type]: + if not validate_condition(result, condition): + return False + return True + + +if __name__ == '__main__': + res_type, region = sys.argv[1:] + raw_result = ''.join([line for line in sys.stdin]) + result = json.loads(raw_result) + passed = validate_result(result, region, res_type) + # True is casted to 1, but 1 indicates error in shell + sys.exit(1 - int(passed))