From ccc43248557c05aba8c4ec26bf08c51c523be5b1 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Tue, 15 Apr 2014 07:43:01 -0400 Subject: [PATCH] feature matrix definition in yaml This adds a definition mechanism for the d-g feature matrix in yaml, a python tool to process that into an ENABLED_SERVICES list and a test script to verify that it works as expected. The theory here is that we create configs (which match to d-g DEVSTACK_GATE_ vars) that enable or disable "features". The features then enable or disable "services" (and eventually extensions). An important part of this is the ability to rm-* content. That means the neutron feature can rm-services: n-net. The hope is this makes reviewing changes more straight forward, and also makes it so if something goes wrong we don't run a job missing services, because we don't need to make sure we add things in the else clauses. A follow up patch integrates this into d-g proper, but this patch seemed easier to review on it's own. Change-Id: Ib030f820073dd0b450b362fd721f9477778c04b0 --- features.yaml | 123 +++++++++++++++++++++++++++++++++++++++++++++ test-features.sh | 86 ++++++++++++++++++++++++++++++++ test-matrix.py | 126 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 features.yaml create mode 100755 test-features.sh create mode 100755 test-matrix.py diff --git a/features.yaml b/features.yaml new file mode 100644 index 00000000..8a4bca85 --- /dev/null +++ b/features.yaml @@ -0,0 +1,123 @@ +config: + default: + master: [default, ceilometer, glance, horizon, nova, swift, cinder, keystone, heat, trove] + icehouse: [default, ceilometer, glance, horizon, nova, swift, cinder, keystone, heat, trove] + havana: [default, ceilometer, glance, horizon, nova, swift, cinder, keystone, heat] + neutron: + features: [neutron] + # different backends + postgres: + features: [postgresql] + # feature changes for different test matrixes + grenade: + rm-features: [ceilometer, heat, trove, sahara] + tempest: + features: [tempest] + # feature changes for different configs of existing services + nova_api_metadata_split: + features: [nova-md] + cells: + features: [nova-cells] + # feature declarations for incubated or recently integrated projects (so they + # can be tested outside the releases they were supported in) + trove: + features: [trove] + marconi: + features: [marconi] + sahara: + features: [sahara] + ironic: + features: [ironic] + +features: + default: + base: + services: [mysql, rabbit, dstat] + + ceilometer: + base: + services: [ceilometer-acompute, ceilometer-acentral, ceilometer-collector, ceilometer-api, ceilometer-alarm-notifier, ceilometer-alarm-evaluator, ceilometer-anotification] + havana: + rm-services: [ceilometer-alarm-notifier, ceilometer-alarm-evaluator, ceilometer-anotification] + + glance: + base: + services: [g-api, g-reg] + + keystone: + base: + services: [key] + + horizon: + base: + services: [horizon] + + nova: + base: + services: [n-api, n-cond, n-cpu, n-crt, n-net, n-obj, n-sch] + havana: + rm-compute-ext: + icehouse: + compute-ext: + + nova-md: + base: + services: [n-api-meta] + + nova-cells: + base: + services: [n-cell] + rm-compute-ext: [agregates, hosts] + + neutron: + base: + services: [quantum, q-svc, q-agt, q-dhcp, q-l3, q-meta, q-lbaas, q-vpn, q-fwaas, q-metering] + rm-services: [n-net] + + swift: + base: + services: [s-proxy, s-account, s-container, s-object] + + cinder: + base: + services: [cinder, c-api, c-vol, c-sch, c-bak] + + heat: + base: + services: [heat, h-api, h-api-cfn, h-api-cw, h-eng] + + trove: + base: + services: [trove, tr-api, tr-tmgr, tr-cond] + + ironic: + base: + services: [ir-api, ir-cond] + + sahara: + base: + services: [sahara] + + marconi: + base: + services: [marconi-server] + + tempest: + base: + services: [tempest] + + # service overrides + postgresql: + base: + services: [postgresql] + rm-services: [mysql] + + zeromq: + base: + services: [zeromq] + rm-services: [rabbit] + + qpid: + base: + services: [qpid] + rm-services: [rabbit] \ No newline at end of file diff --git a/test-features.sh b/test-features.sh new file mode 100755 index 00000000..d5f34668 --- /dev/null +++ b/test-features.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# 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. + +ERRORS=0 + +TEMPEST_FULL_MASTER="n-api,n-crt,n-obj,n-cpu,n-sch,n-cond,g-api,g-reg,key,horizon,c-api,c-vol,c-sch,c-bak,cinder,s-proxy,s-account,s-container,s-object,mysql,rabbit,dstat,tempest,heat,h-api,h-api-cfn,h-api-cw,h-eng,ceilometer-acompute,ceilometer-acentral,ceilometer-collector,ceilometer-api,ceilometer-alarm-notifier,ceilometer-alarm-evaluator,ceilometer-anotification,trove,tr-api,tr-tmgr,tr-cond,n-net" + +TEMPEST_NEUTRON_MASTER="n-api,n-crt,n-obj,n-cpu,n-sch,n-cond,g-api,g-reg,key,horizon,c-api,c-vol,c-sch,c-bak,cinder,s-proxy,s-account,s-container,s-object,mysql,rabbit,dstat,tempest,heat,h-api,h-api-cfn,h-api-cw,h-eng,ceilometer-acompute,ceilometer-acentral,ceilometer-collector,ceilometer-api,ceilometer-alarm-notifier,ceilometer-alarm-evaluator,ceilometer-anotification,trove,tr-api,tr-tmgr,tr-cond,quantum,q-svc,q-agt,q-dhcp,q-l3,q-meta,q-lbaas,q-vpn,q-fwaas,q-metering" + +TEMPEST_HEAT_SLOW_MASTER="n-api,n-crt,n-obj,n-cpu,n-sch,n-cond,g-api,g-reg,key,horizon,c-api,c-vol,c-sch,c-bak,cinder,s-proxy,s-account,s-container,s-object,mysql,rabbit,dstat,tempest,heat,h-api,h-api-cfn,h-api-cw,h-eng,ceilometer-acompute,ceilometer-acentral,ceilometer-collector,ceilometer-api,ceilometer-alarm-notifier,ceilometer-alarm-evaluator,ceilometer-anotification,trove,tr-api,tr-tmgr,tr-cond,quantum,q-svc,q-agt,q-dhcp,q-l3,q-meta,q-lbaas,q-vpn,q-fwaas,q-metering" + +GRENADE_NEW_MASTER="n-api,n-crt,n-obj,n-cpu,n-sch,n-cond,g-api,g-reg,key,horizon,c-api,c-vol,c-sch,c-bak,cinder,s-proxy,s-account,s-container,s-object,mysql,rabbit,dstat,tempest,n-net" + +GRENADE_OLD_MASTER="n-api,n-crt,n-obj,n-cpu,n-sch,n-cond,g-api,g-reg,key,horizon,c-api,c-vol,c-sch,c-bak,cinder,s-proxy,s-account,s-container,s-object,mysql,rabbit,dstat,tempest,n-net" + +TEMPEST_FULL_HAVANA="n-api,n-crt,n-obj,n-cpu,n-sch,n-cond,g-api,g-reg,key,horizon,c-api,c-vol,c-sch,c-bak,cinder,s-proxy,s-account,s-container,s-object,mysql,rabbit,dstat,tempest,heat,h-api,h-api-cfn,h-api-cw,h-eng,ceilometer-acompute,ceilometer-acentral,ceilometer-collector,ceilometer-api,n-net" + +# Utility function for tests +function assert_list_equal { + local source=$(echo $1 | awk 'BEGIN{RS=",";} {print $1}' | sort -V | xargs echo) + local target=$(echo $2 | awk 'BEGIN{RS=",";} {print $1}' | sort -V | xargs echo) + if [[ "$target" != "$source" ]]; then + echo -n `caller 0 | awk '{print $2}'` + echo -e " - ERROR\n $target \n != $source" + ERRORS=1 + else + # simple backtrace progress detector + echo -n `caller 0 | awk '{print $2}'` + echo " - ok" + fi +} + +function test_full_master { + local results=$(DEVSTACK_GATE_TEMPEST=1 ./test-matrix.py) + assert_list_equal $TEMPEST_FULL_MASTER $results +} + +function test_full_havana { + local results=$(DEVSTACK_GATE_TEMPEST=1 ./test-matrix.py -b stable/havana) + assert_list_equal $TEMPEST_FULL_HAVANA $results +} + +function test_neutron_master { + local results=$(DEVSTACK_GATE_NEUTRON=1 DEVSTACK_GATE_TEMPEST=1 ./test-matrix.py) + assert_list_equal $TEMPEST_NEUTRON_MASTER $results +} + +function test_heat_slow_master { + local results=$(DEVSTACK_GATE_TEMPEST_HEAT_SLOW=1 DEVSTACK_GATE_NEUTRON=1 DEVSTACK_GATE_TEMPEST=1 ./test-matrix.py) + assert_list_equal $TEMPEST_HEAT_SLOW_MASTER $results +} + +function test_grenade_new_master { + local results=$(DEVSTACK_GATE_TEMPEST_HEAT_SLOW=1 DEVSTACK_GATE_GRENADE=1 DEVSTACK_GATE_TEMPEST=1 ./test-matrix.py) + assert_list_equal $GRENADE_NEW_MASTER $results +} + +function test_grenade_old_master { + local results=$(DEVSTACK_GATE_GRENADE=1 DEVSTACK_GATE_TEMPEST=1 ./test-matrix.py -b stable/havana) + assert_list_equal $GRENADE_OLD_MASTER $results +} + +test_full_master +test_neutron_master +test_heat_slow_master +test_grenade_new_master +test_grenade_old_master +test_full_havana + +if [[ "$ERRORS" -ne 0 ]]; then + echo "Errors detected, job failed" + exit 1 +fi diff --git a/test-matrix.py b/test-matrix.py new file mode 100755 index 00000000..b4a76f6b --- /dev/null +++ b/test-matrix.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +# 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 argparse +import logging +import os +import sys +import yaml + +GRID = None +ALLOWED_BRANCHES = ('havana', 'icehouse', 'master') + +FORMAT = '%(asctime)s %(levelname)s: %(message)s' +logging.basicConfig(format=FORMAT) +LOG = logging.getLogger(__name__) + + +def parse_features(fname): + with open(fname) as f: + return yaml.load(f) + + +def normalize_branch(branch): + if branch.startswith("stable/"): + branch = branch[len("stable/"):] + if branch not in ALLOWED_BRANCHES: + LOG.error("unknown branch name %s" % branch) + sys.exit(1) + return branch + + +def configs_from_env(): + configs = [] + for k, v in os.environ.iteritems(): + if k.startswith('DEVSTACK_GATE_'): + if v == '1': + f = k.split('DEVSTACK_GATE_')[1] + configs.append(f.lower()) + return configs + + +def calc_services(branch, features): + services = set() + for feature in features: + services.update(GRID['features'][feature]['base'].get('services', [])) + if branch in GRID['features'][feature]: + services.update( + GRID['features'][feature][branch].get('services', [])) + + # deletes always trump adds + for feature in features: + services.difference_update( + GRID['features'][feature]['base'].get('rm-services', [])) + + if branch in GRID['features'][feature]: + services.difference_update( + GRID['features'][feature][branch].get('rm-services', [])) + return sorted(list(services)) + + +def calc_features(branch, configs=[]): + LOG.debug("Branch: %s" % branch) + LOG.debug("Configs: %s" % configs) + features = set(GRID['config']['default'][branch]) + # do all the adds first + for config in configs: + if config in GRID['config']: + features.update(GRID['config'][config].get('features', [])) + + # removes always trump + for config in configs: + if config in GRID['config']: + features.difference_update( + GRID['config'][config].get('rm-features', [])) + return sorted(list(features)) + + +def get_opts(): + usage = """ +Compute the test matrix for devstack gate jobs from a combination +of environmental feature definitions and flags. +""" + parser = argparse.ArgumentParser(description=usage) + parser.add_argument('-f', '--features', + default='features.yaml', + help="Yaml file describing the features matrix") + parser.add_argument('-b', '--branch', + default="master", + help="Branch to compute the matrix for") + parser.add_argument('-m', '--mode', + default="services", + help="What to return (services, compute-ext)") + return parser.parse_args() + + +def main(): + global GRID + opts = get_opts() + GRID = parse_features(opts.features) + branch = normalize_branch(opts.branch) + + features = calc_features(branch, configs_from_env()) + LOG.debug("Features: %s " % features) + + services = calc_services(branch, features) + LOG.debug("Services: %s " % services) + + if opts.mode == "services": + print ",".join(services) + + +if __name__ == "__main__": + sys.exit(main())