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
This commit is contained in:
Sean Dague
2014-04-15 07:43:01 -04:00
parent 06ddbc08b5
commit ccc4324855
3 changed files with 335 additions and 0 deletions

123
features.yaml Normal file
View File

@@ -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]

86
test-features.sh Executable file
View File

@@ -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

126
test-matrix.py Executable file
View File

@@ -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())