New integration tests - base functional
Features: * create node group templates * create cluster templates * create clusters * check cluster status * default templates for hdp and vanilla plugins Command for run tests: $ tox -e scenario <path_to_yaml> partially implements bp: scenario-integration-tests Change-Id: Ief6aafff713520c972846aa45f0aaefd34ea1bd7
This commit is contained in:
parent
e8c8880d35
commit
23e9aacad4
@ -48,6 +48,8 @@ include sahara/service/edp/resources/*.xml
|
||||
include sahara/service/edp/resources/*.jar
|
||||
include sahara/service/edp/resources/launch_command.py
|
||||
include sahara/swift/resources/*.xml
|
||||
include sahara/tests/scenario/testcase.py.mako
|
||||
recursive-include sahara/tests/scenario/templates *.json
|
||||
include sahara/tests/unit/plugins/vanilla/hadoop2/resources/*.txt
|
||||
include sahara/tests/unit/plugins/mapr/utils/resources/*.topology
|
||||
include sahara/tests/unit/plugins/mapr/utils/resources/*.json
|
||||
|
17
etc/scenario/simple-testcase.yaml
Normal file
17
etc/scenario/simple-testcase.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
credentials:
|
||||
os_username: admin
|
||||
os_password: nova
|
||||
os_tenant: admin
|
||||
os_auth_url: http://localhost:5000/v2.0
|
||||
|
||||
network:
|
||||
private_network: private
|
||||
public_network: public
|
||||
|
||||
clusters:
|
||||
- plugin_name: vanilla
|
||||
plugin_version: 2.6.0
|
||||
image: sahara-juno-vanilla-2.6.0-ubuntu-14.04
|
||||
- plugin_name: hdp
|
||||
plugin_version: 2.0.6
|
||||
image: f3c4a228-9ba4-41f1-b100-a0587689d4dd
|
0
sahara/tests/scenario/__init__.py
Normal file
0
sahara/tests/scenario/__init__.py
Normal file
196
sahara/tests/scenario/base.py
Normal file
196
sahara/tests/scenario/base.py
Normal file
@ -0,0 +1,196 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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 __future__ import print_function
|
||||
import functools
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
import fixtures
|
||||
from oslo_utils import excutils
|
||||
from tempest_lib import base
|
||||
from tempest_lib import exceptions as exc
|
||||
|
||||
from sahara.tests.scenario import clients
|
||||
from sahara.tests.scenario import utils
|
||||
|
||||
DEFAULT_TEMPLATES_PATH = (
|
||||
'sahara/tests/scenario/templates/%(plugin_name)s/%(hadoop_version)s')
|
||||
|
||||
|
||||
def errormsg(message):
|
||||
def decorator(fct):
|
||||
@functools.wraps(fct)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return fct(*args, **kwargs)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
print(message)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
class BaseTestCase(base.BaseTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(BaseTestCase, cls).setUpClass()
|
||||
cls.network = None
|
||||
cls.credentials = None
|
||||
cls.testcase = None
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
self._init_clients()
|
||||
self.plugin_opts = {
|
||||
'plugin_name': self.testcase['plugin_name'],
|
||||
'hadoop_version': self.testcase['plugin_version']
|
||||
}
|
||||
self.template_path = DEFAULT_TEMPLATES_PATH % self.plugin_opts
|
||||
|
||||
def _init_clients(self):
|
||||
username = self.credentials['os_username']
|
||||
password = self.credentials['os_password']
|
||||
tenant_name = self.credentials['os_tenant']
|
||||
auth_url = self.credentials['os_auth_url']
|
||||
sahara_url = self.credentials['sahara_url']
|
||||
|
||||
self.sahara = clients.SaharaClient(username=username,
|
||||
api_key=password,
|
||||
project_name=tenant_name,
|
||||
auth_url=auth_url,
|
||||
sahara_url=sahara_url)
|
||||
self.nova = clients.NovaClient(username=username,
|
||||
api_key=password,
|
||||
project_id=tenant_name,
|
||||
auth_url=auth_url)
|
||||
self.neutron = clients.NeutronClient(username=username,
|
||||
password=password,
|
||||
tenant_name=tenant_name,
|
||||
auth_url=auth_url)
|
||||
|
||||
def create_cluster(self):
|
||||
ngs = self._create_node_group_templates()
|
||||
cl_tmpl_id = self._create_cluster_template(ngs)
|
||||
cl_id = self._create_cluster(cl_tmpl_id)
|
||||
self._poll_cluster_status(cl_id)
|
||||
|
||||
@errormsg("Create node group templates failed")
|
||||
def _create_node_group_templates(self):
|
||||
ng_id_map = {}
|
||||
floating_ip_pool = None
|
||||
if self.network['type'] == 'neutron':
|
||||
floating_ip_pool = self.neutron.get_network_id(
|
||||
self.network['public_network'])
|
||||
elif not self.network['auto_assignment_floating_ip']:
|
||||
floating_ip_pool = self.network['public_network']
|
||||
|
||||
node_groups = []
|
||||
if self.testcase.get('node_group_templates'):
|
||||
for ng in self.testcase['node_group_templates']:
|
||||
node_groups.append(ng)
|
||||
else:
|
||||
templates_path = os.path.join(self.template_path,
|
||||
'node_group_template_*.json')
|
||||
for template_file in glob.glob(templates_path):
|
||||
with open(template_file) as data:
|
||||
node_groups.append(json.load(data))
|
||||
|
||||
for ng in node_groups:
|
||||
kwargs = dict(ng)
|
||||
kwargs.update(self.plugin_opts)
|
||||
kwargs['name'] = utils.rand_name(kwargs['name'])
|
||||
kwargs['floating_ip_pool'] = floating_ip_pool
|
||||
ng_id = self.__create_node_group_template(**kwargs)
|
||||
ng_id_map[ng['name']] = ng_id
|
||||
|
||||
return ng_id_map
|
||||
|
||||
@errormsg("Create cluster template failed")
|
||||
def _create_cluster_template(self, node_groups):
|
||||
template = None
|
||||
if self.testcase.get('cluster_template'):
|
||||
template = self.testcase['cluster_template']
|
||||
else:
|
||||
template_path = os.path.join(self.template_path,
|
||||
'cluster_template.json')
|
||||
with open(template_path) as data:
|
||||
template = json.load(data)
|
||||
|
||||
kwargs = dict(template)
|
||||
ngs = kwargs['node_group_templates']
|
||||
del kwargs['node_group_templates']
|
||||
kwargs['node_groups'] = []
|
||||
for ng, count in ngs.items():
|
||||
kwargs['node_groups'].append({
|
||||
'name': utils.rand_name(ng),
|
||||
'node_group_template_id': node_groups[ng],
|
||||
'count': count})
|
||||
|
||||
kwargs.update(self.plugin_opts)
|
||||
kwargs['name'] = utils.rand_name(kwargs['name'])
|
||||
if self.network['type'] == 'neutron':
|
||||
kwargs['net_id'] = self.neutron.get_network_id(
|
||||
self.network['private_network'])
|
||||
|
||||
return self.__create_cluster_template(**kwargs)
|
||||
|
||||
@errormsg("Create cluster failed")
|
||||
def _create_cluster(self, cluster_template_id):
|
||||
if self.testcase.get('cluster'):
|
||||
kwargs = dict(self.testcase['cluster'])
|
||||
else:
|
||||
kwargs = {} # default template
|
||||
|
||||
kwargs.update(self.plugin_opts)
|
||||
kwargs['name'] = utils.rand_name(kwargs.get('name', 'test'))
|
||||
kwargs['cluster_template_id'] = cluster_template_id
|
||||
kwargs['default_image_id'] = self.nova.get_image_id(
|
||||
self.testcase['image'])
|
||||
|
||||
return self.__create_cluster(**kwargs)
|
||||
|
||||
def _poll_cluster_status(self, cluster_id):
|
||||
# TODO(sreshetniak): make timeout configurable
|
||||
with fixtures.Timeout(1800, gentle=True):
|
||||
while True:
|
||||
status = self.sahara.get_cluster_status(cluster_id)
|
||||
if status == 'Active':
|
||||
break
|
||||
if status == 'Error':
|
||||
raise exc.TempestException("Cluster in %s state" % status)
|
||||
time.sleep(3)
|
||||
|
||||
# sahara client ops
|
||||
|
||||
def __create_node_group_template(self, *args, **kwargs):
|
||||
id = self.sahara.create_node_group_template(*args, **kwargs)
|
||||
if not self.testcase['retain_resources']:
|
||||
self.addCleanup(self.sahara.delete_node_group_template, id)
|
||||
return id
|
||||
|
||||
def __create_cluster_template(self, *args, **kwargs):
|
||||
id = self.sahara.create_cluster_template(*args, **kwargs)
|
||||
if not self.testcase['retain_resources']:
|
||||
self.addCleanup(self.sahara.delete_cluster_template, id)
|
||||
return id
|
||||
|
||||
def __create_cluster(self, *args, **kwargs):
|
||||
id = self.sahara.create_cluster(*args, **kwargs)
|
||||
if not self.testcase['retain_resources']:
|
||||
self.addCleanup(self.sahara.delete_cluster, id)
|
||||
return id
|
109
sahara/tests/scenario/clients.py
Normal file
109
sahara/tests/scenario/clients.py
Normal file
@ -0,0 +1,109 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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 time
|
||||
|
||||
import fixtures
|
||||
from neutronclient.neutron import client as neutron_client
|
||||
from novaclient import client as nova_client
|
||||
from oslo_utils import uuidutils
|
||||
from saharaclient.api import base as saharaclient_base
|
||||
from saharaclient import client as sahara_client
|
||||
from tempest_lib import exceptions as exc
|
||||
|
||||
|
||||
class Client(object):
|
||||
def is_resource_deleted(self, method, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_resource(self, method, *args, **kwargs):
|
||||
# TODO(sreshetniak): make timeout configurable
|
||||
with fixtures.Timeout(300, gentle=True):
|
||||
while True:
|
||||
if self.is_resource_deleted(method, *args, **kwargs):
|
||||
break
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
class SaharaClient(Client):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.sahara_client = sahara_client.Client('1.1', *args, **kwargs)
|
||||
|
||||
def create_node_group_template(self, *args, **kwargs):
|
||||
data = self.sahara_client.node_group_templates.create(*args, **kwargs)
|
||||
return data.id
|
||||
|
||||
def delete_node_group_template(self, node_group_template_id):
|
||||
return self.delete_resource(
|
||||
self.sahara_client.node_group_templates.delete,
|
||||
node_group_template_id)
|
||||
|
||||
def create_cluster_template(self, *args, **kwargs):
|
||||
data = self.sahara_client.cluster_templates.create(*args, **kwargs)
|
||||
return data.id
|
||||
|
||||
def delete_cluster_template(self, cluster_template_id):
|
||||
return self.delete_resource(
|
||||
self.sahara_client.cluster_templates.delete,
|
||||
cluster_template_id)
|
||||
|
||||
def create_cluster(self, *args, **kwargs):
|
||||
data = self.sahara_client.clusters.create(*args, **kwargs)
|
||||
return data.id
|
||||
|
||||
def delete_cluster(self, cluster_id):
|
||||
return self.delete_resource(
|
||||
self.sahara_client.clusters.delete,
|
||||
cluster_id)
|
||||
|
||||
def get_cluster_status(self, cluster_id):
|
||||
data = self.sahara_client.clusters.get(cluster_id)
|
||||
return str(data.status)
|
||||
|
||||
def is_resource_deleted(self, method, *args, **kwargs):
|
||||
try:
|
||||
method(*args, **kwargs)
|
||||
except saharaclient_base.APIException as ex:
|
||||
return ex.error_code == 404
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class NovaClient(Client):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.nova_client = nova_client.Client('1.1', *args, **kwargs)
|
||||
|
||||
def get_image_id(self, image_name):
|
||||
if uuidutils.is_uuid_like(image_name):
|
||||
return image_name
|
||||
for image in self.nova_client.images.list():
|
||||
if image.name == image_name:
|
||||
return image.id
|
||||
|
||||
raise exc.NotFound(image_name)
|
||||
|
||||
|
||||
class NeutronClient(Client):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.neutron_client = neutron_client.Client('2.0', *args, **kwargs)
|
||||
|
||||
def get_network_id(self, network_name):
|
||||
if uuidutils.is_uuid_like(network_name):
|
||||
return network_name
|
||||
networks = self.neutron_client.list_networks(name=network_name)
|
||||
networks = networks['networks']
|
||||
if len(networks) < 1:
|
||||
raise exc.NotFound(network_name)
|
||||
return networks[0]['id']
|
95
sahara/tests/scenario/runner.py
Executable file
95
sahara/tests/scenario/runner.py
Executable file
@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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 __future__ import print_function
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from mako import template as mako_template
|
||||
import yaml
|
||||
|
||||
from sahara.openstack.common import fileutils
|
||||
|
||||
|
||||
TEST_TEMPLATE_PATH = 'sahara/tests/scenario/testcase.py.mako'
|
||||
|
||||
|
||||
def set_defaults(config):
|
||||
# set up credentials
|
||||
config['credentials'] = config.get('credentials', {})
|
||||
creds = config['credentials']
|
||||
creds['os_username'] = creds.get('os_username', 'admin')
|
||||
creds['os_password'] = creds.get('os_password', 'nova')
|
||||
creds['os_tenant'] = creds.get('os_tenant', 'admin')
|
||||
creds['os_auth_url'] = creds.get('os_auth_url',
|
||||
'http://localhost:5000/v2.0')
|
||||
creds['sahara_url'] = creds.get('sahara_url', None)
|
||||
|
||||
# set up network
|
||||
config['network'] = config.get('network', {})
|
||||
net = config['network']
|
||||
net['type'] = net.get('type', 'neutron')
|
||||
net['private_network'] = net.get('private_network', 'private')
|
||||
net['auto_assignment_floating_ip'] = net.get('auto_assignment_floating_ip',
|
||||
False)
|
||||
net['public_network'] = net.get('public_network', 'public')
|
||||
|
||||
# set up tests parameters
|
||||
for testcase in config['clusters']:
|
||||
testcase['class_name'] = "".join([
|
||||
testcase['plugin_name'],
|
||||
testcase['plugin_version'].replace('.', '_')])
|
||||
testcase['retain_resources'] = testcase.get('retain_resources', False)
|
||||
|
||||
|
||||
def main():
|
||||
# parse args
|
||||
parser = argparse.ArgumentParser(description="Scenario tests runner.")
|
||||
parser.add_argument('scenario_file', help="Path to scenario file.")
|
||||
args = parser.parse_args()
|
||||
scenario_file = args.scenario_file
|
||||
|
||||
# parse config
|
||||
with open(scenario_file, 'r') as yaml_file:
|
||||
config = yaml.load(yaml_file)
|
||||
|
||||
set_defaults(config)
|
||||
credentials = config['credentials']
|
||||
network = config['network']
|
||||
testcases = config['clusters']
|
||||
|
||||
# create testcase file
|
||||
test_template = mako_template.Template(filename=TEST_TEMPLATE_PATH)
|
||||
testcase_data = test_template.render(testcases=testcases,
|
||||
credentials=credentials,
|
||||
network=network)
|
||||
|
||||
test_dir_path = tempfile.mkdtemp()
|
||||
print("The generated test file located at: %s" % test_dir_path)
|
||||
fileutils.write_to_tempfile(testcase_data, prefix='test_', suffix='.py',
|
||||
path=test_dir_path)
|
||||
|
||||
# run tests
|
||||
os.environ['DISCOVER_DIRECTORY'] = test_dir_path
|
||||
return_code = os.system('bash tools/pretty_tox.sh')
|
||||
sys.exit(return_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "hdp-206",
|
||||
"node_group_templates": {
|
||||
"hdp-master": 1,
|
||||
"hdp-worker": 3
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "hdp-master",
|
||||
"flavor_id": "3",
|
||||
"node_processes": [
|
||||
"NAMENODE",
|
||||
"SECONDARY_NAMENODE",
|
||||
"ZOOKEEPER_SERVER",
|
||||
"AMBARI_SERVER",
|
||||
"HISTORYSERVER",
|
||||
"RESOURCEMANAGER",
|
||||
"GANGLIA_SERVER",
|
||||
"NAGIOS_SERVER",
|
||||
"OOZIE_SERVER"
|
||||
],
|
||||
"auto_security_group" : true
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "hdp-worker",
|
||||
"flavor_id": "3",
|
||||
"node_processes": [
|
||||
"HDFS_CLIENT",
|
||||
"DATANODE",
|
||||
"ZOOKEEPER_CLIENT",
|
||||
"MAPREDUCE2_CLIENT",
|
||||
"YARN_CLIENT",
|
||||
"NODEMANAGER",
|
||||
"PIG",
|
||||
"OOZIE_CLIENT"
|
||||
],
|
||||
"auto_security_group" : true
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "vanilla-26",
|
||||
"node_group_templates": {
|
||||
"vanilla-master": 1,
|
||||
"vanilla-worker": 3
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "vanilla-master",
|
||||
"flavor_id": "3",
|
||||
"node_processes": [
|
||||
"namenode",
|
||||
"resourcemanager",
|
||||
"historyserver",
|
||||
"oozie"
|
||||
],
|
||||
"auto_security_group": true
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "vanilla-worker",
|
||||
"flavor_id": "3",
|
||||
"node_processes": [
|
||||
"datanode",
|
||||
"nodemanager"
|
||||
],
|
||||
"auto_security_group": true
|
||||
}
|
18
sahara/tests/scenario/testcase.py.mako
Normal file
18
sahara/tests/scenario/testcase.py.mako
Normal file
@ -0,0 +1,18 @@
|
||||
from sahara.tests.scenario import base
|
||||
|
||||
% for testcase in testcases:
|
||||
${make_testcase(testcase)}
|
||||
% endfor
|
||||
|
||||
<%def name="make_testcase(testcase)">
|
||||
class ${testcase['class_name']}TestCase(base.BaseTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(${testcase['class_name']}TestCase, cls).setUpClass()
|
||||
cls.credentials = ${credentials}
|
||||
cls.network = ${network}
|
||||
cls.testcase = ${testcase}
|
||||
|
||||
def test_plugin(self):
|
||||
self.create_cluster()
|
||||
</%def>
|
24
sahara/tests/scenario/utils.py
Normal file
24
sahara/tests/scenario/utils.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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_utils import uuidutils
|
||||
|
||||
|
||||
def rand_name(name=''):
|
||||
rand_data = uuidutils.generate_uuid()[:8]
|
||||
if name:
|
||||
return '%s-%s' % (name, rand_data)
|
||||
else:
|
||||
return rand_data
|
@ -4,6 +4,7 @@
|
||||
|
||||
hacking>=0.10.0,<0.11
|
||||
|
||||
Mako>=0.4.0
|
||||
MySQL-python
|
||||
bashate>=0.2 # Apache-2.0
|
||||
coverage>=3.6
|
||||
|
4
tox.ini
4
tox.ini
@ -21,6 +21,10 @@ setenv =
|
||||
DISCOVER_DIRECTORY=sahara/tests/integration
|
||||
commands = bash tools/pretty_tox.sh '{posargs}'
|
||||
|
||||
[testenv:scenario]
|
||||
setenv = VIRTUALENV={envdir}
|
||||
commands = python {toxinidir}/sahara/tests/scenario/runner.py "{posargs}"
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user