From f4d388ea508ba00771e43a219ac64e0d430b73bd Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Sun, 25 Jun 2017 21:25:36 -0400 Subject: Migrates Apex to Python Removes all bash libraries and converts almost all of the code to a mixture of Python and Ansible. utils.sh and clean.sh still exist. clean.sh will be migrated fully to clean.py in another patch. The Apex Python package is now built into the opnfv-apex-common RPM. To install locally do 'pip3 install .'. To deploy: opnfv-deploy -d -n --image-dir /root/apex/.build -v --debug Non-python files (THT yaml, settings files, ansible playbooks) are all installed into /usr/share/opnfv-apex/. The RPM will copy settings files into /etc/opnfv-apex/. JIRA: APEX-317 Change-Id: I3232f0329bcd13bce5a28da6a8c9c84d0b048024 Signed-off-by: Tim Rozet --- .gitignore | 7 + apex/__init__.py | 15 + apex/build.py | 238 +++++++++ apex/build/__init__.py | 0 apex/build/build_utils.py | 108 ++++ apex/clean.py | 65 +++ apex/common/__init__.py | 0 apex/common/constants.py | 46 ++ apex/common/exceptions.py | 12 + apex/common/parsers.py | 73 +++ apex/common/utils.py | 107 ++++ apex/deploy.py | 441 ++++++++++++++++ apex/inventory/__init__.py | 0 apex/inventory/inventory.py | 89 ++++ apex/network/__init__.py | 0 apex/network/ip_utils.py | 230 +++++++++ apex/network/jumphost.py | 172 +++++++ apex/network/network_environment.py | 218 ++++++++ apex/overcloud/__init__.py | 0 apex/overcloud/config.py | 76 +++ apex/overcloud/overcloud_deploy.py | 556 +++++++++++++++++++++ apex/settings/__init__.py | 0 apex/settings/deploy_settings.py | 188 +++++++ apex/settings/network_settings.py | 327 ++++++++++++ apex/tests/__init__.py | 0 apex/tests/config/inventory.yaml | 57 +++ apex/tests/constants.py | 12 + apex/tests/playbooks/test_playbook.yaml | 5 + apex/tests/smoke_tests/execute_smoke_tests.sh | 3 + apex/tests/smoke_tests/execute_tests.yml | 11 + apex/tests/smoke_tests/prepare_undercloud.yml | 9 + apex/tests/smoke_tests/smoke_tests.yml | 3 + apex/tests/test_apex_clean.py | 41 ++ apex/tests/test_apex_common_utils.py | 59 +++ apex/tests/test_apex_deploy_settings.py | 101 ++++ apex/tests/test_apex_inventory.py | 69 +++ apex/tests/test_apex_ip_utils.py | 132 +++++ apex/tests/test_apex_network_environment.py | 169 +++++++ apex/tests/test_apex_network_settings.py | 156 ++++++ apex/undercloud/__init__.py | 0 apex/undercloud/undercloud.py | 206 ++++++++ apex/virtual/__init__.py | 0 apex/virtual/configure_vm.py | 206 ++++++++ apex/virtual/virtual_utils.py | 140 ++++++ build/Makefile | 14 +- build/domain.xml | 34 ++ build/rpm_specs/opnfv-apex-common.spec | 99 +--- build/variables.sh | 4 +- ci/build.py | 234 --------- ci/build.sh | 2 +- ci/clean.sh | 118 ++++- ci/deploy.sh | 248 +-------- ci/run_smoke_tests.sh | 2 - ci/util.sh | 84 +++- lib/ansible/playbooks/build_dependencies.yml | 10 +- lib/ansible/playbooks/configure_undercloud.yml | 116 +++++ lib/ansible/playbooks/deploy_dependencies.yml | 66 +++ lib/ansible/playbooks/deploy_overcloud.yml | 68 +++ lib/ansible/playbooks/post_deploy_overcloud.yml | 45 ++ lib/ansible/playbooks/post_deploy_undercloud.yml | 118 +++++ .../playbooks/templates/external_vlan_ifcfg.yml.j2 | 9 + .../templates/virsh_network_default.xml.j2 | 10 + .../playbooks/templates/virsh_network_ovs.xml.j2 | 6 + lib/ansible/playbooks/templates/virsh_pool.xml.j2 | 6 + lib/ansible/playbooks/undercloud_aarch64.yml | 49 ++ lib/common-functions.sh | 308 ------------ lib/configure-deps-functions.sh | 173 ------- lib/configure-vm | 203 -------- lib/installer/domain.xml | 34 -- lib/overcloud-deploy-functions.sh | 503 ------------------- lib/parse-functions.sh | 70 --- lib/post-install-functions.sh | 281 ----------- lib/python/apex/__init__.py | 15 - lib/python/apex/clean.py | 39 -- lib/python/apex/common/__init__.py | 0 lib/python/apex/common/constants.py | 30 -- lib/python/apex/common/utils.py | 31 -- lib/python/apex/deploy_settings.py | 195 -------- lib/python/apex/inventory.py | 98 ---- lib/python/apex/ip_utils.py | 230 --------- lib/python/apex/network_environment.py | 219 -------- lib/python/apex/network_settings.py | 360 ------------- lib/python/apex_python_utils.py | 265 ---------- lib/python/build_utils.py | 108 ---- lib/undercloud-functions.sh | 291 ----------- lib/utility-functions.sh | 85 ---- lib/virtual-setup-functions.sh | 164 ------ requirements.txt | 13 + setup.cfg | 48 ++ setup.py | 19 + test-requirements.txt | 6 + tests/config/inventory.yaml | 57 --- tests/smoke_tests/execute_smoke_tests.sh | 3 - tests/smoke_tests/execute_tests.yml | 11 - tests/smoke_tests/prepare_undercloud.yml | 9 - tests/smoke_tests/smoke_tests.yml | 3 - tests/test_apex_clean.py | 41 -- tests/test_apex_common_utils.py | 39 -- tests/test_apex_deploy_settings.py | 107 ---- tests/test_apex_inventory.py | 81 --- tests/test_apex_ip_utils.py | 135 ----- tests/test_apex_network_environment.py | 170 ------- tests/test_apex_network_settings.py | 160 ------ tests/test_apex_python_utils_py.py | 91 ---- tox.ini | 26 + 105 files changed, 5209 insertions(+), 5201 deletions(-) create mode 100644 apex/__init__.py create mode 100644 apex/build.py create mode 100644 apex/build/__init__.py create mode 100644 apex/build/build_utils.py create mode 100644 apex/clean.py create mode 100644 apex/common/__init__.py create mode 100644 apex/common/constants.py create mode 100644 apex/common/exceptions.py create mode 100644 apex/common/parsers.py create mode 100644 apex/common/utils.py create mode 100644 apex/deploy.py create mode 100644 apex/inventory/__init__.py create mode 100644 apex/inventory/inventory.py create mode 100644 apex/network/__init__.py create mode 100644 apex/network/ip_utils.py create mode 100644 apex/network/jumphost.py create mode 100644 apex/network/network_environment.py create mode 100644 apex/overcloud/__init__.py create mode 100644 apex/overcloud/config.py create mode 100644 apex/overcloud/overcloud_deploy.py create mode 100644 apex/settings/__init__.py create mode 100644 apex/settings/deploy_settings.py create mode 100644 apex/settings/network_settings.py create mode 100644 apex/tests/__init__.py create mode 100644 apex/tests/config/inventory.yaml create mode 100644 apex/tests/constants.py create mode 100644 apex/tests/playbooks/test_playbook.yaml create mode 100755 apex/tests/smoke_tests/execute_smoke_tests.sh create mode 100644 apex/tests/smoke_tests/execute_tests.yml create mode 100644 apex/tests/smoke_tests/prepare_undercloud.yml create mode 100644 apex/tests/smoke_tests/smoke_tests.yml create mode 100644 apex/tests/test_apex_clean.py create mode 100644 apex/tests/test_apex_common_utils.py create mode 100644 apex/tests/test_apex_deploy_settings.py create mode 100644 apex/tests/test_apex_inventory.py create mode 100644 apex/tests/test_apex_ip_utils.py create mode 100644 apex/tests/test_apex_network_environment.py create mode 100644 apex/tests/test_apex_network_settings.py create mode 100644 apex/undercloud/__init__.py create mode 100644 apex/undercloud/undercloud.py create mode 100644 apex/virtual/__init__.py create mode 100755 apex/virtual/configure_vm.py create mode 100644 apex/virtual/virtual_utils.py create mode 100644 build/domain.xml delete mode 100644 ci/build.py create mode 100644 lib/ansible/playbooks/configure_undercloud.yml create mode 100644 lib/ansible/playbooks/deploy_dependencies.yml create mode 100644 lib/ansible/playbooks/deploy_overcloud.yml create mode 100644 lib/ansible/playbooks/post_deploy_overcloud.yml create mode 100644 lib/ansible/playbooks/post_deploy_undercloud.yml create mode 100644 lib/ansible/playbooks/templates/external_vlan_ifcfg.yml.j2 create mode 100644 lib/ansible/playbooks/templates/virsh_network_default.xml.j2 create mode 100644 lib/ansible/playbooks/templates/virsh_network_ovs.xml.j2 create mode 100644 lib/ansible/playbooks/templates/virsh_pool.xml.j2 create mode 100644 lib/ansible/playbooks/undercloud_aarch64.yml delete mode 100644 lib/common-functions.sh delete mode 100755 lib/configure-deps-functions.sh delete mode 100755 lib/configure-vm delete mode 100644 lib/installer/domain.xml delete mode 100755 lib/overcloud-deploy-functions.sh delete mode 100755 lib/parse-functions.sh delete mode 100755 lib/post-install-functions.sh delete mode 100644 lib/python/apex/__init__.py delete mode 100644 lib/python/apex/clean.py delete mode 100644 lib/python/apex/common/__init__.py delete mode 100644 lib/python/apex/common/constants.py delete mode 100644 lib/python/apex/common/utils.py delete mode 100644 lib/python/apex/deploy_settings.py delete mode 100644 lib/python/apex/inventory.py delete mode 100644 lib/python/apex/ip_utils.py delete mode 100644 lib/python/apex/network_environment.py delete mode 100644 lib/python/apex/network_settings.py delete mode 100755 lib/python/apex_python_utils.py delete mode 100644 lib/python/build_utils.py delete mode 100755 lib/undercloud-functions.sh delete mode 100644 lib/utility-functions.sh delete mode 100755 lib/virtual-setup-functions.sh create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 test-requirements.txt delete mode 100644 tests/config/inventory.yaml delete mode 100755 tests/smoke_tests/execute_smoke_tests.sh delete mode 100644 tests/smoke_tests/execute_tests.yml delete mode 100644 tests/smoke_tests/prepare_undercloud.yml delete mode 100644 tests/smoke_tests/smoke_tests.yml delete mode 100644 tests/test_apex_clean.py delete mode 100644 tests/test_apex_common_utils.py delete mode 100644 tests/test_apex_deploy_settings.py delete mode 100644 tests/test_apex_inventory.py delete mode 100644 tests/test_apex_ip_utils.py delete mode 100644 tests/test_apex_network_environment.py delete mode 100644 tests/test_apex_network_settings.py delete mode 100644 tests/test_apex_python_utils_py.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 511a0de1..47eaef64 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,10 @@ .build/ .cache/ ci/apex_build.log +ci/apex_deploy.log +.tox/ +apex.egg-info/ +/apex/tests/playbooks/*.retry +coverage.xml +nosetests.xml +ci/apex_clean.log diff --git a/apex/__init__.py b/apex/__init__.py new file mode 100644 index 00000000..4db820d9 --- /dev/null +++ b/apex/__init__.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from apex.network.network_environment import NetworkEnvironment +from apex.settings.deploy_settings import DeploySettings +from apex.settings.network_settings import NetworkSettings +from .clean import clean_nodes +from .inventory.inventory import Inventory diff --git a/apex/build.py b/apex/build.py new file mode 100644 index 00000000..cda4e061 --- /dev/null +++ b/apex/build.py @@ -0,0 +1,238 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import argparse +import logging +import os +import subprocess +import sys +import uuid +import yaml + +CACHE_JOURNAL = 'cache_journal.yaml' +TMP_CACHE = '.cache' +BUILD_ROOT = 'build' +BUILD_LOG_FILE = './apex_build.log' + + +class ApexBuildException(Exception): + pass + + +def create_build_parser(): + build_parser = argparse.ArgumentParser() + build_parser.add_argument('--debug', action='store_true', default=False, + help="Turn on debug messages") + build_parser.add_argument('-l', '--log-file', + default=BUILD_LOG_FILE, + dest='log_file', help="Log file to log to") + build_parser.add_argument('-c', '--cache-dir', + dest='cache_dir', + default=None, + help='Directory to store cache') + build_parser.add_argument('--iso', action='store_true', + default=False, + help='Build ISO image') + build_parser.add_argument('--rpms', action='store_true', + default=False, + help='Build RPMs') + build_parser.add_argument('-r', '--release', + dest='build_version', + help='Version to apply to build ' + 'artifact label') + + return build_parser + + +def get_journal(cache_dir): + """ + Search for the journal file and returns its contents + :param cache_dir: cache storage directory where journal file is + :return: content of journal file + """ + journal_file = "{}/{}".format(cache_dir, CACHE_JOURNAL) + if os.path.isfile(journal_file) is False: + logging.info("Journal file not found {}, skipping cache search".format( + journal_file)) + else: + with open(journal_file, 'r') as fh: + cache_journal = yaml.safe_load(fh) + assert isinstance(cache_journal, list) + return cache_journal + + +def get_cache_file(cache_dir): + """ + Searches for a valid cache entry in the cache journal + :param cache_dir: directory where cache and journal are located + :return: name of valid cache file + """ + cache_journal = get_journal(cache_dir) + if cache_journal is not None: + valid_cache = cache_journal[-1] + if os.path.isfile(valid_cache): + return valid_cache + + +def unpack_cache(cache_dest, cache_dir=None): + if cache_dir is None: + logging.info("Cache directory not provided, skipping cache unpack") + return + elif os.path.isdir(cache_dir) is False: + logging.info("Cache Directory does not exist, skipping cache unpack") + return + else: + logging.info("Cache Directory Found: {}".format(cache_dir)) + cache_file = get_cache_file(cache_dir) + if cache_file is None: + logging.info("No cache file detected, skipping cache unpack") + return + logging.info("Unpacking Cache {}".format(cache_file)) + if not os.path.exists(cache_dest): + os.makedirs(cache_dest) + try: + subprocess.check_call(["tar", "xvf", cache_file, "-C", cache_dest]) + except subprocess.CalledProcessError: + logging.warning("Cache unpack failed") + return + logging.info("Cache unpacked, contents are: {}", + os.listdir(cache_dest)) + + +def build(build_root, version, iso=False, rpms=False): + if iso: + make_targets = ['iso'] + elif rpms: + make_targets = ['rpms'] + else: + make_targets = ['images', 'rpms-check'] + if version is not None: + make_args = ['RELEASE={}'.format(version)] + else: + make_args = [] + logging.info('Building targets: {}'.format(make_targets)) + try: + output = subprocess.check_output(["make"] + make_args + ["-C", + build_root] + make_targets) + logging.info(output) + except subprocess.CalledProcessError as e: + logging.error("Failed to build Apex artifacts") + logging.error(e.output) + raise e + + +def build_cache(cache_source, cache_dir): + """ + Tar up new cache with unique name and store it in cache storage + directory. Also update journal file with new cache entry. + :param cache_source: source files to tar up when building cache file + :param cache_dir: cache storage location + :return: None + """ + if cache_dir is None: + logging.info("No cache dir specified, will not build cache") + return + cache_name = 'apex-cache-{}.tgz'.format(str(uuid.uuid4())) + cache_full_path = os.path.join(cache_dir, cache_name) + os.makedirs(cache_dir, exist_ok=True) + try: + subprocess.check_call(['tar', '--atime-preserve', '--dereference', + '-caf', cache_full_path, '-C', cache_source, + '.']) + except BaseException as e: + logging.error("Unable to build new cache tarball") + if os.path.isfile(cache_full_path): + os.remove(cache_full_path) + raise e + if os.path.isfile(cache_full_path): + logging.info("Cache Build Complete") + # update journal + cache_entries = get_journal(cache_dir) + if cache_entries is None: + cache_entries = [cache_name] + else: + cache_entries.append(cache_name) + journal_file = os.path.join(cache_dir, CACHE_JOURNAL) + with open(journal_file, 'w') as fh: + yaml.safe_dump(cache_entries, fh, default_flow_style=False) + logging.info("Journal updated with new entry: {}".format(cache_name)) + else: + logging.warning("Cache file did not build correctly") + + +def prune_cache(cache_dir): + """ + Remove older cache entries if there are more than 2 + :param cache_dir: Cache storage directory + :return: None + """ + if cache_dir is None: + return + cache_modified_flag = False + cache_entries = get_journal(cache_dir) + while len(cache_entries) > 2: + logging.debug("Will remove older cache entries") + cache_to_rm = cache_entries[0] + cache_full_path = os.path.join(cache_dir, cache_to_rm) + if os.path.isfile(cache_full_path): + try: + os.remove(cache_full_path) + cache_entries.pop(0) + cache_modified_flag = True + except os.EX_OSERR: + logging.warning("Failed to remove cache file: {}".format( + cache_full_path)) + break + + else: + logging.debug("No more cache cleanup necessary") + + if cache_modified_flag: + logging.debug("Updating cache journal") + journal_file = os.path.join(cache_dir, CACHE_JOURNAL) + with open(journal_file, 'w') as fh: + yaml.safe_dump(cache_entries, fh, default_flow_style=False) + +if __name__ == '__main__': + parser = create_build_parser() + args = parser.parse_args(sys.argv[1:]) + if args.debug: + log_level = logging.DEBUG + else: + log_level = logging.INFO + os.makedirs(os.path.dirname(args.log_file), exist_ok=True) + formatter = '%(asctime)s %(levelname)s: %(message)s' + logging.basicConfig(filename=args.log_file, + format=formatter, + datefmt='%m/%d/%Y %I:%M:%S %p', + level=log_level) + console = logging.StreamHandler() + console.setLevel(log_level) + console.setFormatter(logging.Formatter(formatter)) + logging.getLogger('').addHandler(console) + apex_root = os.path.split(os.getcwd())[0] + if 'apex/apex' in apex_root: + apex_root = os.path.split(apex_root)[0] + for root, dirs, files in os.walk(apex_root): + if BUILD_ROOT in dirs and 'apex/apex' not in root: + apex_root = root + break + apex_build_root = os.path.join(apex_root, BUILD_ROOT) + if os.path.isdir(apex_build_root): + cache_tmp_dir = os.path.join(apex_root, TMP_CACHE) + else: + logging.error("You must execute this script inside of the Apex " + "local code repository") + raise ApexBuildException("Invalid path for apex root: {}. Must be " + "invoked from within Apex code directory.". + format(apex_root)) + unpack_cache(cache_tmp_dir, args.cache_dir) + build(apex_build_root, args.build_version, args.iso, args.rpms) + build_cache(cache_tmp_dir, args.cache_dir) + prune_cache(args.cache_dir) diff --git a/apex/build/__init__.py b/apex/build/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apex/build/build_utils.py b/apex/build/build_utils.py new file mode 100644 index 00000000..14327a90 --- /dev/null +++ b/apex/build/build_utils.py @@ -0,0 +1,108 @@ +############################################################################## +# Copyright (c) 2017 Feng Pan (fpan@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import argparse +import git +import logging +import os +from pygerrit2.rest import GerritRestAPI +import re +import shutil +import sys + + +def clone_fork(args): + ref = None + logging.info("Cloning {}".format(args.repo)) + + try: + cm = git.Repo(search_parent_directories=True).commit().message + except git.exc.InvalidGitRepositoryError: + logging.debug('Current Apex directory is not a git repo: {}' + .format(os.getcwd())) + cm = '' + + logging.info("Current commit message: {}".format(cm)) + m = re.search('{}:\s*(\S+)'.format(args.repo), cm) + + if m: + change_id = m.group(1) + logging.info("Using change ID {} from {}".format(change_id, args.repo)) + rest = GerritRestAPI(url=args.url) + change_str = "changes/{}?o=CURRENT_REVISION".format(change_id) + change = rest.get(change_str) + try: + assert change['status'] not in 'ABANDONED' 'CLOSED',\ + 'Change {} is in {} state'.format(change_id, change['status']) + if change['status'] == 'MERGED': + logging.info('Change {} is merged, ignoring...' + .format(change_id)) + else: + current_revision = change['current_revision'] + ref = change['revisions'][current_revision]['ref'] + logging.info('setting ref to {}'.format(ref)) + except KeyError: + logging.error('Failed to get valid change data structure from url ' + '{}/{}, data returned: \n{}' + .format(change_id, change_str, change)) + raise + + # remove existing file or directory named repo + if os.path.exists(args.repo): + if os.path.isdir(args.repo): + shutil.rmtree(args.repo) + else: + os.remove(args.repo) + + ws = git.Repo.clone_from("{}/{}".format(args.url, args.repo), + args.repo, b=args.branch) + if ref: + git_cmd = ws.git + git_cmd.fetch("{}/{}".format(args.url, args.repo), ref) + git_cmd.checkout('FETCH_HEAD') + logging.info('Checked out commit:\n{}'.format(ws.head.commit.message)) + + +def get_parser(): + parser = argparse.ArgumentParser() + parser.add_argument('--debug', action='store_true', default=False, + help="Turn on debug messages") + subparsers = parser.add_subparsers() + fork = subparsers.add_parser('clone-fork', + help='Clone fork of dependent repo') + fork.add_argument('-r', '--repo', required=True, help='Name of repository') + fork.add_argument('-u', '--url', + default='https://gerrit.opnfv.org/gerrit', + help='Gerrit URL of repository') + fork.add_argument('-b', '--branch', + default='master', + help='Branch to checkout') + fork.set_defaults(func=clone_fork) + return parser + + +def main(): + parser = get_parser() + args = parser.parse_args(sys.argv[1:]) + if args.debug: + logging_level = logging.DEBUG + else: + logging_level = logging.INFO + + logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging_level) + if hasattr(args, 'func'): + args.func(args) + else: + parser.print_help() + exit(1) + +if __name__ == "__main__": + main() diff --git a/apex/clean.py b/apex/clean.py new file mode 100644 index 00000000..af9e8ce0 --- /dev/null +++ b/apex/clean.py @@ -0,0 +1,65 @@ +############################################################################## +# Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# Clean will eventually be migrated to this file + +import argparse +import logging +import os +import pyipmi +import pyipmi.interfaces +import sys + +from .common import utils + + +def clean_nodes(inventory): + inv_dict = utils.parse_yaml(inventory) + if inv_dict is None or 'nodes' not in inv_dict: + logging.error("Inventory file is empty or missing nodes definition") + sys.exit(1) + for node, node_info in inv_dict['nodes'].items(): + logging.info("Cleaning node: {}".format(node)) + try: + interface = pyipmi.interfaces.create_interface( + 'ipmitool', interface_type='lanplus') + connection = pyipmi.create_connection(interface) + connection.session.set_session_type_rmcp(node_info['ipmi_ip']) + connection.target = pyipmi.Target(0x20) + connection.session.set_auth_type_user(node_info['ipmi_user'], + node_info['ipmi_pass']) + connection.session.establish() + connection.chassis_control_power_down() + except Exception as e: + logging.error("Failure while shutting down node {}".format(e)) + sys.exit(1) + + +def main(): + clean_parser = argparse.ArgumentParser() + clean_parser.add_argument('-f', + dest='inv_file', + required=True, + help='File which contains inventory') + args = clean_parser.parse_args(sys.argv[1:]) + os.makedirs(os.path.dirname('./apex_clean.log'), exist_ok=True) + formatter = '%(asctime)s %(levelname)s: %(message)s' + logging.basicConfig(filename='./apex_clean.log', + format=formatter, + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging.DEBUG) + console = logging.StreamHandler() + console.setLevel(logging.DEBUG) + console.setFormatter(logging.Formatter(formatter)) + logging.getLogger('').addHandler(console) + clean_nodes(args.inv_file) + + +if __name__ == '__main__': + main() diff --git a/apex/common/__init__.py b/apex/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apex/common/constants.py b/apex/common/constants.py new file mode 100644 index 00000000..0df71526 --- /dev/null +++ b/apex/common/constants.py @@ -0,0 +1,46 @@ +############################################################################## +# Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import os + +ADMIN_NETWORK = 'admin' +TENANT_NETWORK = 'tenant' +EXTERNAL_NETWORK = 'external' +STORAGE_NETWORK = 'storage' +API_NETWORK = 'api' +CONTROLLER = 'controller' +COMPUTE = 'compute' + +OPNFV_NETWORK_TYPES = [ADMIN_NETWORK, TENANT_NETWORK, EXTERNAL_NETWORK, + STORAGE_NETWORK, API_NETWORK] +DNS_SERVERS = ["8.8.8.8", "8.8.4.4"] +NTP_SERVER = ["pool.ntp.org"] +COMPUTE = 'compute' +CONTROLLER = 'controller' +ROLES = [COMPUTE, CONTROLLER] +DOMAIN_NAME = 'localdomain.com' +COMPUTE_PRE = "OS::TripleO::ComputeExtraConfigPre" +CONTROLLER_PRE = "OS::TripleO::ControllerExtraConfigPre" +PRE_CONFIG_DIR = "/usr/share/openstack-tripleo-heat-templates/puppet/" \ + "extraconfig/pre_deploy/" +DEFAULT_ROOT_DEV = 'sda' +LIBVIRT_VOLUME_PATH = '/var/lib/libvirt/images' + +VIRT_UPLOAD = '--upload' +VIRT_INSTALL = '-install' +VIRT_RUN_CMD = '--run-command' +VIRT_PW = '--root-password' + +THT_DIR = '/usr/share/openstack-tripleo-heat-templates' +THT_ENV_DIR = os.path.join(THT_DIR, 'environments') + +DEFAULT_ODL_VERSION = 'carbon' +DEBUG_OVERCLOUD_PW = 'opnfvapex' +NET_ENV_FILE = 'network-environment.yaml' +DEPLOY_TIMEOUT = 90 diff --git a/apex/common/exceptions.py b/apex/common/exceptions.py new file mode 100644 index 00000000..c660213f --- /dev/null +++ b/apex/common/exceptions.py @@ -0,0 +1,12 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +class ApexDeployException(Exception): + pass diff --git a/apex/common/parsers.py b/apex/common/parsers.py new file mode 100644 index 00000000..8744c862 --- /dev/null +++ b/apex/common/parsers.py @@ -0,0 +1,73 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import json +import logging +import pprint +import os +import re + +from apex.common.exceptions import ApexDeployException + +"""Parser functions for overcloud/openstack output""" + + +def parse_nova_output(in_file): + """ + Parses nova list output into a dictionary format for node name and ip + :param in_file: json format from openstack server list + :return: dictionary format for {"node name": "node ip"} + """ + if not os.path.isfile(in_file): + raise FileNotFoundError(in_file) + node_dict = dict() + with open(in_file, 'r') as fh: + nova_list = json.load(fh) + + for server in nova_list: + ip_match = re.search('([0-9]+\.){3}[0-9]+', server['Networks']) + if ip_match is None: + logging.error("Unable to find IP in nova output " + "{}".format(pprint.pformat(server, indent=4))) + raise ApexDeployException("Unable to parse IP from nova output") + else: + node_dict[server['Name']] = ip_match.group(0) + + if not node_dict: + raise ApexDeployException("No overcloud nodes found in: {}".format( + in_file)) + return node_dict + + +def parse_overcloudrc(in_file): + """ + Parses overcloudrc into a dictionary format for key and value + :param in_file: + :return: dictionary format for {"variable": "value"} + """ + logging.debug("Parsing overcloudrc file {}".format(in_file)) + if not os.path.isfile(in_file): + raise FileNotFoundError(in_file) + creds = {} + with open(in_file, 'r') as fh: + lines = fh.readlines() + kv_pattern = re.compile('^export\s+([^\s]+)=([^\s]+)$') + for line in lines: + if 'export' not in line: + continue + else: + res = re.search(kv_pattern, line.strip()) + if res: + creds[res.group(1)] = res.group(2) + logging.debug("os cred found: {}, {}".format(res.group(1), + res.group(2))) + else: + logging.debug("os cred not found in: {}".format(line)) + + return creds diff --git a/apex/common/utils.py b/apex/common/utils.py new file mode 100644 index 00000000..848f2644 --- /dev/null +++ b/apex/common/utils.py @@ -0,0 +1,107 @@ +############################################################################## +# Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import json +import logging +import os +import pprint +import subprocess +import yaml + + +def str2bool(var): + if isinstance(var, bool): + return var + else: + return var.lower() in ("true", "yes") + + +def parse_yaml(yaml_file): + with open(yaml_file) as f: + parsed_dict = yaml.safe_load(f) + return parsed_dict + + +def dump_yaml(data, file): + """ + Dumps data to a file as yaml + :param data: yaml to be written to file + :param file: filename to write to + :return: + """ + logging.debug("Writing file {} with " + "yaml data:\n{}".format(file, yaml.safe_dump(data))) + with open(file, "w") as fh: + yaml.safe_dump(data, fh, default_flow_style=False) + + +def dict_objects_to_str(dictionary): + if isinstance(dictionary, list): + tmp_list = [] + for element in dictionary: + if isinstance(element, dict): + tmp_list.append(dict_objects_to_str(element)) + else: + tmp_list.append(str(element)) + return tmp_list + elif not isinstance(dictionary, dict): + if not isinstance(dictionary, bool): + return str(dictionary) + else: + return dictionary + return dict((k, dict_objects_to_str(v)) for + k, v in dictionary.items()) + + +def run_ansible(ansible_vars, playbook, host='localhost', user='root', + tmp_dir=None, dry_run=False): + """ + Executes ansible playbook and checks for errors + :param ansible_vars: dictionary of variables to inject into ansible run + :param playbook: playbook to execute + :param tmp_dir: temp directory to store ansible command + :param dry_run: Do not actually apply changes + :return: None + """ + logging.info("Executing ansible playbook: {}".format(playbook)) + inv_host = "{},".format(host) + if host == 'localhost': + conn_type = 'local' + else: + conn_type = 'smart' + ansible_command = ['ansible-playbook', '--become', '-i', inv_host, + '-u', user, '-c', conn_type, playbook, '-vvv'] + if dry_run: + ansible_command.append('--check') + + if isinstance(ansible_vars, dict) and ansible_vars: + logging.debug("Ansible variables to be set:\n{}".format( + pprint.pformat(ansible_vars))) + ansible_command.append('--extra-vars') + ansible_command.append(json.dumps(ansible_vars)) + if tmp_dir: + ansible_tmp = os.path.join(tmp_dir, + os.path.basename(playbook) + '.rerun') + # FIXME(trozet): extra vars are printed without single quotes + # so a dev has to add them manually to the command to rerun + # the playbook. Need to test if we can just add the single quotes + # to the json dumps to the ansible command and see if that works + with open(ansible_tmp, 'w') as fh: + fh.write("ANSIBLE_HOST_KEY_CHECKING=FALSE {}".format( + ' '.join(ansible_command))) + try: + my_env = os.environ.copy() + my_env['ANSIBLE_HOST_KEY_CHECKING'] = 'False' + logging.info("Executing playbook...this may take some time") + logging.debug(subprocess.check_output(ansible_command, env=my_env, + stderr=subprocess.STDOUT).decode('utf-8')) + except subprocess.CalledProcessError as e: + logging.error("Error executing ansible: {}".format( + pprint.pformat(e.output.decode('utf-8')))) + raise diff --git a/apex/deploy.py b/apex/deploy.py new file mode 100644 index 00000000..76708e96 --- /dev/null +++ b/apex/deploy.py @@ -0,0 +1,441 @@ +#!/usr/bin/env python + +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import argparse +import json +import logging +import os +import pprint +import shutil +import sys +import tempfile + +import apex.virtual.configure_vm as vm_lib +import apex.virtual.virtual_utils as virt_utils +from apex import DeploySettings +from apex import Inventory +from apex import NetworkEnvironment +from apex import NetworkSettings +from apex.common import utils +from apex.common import constants +from apex.common import parsers +from apex.common.exceptions import ApexDeployException +from apex.network import jumphost +from apex.undercloud import undercloud as uc_lib +from apex.overcloud import config as oc_cfg +from apex.overcloud import overcloud_deploy + +APEX_TEMP_DIR = tempfile.mkdtemp() +ANSIBLE_PATH = 'ansible/playbooks' +SDN_IMAGE = 'overcloud-full-opendaylight.qcow2' + + +def deploy_quickstart(args, deploy_settings_file, network_settings_file, + inventory_file=None): + pass + + +def validate_cross_settings(deploy_settings, net_settings, inventory): + """ + Used to validate compatibility across settings file. + :param deploy_settings: parsed settings for deployment + :param net_settings: parsed settings for network + :param inventory: parsed inventory file + :return: None + """ + + if deploy_settings['deploy_options']['dataplane'] != 'ovs' and 'tenant' \ + not in net_settings.enabled_network_list: + raise ApexDeployException("Setting a DPDK based dataplane requires" + "a dedicated NIC for tenant network") + + # TODO(trozet): add more checks here like RAM for ODL, etc + # check if odl_vpp_netvirt is true and vpp is set + # Check if fdio and nosdn: + # tenant_nic_mapping_controller_members" == + # "$tenant_nic_mapping_compute_members + + +def build_vms(inventory, network_settings): + """ + Creates VMs and configures vbmc and host + :param inventory: + :param network_settings: + :return: + """ + + for idx, node in enumerate(inventory['nodes']): + name = 'baremetal{}'.format(idx) + volume = name + ".qcow2" + volume_path = os.path.join(constants.LIBVIRT_VOLUME_PATH, volume) + # TODO(trozet): add back aarch64 + # TODO(trozet): add error checking + vm_lib.create_vm( + name, volume_path, + baremetal_interfaces=network_settings.enabled_network_list, + memory=node['memory'], cpus=node['cpu'], + macs=[node['mac_address']]) + virt_utils.host_setup({name: node['pm_port']}) + + +def create_deploy_parser(): + deploy_parser = argparse.ArgumentParser() + deploy_parser.add_argument('--debug', action='store_true', default=False, + help="Turn on debug messages") + deploy_parser.add_argument('-l', '--log-file', + default='./apex_deploy.log', + dest='log_file', help="Log file to log to") + deploy_parser.add_argument('-d', '--deploy-settings', + dest='deploy_settings_file', + required=True, + help='File which contains Apex deploy settings') + deploy_parser.add_argument('-n', '--network-settings', + dest='network_settings_file', + required=True, + help='File which contains Apex network ' + 'settings') + deploy_parser.add_argument('-i', '--inventory-file', + dest='inventory_file', + default=None, + help='Inventory file which contains POD ' + 'definition') + deploy_parser.add_argument('-e', '--environment-file', + dest='env_file', + default='opnfv-environment.yaml', + help='Provide alternate base env file') + deploy_parser.add_argument('-v', '--virtual', action='store_true', + default=False, + dest='virtual', + help='Enable virtual deployment') + deploy_parser.add_argument('--interactive', action='store_true', + default=False, + help='Enable interactive deployment mode which ' + 'requires user to confirm steps of ' + 'deployment') + deploy_parser.add_argument('--virtual-computes', + dest='virt_compute_nodes', + default=1, + help='Number of Virtual Compute nodes to create' + ' and use during deployment (defaults to 1' + ' for noha and 2 for ha)') + deploy_parser.add_argument('--virtual-cpus', + dest='virt_cpus', + default=4, + help='Number of CPUs to use per Overcloud VM in' + ' a virtual deployment (defaults to 4)') + deploy_parser.add_argument('--virtual-default-ram', + dest='virt_default_ram', + default=8, + help='Amount of default RAM to use per ' + 'Overcloud VM in GB (defaults to 8).') + deploy_parser.add_argument('--virtual-compute-ram', + dest='virt_compute_ram', + default=None, + help='Amount of RAM to use per Overcloud ' + 'Compute VM in GB (defaults to 8). ' + 'Overrides --virtual-default-ram arg for ' + 'computes') + deploy_parser.add_argument('--deploy-dir', + default='/usr/share/opnfv-apex', + help='Directory to deploy from which contains ' + 'base config files for deployment') + deploy_parser.add_argument('--image-dir', + default='/var/opt/opnfv/images', + help='Directory which contains ' + 'base disk images for deployment') + deploy_parser.add_argument('--lib-dir', + default='/usr/share/opnfv-apex', + help='Directory path for apex ansible ' + 'and third party libs') + deploy_parser.add_argument('--quickstart', action='store_true', + default=False, + help='Use tripleo-quickstart to deploy') + return deploy_parser + + +def validate_deploy_args(args): + """ + Validates arguments for deploy + :param args: + :return: None + """ + + logging.debug('Validating arguments for deployment') + if args.virtual and args.inventory_file is not None: + logging.error("Virtual enabled but inventory file also given") + raise ApexDeployException('You should not specify an inventory file ' + 'with virtual deployments') + elif args.virtual: + args.inventory_file = os.path.join(APEX_TEMP_DIR, + 'inventory-virt.yaml') + elif os.path.isfile(args.inventory_file) is False: + logging.error("Specified inventory file does not exist: {}".format( + args.inventory_file)) + raise ApexDeployException('Specified inventory file does not exist') + + for settings_file in (args.deploy_settings_file, + args.network_settings_file): + if os.path.isfile(settings_file) is False: + logging.error("Specified settings file does not " + "exist: {}".format(settings_file)) + raise ApexDeployException('Specified settings file does not ' + 'exist: {}'.format(settings_file)) + + +def main(): + parser = create_deploy_parser() + args = parser.parse_args(sys.argv[1:]) + # FIXME (trozet): this is only needed as a workaround for CI. Remove + # when CI is changed + if os.getenv('IMAGES', False): + args.image_dir = os.getenv('IMAGES') + if args.debug: + log_level = logging.DEBUG + else: + log_level = logging.INFO + os.makedirs(os.path.dirname(args.log_file), exist_ok=True) + formatter = '%(asctime)s %(levelname)s: %(message)s' + logging.basicConfig(filename=args.log_file, + format=formatter, + datefmt='%m/%d/%Y %I:%M:%S %p', + level=log_level) + console = logging.StreamHandler() + console.setLevel(log_level) + console.setFormatter(logging.Formatter(formatter)) + logging.getLogger('').addHandler(console) + validate_deploy_args(args) + # Parse all settings + deploy_settings = DeploySettings(args.deploy_settings_file) + logging.info("Deploy settings are:\n {}".format(pprint.pformat( + deploy_settings))) + net_settings = NetworkSettings(args.network_settings_file) + logging.info("Network settings are:\n {}".format(pprint.pformat( + net_settings))) + net_env_file = os.path.join(args.deploy_dir, constants.NET_ENV_FILE) + net_env = NetworkEnvironment(net_settings, net_env_file) + net_env_target = os.path.join(APEX_TEMP_DIR, constants.NET_ENV_FILE) + utils.dump_yaml(dict(net_env), net_env_target) + ha_enabled = deploy_settings['global_params']['ha_enabled'] + if args.virtual: + if args.virt_compute_ram is None: + compute_ram = args.virt_default_ram + else: + compute_ram = args.virt_compute_ram + if deploy_settings['deploy_options']['sdn_controller'] == \ + 'opendaylight' and args.virt_default_ram < 12: + control_ram = 12 + logging.warning('RAM per controller is too low. OpenDaylight ' + 'requires at least 12GB per controller.') + logging.info('Increasing RAM per controller to 12GB') + elif args.virt_default_ram < 10: + control_ram = 10 + logging.warning('RAM per controller is too low. nosdn ' + 'requires at least 10GB per controller.') + logging.info('Increasing RAM per controller to 10GB') + else: + control_ram = args.virt_default_ram + if ha_enabled and args.virt_compute_nodes < 2: + logging.debug('HA enabled, bumping number of compute nodes to 2') + args.virt_compute_nodes = 2 + virt_utils.generate_inventory(args.inventory_file, ha_enabled, + num_computes=args.virt_compute_nodes, + controller_ram=control_ram * 1024, + compute_ram=compute_ram * 1024, + vcpus=args.virt_cpus + ) + inventory = Inventory(args.inventory_file, ha_enabled, args.virtual) + + validate_cross_settings(deploy_settings, net_settings, inventory) + + if args.quickstart: + deploy_settings_file = os.path.join(APEX_TEMP_DIR, + 'apex_deploy_settings.yaml') + utils.dump_yaml(utils.dict_objects_to_str(deploy_settings), + deploy_settings_file) + logging.info("File created: {}".format(deploy_settings_file)) + network_settings_file = os.path.join(APEX_TEMP_DIR, + 'apex_network_settings.yaml') + utils.dump_yaml(utils.dict_objects_to_str(net_settings), + network_settings_file) + logging.info("File created: {}".format(network_settings_file)) + deploy_quickstart(args, deploy_settings_file, network_settings_file, + args.inventory_file) + else: + # TODO (trozet): add logic back from: + # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR) + ansible_args = { + 'virsh_enabled_networks': net_settings.enabled_network_list + } + ansible_path = os.path.join(args.lib_dir, ANSIBLE_PATH) + utils.run_ansible(ansible_args, + os.path.join(args.lib_dir, + ansible_path, + 'deploy_dependencies.yml')) + uc_external = False + if 'external' in net_settings.enabled_network_list: + uc_external = True + if args.virtual: + # create all overcloud VMs + build_vms(inventory, net_settings) + else: + # Attach interfaces to jumphost for baremetal deployment + jump_networks = ['admin'] + if uc_external: + jump_networks.append('external') + for network in jump_networks: + iface = net_settings['network'][network]['installer_vm'][ + 'members'](0) + bridge = "br-{}".format(network) + jumphost.attach_interface_to_ovs(bridge, iface, network) + # Dump all settings out to temp bash files to be sourced + instackenv_json = os.path.join(APEX_TEMP_DIR, 'instackenv.json') + with open(instackenv_json, 'w') as fh: + json.dump(inventory, fh) + + # Create and configure undercloud + if args.debug: + root_pw = constants.DEBUG_OVERCLOUD_PW + else: + root_pw = None + undercloud = uc_lib.Undercloud(args.image_dir, + root_pw=root_pw, + external_network=uc_external) + undercloud.start() + + # Generate nic templates + for role in 'compute', 'controller': + oc_cfg.create_nic_template(net_settings, deploy_settings, role, + args.deploy_dir, APEX_TEMP_DIR) + # Install Undercloud + undercloud.configure(net_settings, + os.path.join(args.lib_dir, + ansible_path, + 'configure_undercloud.yml'), + APEX_TEMP_DIR) + + # Prepare overcloud-full.qcow2 + logging.info("Preparing Overcloud for deployment...") + sdn_image = os.path.join(args.image_dir, SDN_IMAGE) + overcloud_deploy.prep_image(deploy_settings, sdn_image, APEX_TEMP_DIR, + root_pw=root_pw) + opnfv_env = os.path.join(args.deploy_dir, args.env_file) + overcloud_deploy.prep_env(deploy_settings, net_settings, opnfv_env, + net_env_target, APEX_TEMP_DIR) + overcloud_deploy.create_deploy_cmd(deploy_settings, net_settings, + inventory, APEX_TEMP_DIR, + args.virtual, args.env_file) + deploy_playbook = os.path.join(args.lib_dir, ansible_path, + 'deploy_overcloud.yml') + virt_env = 'virtual-environment.yaml' + bm_env = 'baremetal-environment.yaml' + for p_env in virt_env, bm_env: + shutil.copyfile(os.path.join(args.deploy_dir, p_env), + os.path.join(APEX_TEMP_DIR, p_env)) + + # Start Overcloud Deployment + logging.info("Executing Overcloud Deployment...") + deploy_vars = dict() + deploy_vars['virtual'] = args.virtual + deploy_vars['debug'] = args.debug + deploy_vars['dns_server_args'] = '' + deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR + deploy_vars['stackrc'] = 'source /home/stack/stackrc' + deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc' + for dns_server in net_settings['dns_servers']: + deploy_vars['dns_server_args'] += " --dns-nameserver {}".format( + dns_server) + try: + utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip, + user='stack', tmp_dir=APEX_TEMP_DIR) + logging.info("Overcloud deployment complete") + os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2')) + except Exception: + logging.error("Deployment Failed. Please check log") + raise + + # Post install + logging.info("Executing post deploy configuration") + jumphost.configure_bridges(net_settings) + nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output') + deploy_vars['overcloud_nodes'] = parsers.parse_nova_output( + nova_output) + deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \ + 'GlobalKnownHostsFile=/dev/null -o ' \ + 'UserKnownHostsFile=/dev/null -o ' \ + 'LogLevel=error' + deploy_vars['external_network_cmds'] = \ + overcloud_deploy.external_network_cmds(net_settings) + # TODO(trozet): just parse all ds_opts as deploy vars one time + ds_opts = deploy_settings['deploy_options'] + deploy_vars['gluon'] = ds_opts['gluon'] + deploy_vars['sdn'] = ds_opts['sdn_controller'] + for dep_option in 'yardstick', 'dovetail', 'vsperf': + if dep_option in ds_opts: + deploy_vars[dep_option] = ds_opts[dep_option] + else: + deploy_vars[dep_option] = False + deploy_vars['dataplane'] = ds_opts['dataplane'] + overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc') + if ds_opts['congress']: + deploy_vars['congress_datasources'] = \ + overcloud_deploy.create_congress_cmds(overcloudrc) + deploy_vars['congress'] = True + else: + deploy_vars['congress'] = False + # TODO(trozet): this is probably redundant with getting external + # network info from undercloud.py + if 'external' in net_settings.enabled_network_list: + ext_cidr = net_settings['networks']['external'][0]['cidr'] + else: + ext_cidr = net_settings['networks']['admin']['cidr'] + deploy_vars['external_cidr'] = str(ext_cidr) + if ext_cidr.version == 6: + deploy_vars['external_network_ipv6'] = True + else: + deploy_vars['external_network_ipv6'] = False + post_undercloud = os.path.join(args.lib_dir, ansible_path, + 'post_deploy_undercloud.yml') + logging.info("Executing post deploy configuration undercloud playbook") + try: + utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip, + user='stack', tmp_dir=APEX_TEMP_DIR) + logging.info("Post Deploy Undercloud Configuration Complete") + except Exception: + logging.error("Post Deploy Undercloud Configuration failed. " + "Please check log") + raise + # Post deploy overcloud node configuration + # TODO(trozet): just parse all ds_opts as deploy vars one time + deploy_vars['sfc'] = ds_opts['sfc'] + deploy_vars['vpn'] = ds_opts['vpn'] + # TODO(trozet): pull all logs and store in tmp dir in overcloud + # playbook + post_overcloud = os.path.join(args.lib_dir, ansible_path, + 'post_deploy_overcloud.yml') + # Run per overcloud node + for node, ip in deploy_vars['overcloud_nodes'].items(): + logging.info("Executing Post deploy overcloud playbook on " + "node {}".format(node)) + try: + utils.run_ansible(deploy_vars, post_overcloud, host=ip, + user='heat-admin', tmp_dir=APEX_TEMP_DIR) + logging.info("Post Deploy Overcloud Configuration Complete " + "for node {}".format(node)) + except Exception: + logging.error("Post Deploy Overcloud Configuration failed " + "for node {}. Please check log".format(node)) + raise + logging.info("Apex deployment complete") + logging.info("Undercloud IP: {}, please connect by doing " + "'opnfv-util undercloud'".format(undercloud.ip)) + # TODO(trozet): add logging here showing controller VIP and horizon url +if __name__ == '__main__': + main() diff --git a/apex/inventory/__init__.py b/apex/inventory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apex/inventory/inventory.py b/apex/inventory/inventory.py new file mode 100644 index 00000000..dd731a83 --- /dev/null +++ b/apex/inventory/inventory.py @@ -0,0 +1,89 @@ +############################################################################## +# Copyright (c) 2016 Dan Radez (dradez@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import json +import platform + +import yaml + +from apex.common import constants +from apex.common import utils + + +class Inventory(dict): + """ + This class parses an APEX inventory yaml file into an object. It + generates or detects all missing fields for deployment. + + It then collapses one level of identification from the object to + convert it to a structure that can be dumped into a json file formatted + such that Triple-O can read the resulting json as an instackenv.json file. + """ + def __init__(self, source, ha=True, virtual=False): + init_dict = {} + self.root_device = constants.DEFAULT_ROOT_DEV + if isinstance(source, str): + with open(source, 'r') as inventory_file: + yaml_dict = yaml.safe_load(inventory_file) + # collapse node identifiers from the structure + init_dict['nodes'] = list(map(lambda n: n[1], + yaml_dict['nodes'].items())) + else: + # assume input is a dict to build from + init_dict = source + + # move ipmi_* to pm_* + # make mac a list + def munge_nodes(node): + node['pm_addr'] = node['ipmi_ip'] + node['pm_password'] = node['ipmi_pass'] + node['pm_user'] = node['ipmi_user'] + node['mac'] = [node['mac_address']] + if 'cpus' in node: + node['cpu'] = node['cpus'] + + for i in ('ipmi_ip', 'ipmi_pass', 'ipmi_user', 'mac_address', + 'disk_device'): + if i == 'disk_device' and 'disk_device' in node.keys(): + self.root_device = node[i] + else: + continue + del node[i] + + return node + + super().__init__({'nodes': list(map(munge_nodes, init_dict['nodes']))}) + + # verify number of nodes + if ha and len(self['nodes']) < 5 and not virtual: + raise InventoryException('You must provide at least 5 ' + 'nodes for HA baremetal deployment') + elif len(self['nodes']) < 2: + raise InventoryException('You must provide at least 2 nodes ' + 'for non-HA baremetal deployment') + + if virtual: + self['arch'] = platform.machine() + self['host-ip'] = '192.168.122.1' + self['power_manager'] = \ + 'nova.virt.baremetal.virtual_power_driver.VirtualPowerManager' + self['seed-ip'] = '' + self['ssh-key'] = 'INSERT_STACK_USER_PRIV_KEY' + self['ssh-user'] = 'root' + + def dump_instackenv_json(self): + print(json.dumps(dict(self), sort_keys=True, indent=4)) + + +class InventoryException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return self.value diff --git a/apex/network/__init__.py b/apex/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apex/network/ip_utils.py b/apex/network/ip_utils.py new file mode 100644 index 00000000..ae60b705 --- /dev/null +++ b/apex/network/ip_utils.py @@ -0,0 +1,230 @@ +############################################################################## +# Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import ipaddress +import subprocess +import re +import logging + + +def get_ip_range(start_offset=None, count=None, end_offset=None, + cidr=None, interface=None): + """ + Generate IP range for a network (cidr) or an interface. + + If CIDR is provided, it will take precedence over interface. In this case, + The entire CIDR IP address space is considered usable. start_offset will be + calculated from the network address, and end_offset will be calculated from + the last address in subnet. + + If interface is provided, the interface IP will be used to calculate + offsets: + - If the interface IP is in the first half of the address space, + start_offset will be calculated from the interface IP, and end_offset + will be calculated from end of address space. + - If the interface IP is in the second half of the address space, + start_offset will be calculated from the network address in the address + space, and end_offset will be calculated from the interface IP. + + 2 of start_offset, end_offset and count options must be provided: + - If start_offset and end_offset are provided, a range from + start_offset to end_offset will be returned. + - If count is provided, a range from either start_offset to + (start_offset+count) or (end_offset-count) to end_offset will be + returned. The IP range returned will be of size . + Both start_offset and end_offset must be greater than 0. + + Returns IP range in the format of "first_addr,second_addr" or exception + is raised. + """ + if cidr: + if count and start_offset and not end_offset: + start_index = start_offset + end_index = start_offset + count - 1 + elif count and end_offset and not start_offset: + end_index = -1 - end_offset + start_index = -1 - end_index - count + 1 + elif start_offset and end_offset and not count: + start_index = start_offset + end_index = -1 - end_offset + else: + raise IPUtilsException("Argument error: must pass in exactly 2 of" + " start_offset, end_offset and count") + + start_ip = cidr[start_index] + end_ip = cidr[end_index] + network = cidr + elif interface: + network = interface.network + number_of_addr = network.num_addresses + if interface.ip < network[int(number_of_addr / 2)]: + if count and start_offset and not end_offset: + start_ip = interface.ip + start_offset + end_ip = start_ip + count - 1 + elif count and end_offset and not start_offset: + end_ip = network[-1 - end_offset] + start_ip = end_ip - count + 1 + elif start_offset and end_offset and not count: + start_ip = interface.ip + start_offset + end_ip = network[-1 - end_offset] + else: + raise IPUtilsException( + "Argument error: must pass in exactly 2 of" + " start_offset, end_offset and count") + else: + if count and start_offset and not end_offset: + start_ip = network[start_offset] + end_ip = start_ip + count - 1 + elif count and end_offset and not start_offset: + end_ip = interface.ip - end_offset + start_ip = end_ip - count + 1 + elif start_offset and end_offset and not count: + start_ip = network[start_offset] + end_ip = interface.ip - end_offset + else: + raise IPUtilsException( + "Argument error: must pass in exactly 2 of" + " start_offset, end_offset and count") + + else: + raise IPUtilsException("Must pass in cidr or interface to generate" + "ip range") + + range_result = _validate_ip_range(start_ip, end_ip, network) + if range_result: + ip_range = "{},{}".format(start_ip, end_ip) + return ip_range + else: + raise IPUtilsException("Invalid IP range: {},{} for network {}" + .format(start_ip, end_ip, network)) + + +def get_ip(offset, cidr=None, interface=None): + """ + Returns an IP in a network given an offset. + + Either cidr or interface must be provided, cidr takes precedence. + + If cidr is provided, offset is calculated from network address. + If interface is provided, offset is calculated from interface IP. + + offset can be positive or negative, but the resulting IP address must also + be contained in the same subnet, otherwise an exception will be raised. + + returns a IP address object. + """ + if cidr: + ip = cidr[0 + offset] + network = cidr + elif interface: + ip = interface.ip + offset + network = interface.network + else: + raise IPUtilsException("Must pass in cidr or interface to generate IP") + + if ip not in network: + raise IPUtilsException("IP {} not in network {}".format(ip, network)) + else: + return str(ip) + + +def get_interface(nic, address_family=4): + """ + Returns interface object for a given NIC name in the system + + Only global address will be returned at the moment. + + Returns interface object if an address is found for the given nic, + otherwise returns None. + """ + if not nic.strip(): + logging.error("empty nic name specified") + return None + output = subprocess.getoutput("/usr/sbin/ip -{} addr show {} scope global" + .format(address_family, nic)) + if address_family == 4: + pattern = re.compile("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}") + elif address_family == 6: + pattern = re.compile("([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}/\d{1,3}") + else: + raise IPUtilsException("Invalid address family: {}" + .format(address_family)) + match = re.search(pattern, output) + if match: + logging.info("found interface {} ip: {}".format(nic, match.group())) + return ipaddress.ip_interface(match.group()) + else: + logging.info("interface ip not found! ip address output:\n{}" + .format(output)) + return None + + +def find_gateway(interface): + """ + Validate gateway on the system + + Ensures that the provided interface object is in fact configured as default + route on the system. + + Returns gateway IP (reachable from interface) if default route is found, + otherwise returns None. + """ + + address_family = interface.version + output = subprocess.getoutput("/usr/sbin/ip -{} route".format( + address_family)) + + pattern = re.compile("default\s+via\s+(\S+)\s+") + match = re.search(pattern, output) + + if match: + gateway_ip = match.group(1) + reverse_route_output = subprocess.getoutput("/usr/sbin/ip route get {}" + .format(gateway_ip)) + pattern = re.compile("{}.+src\s+{}".format(gateway_ip, interface.ip)) + if not re.search(pattern, reverse_route_output): + logging.warning("Default route doesn't match interface specified: " + "{}".format(reverse_route_output)) + return None + else: + return gateway_ip + else: + logging.warning("Can't find gateway address on system") + return None + + +def _validate_ip_range(start_ip, end_ip, cidr): + """ + Validates an IP range is in good order and the range is part of cidr. + + Returns True if validation succeeds, False otherwise. + """ + ip_range = "{},{}".format(start_ip, end_ip) + if end_ip <= start_ip: + logging.warning("IP range {} is invalid: end_ip should be greater " + "than starting ip".format(ip_range)) + return False + if start_ip not in ipaddress.ip_network(cidr): + logging.warning('start_ip {} is not in network {}' + .format(start_ip, cidr)) + return False + if end_ip not in ipaddress.ip_network(cidr): + logging.warning('end_ip {} is not in network {}'.format(end_ip, cidr)) + return False + + return True + + +class IPUtilsException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return self.value diff --git a/apex/network/jumphost.py b/apex/network/jumphost.py new file mode 100644 index 00000000..81562c7a --- /dev/null +++ b/apex/network/jumphost.py @@ -0,0 +1,172 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import logging +import os +import re +import shutil +import subprocess + +from apex.common.exceptions import ApexDeployException +from apex.network import ip_utils + +NET_MAP = { + 'admin': 'br-admin', + 'tenant': 'br-tenant', + 'external': 'br-external', + 'storage': 'br-storage', + 'api': 'br-api' +} + + +def configure_bridges(ns): + """ + Configures IP on jumphost bridges + :param ns: network_settings + :return: None + """ + bridge_networks = ['admin'] + if 'external' in ns.enabled_network_list: + bridge_networks.append('external') + for network in bridge_networks: + if network == 'external': + net_config = ns['networks'][network][0] + else: + net_config = ns['networks'][network] + cidr = net_config['cidr'] + interface = ip_utils.get_interface(NET_MAP[network], cidr.version) + + if interface: + logging.info("Bridge {} already configured with IP: {}".format( + NET_MAP[network], interface.ip)) + else: + logging.info("Will configure IP for {}".format(NET_MAP[network])) + ovs_ip = net_config['overcloud_ip_range'][1] + if cidr.version == 6: + ipv6_br_path = "/proc/sys/net/ipv6/conf/{}/disable_" \ + "ipv6".format(NET_MAP[network]) + try: + subprocess.check_call('echo', 0, '>', ipv6_br_path) + except subprocess.CalledProcessError: + logging.error("Unable to enable ipv6 on " + "bridge {}".format(NET_MAP[network])) + raise + try: + ip_prefix = "{}/{}".format(ovs_ip, cidr.prefixlen) + subprocess.check_call(['ip', 'addr', 'add', ip_prefix, 'dev', + NET_MAP[network]]) + subprocess.check_call(['ip', 'link', 'set', 'up', NET_MAP[ + network]]) + logging.info("IP configured: {} on bridge {}".format(ovs_ip, + NET_MAP[network])) + except subprocess.CalledProcessError: + logging.error("Unable to configure IP address on " + "bridge {}".format(NET_MAP[network])) + + +def attach_interface_to_ovs(bridge, interface, network): + """ + Attaches jumphost interface to OVS for baremetal deployments + :param bridge: bridge to attach to + :param interface: interface to attach to bridge + :param network: Apex network type for these interfaces + :return: None + """ + + net_cfg_path = '/etc/sysconfig/network-scripts' + if_file = os.path.join(net_cfg_path, "ifcfg-{}".format(interface)) + ovs_file = os.path.join(net_cfg_path, "ifcfg-{}".format(bridge)) + + logging.info("Attaching interface: {} to bridge: {} on network {}".format( + bridge, interface, network + )) + + try: + output = subprocess.check_output(['ovs-vsctl', 'list-ports', bridge], + stderr=subprocess.STDOUT) + if bridge in output: + logging.debug("Interface already attached to bridge") + return + except subprocess.CalledProcessError as e: + logging.error("Unable to dump ports for bridge: {}".format(bridge)) + logging.error("Error output: {}".format(e.output)) + raise + + if not os.path.isfile(if_file): + logging.error("Interface ifcfg not found: {}".format(if_file)) + raise FileNotFoundError("Interface file missing: {}".format(if_file)) + + ifcfg_params = { + 'IPADDR': '', + 'NETMASK': '', + 'GATEWAY': '', + 'METRIC': '', + 'DNS1': '', + 'DNS2': '', + 'PREFIX': '' + } + with open(if_file, 'r') as fh: + interface_output = fh.read() + + for param in ifcfg_params.keys(): + match = re.search("{}=(.*)\n".format(param), interface_output) + if match: + ifcfg_params[param] = match.group(1) + + if not ifcfg_params['IPADDR']: + logging.error("IPADDR missing in {}".format(if_file)) + raise ApexDeployException("IPADDR missing in {}".format(if_file)) + if not (ifcfg_params['NETMASK'] or ifcfg_params['PREFIX']): + logging.error("NETMASK/PREFIX missing in {}".format(if_file)) + raise ApexDeployException("NETMASK/PREFIX missing in {}".format( + if_file)) + if network == 'external' and not ifcfg_params['GATEWAY']: + logging.error("GATEWAY is required to be in {} for external " + "network".format(if_file)) + raise ApexDeployException("GATEWAY is required to be in {} for " + "external network".format(if_file)) + + shutil.move(if_file, "{}.orig".format(if_file)) + if_content = """DEVICE={} +DEVICETYPE=ovs +TYPE=OVSPort +PEERDNS=no +BOOTPROTO=static +NM_CONTROLLED=no +ONBOOT=yes +OVS_BRIDGE={} +PROMISC=yes""".format(interface, bridge) + + bridge_content = """DEVICE={} +DEVICETYPE=ovs +BOOTPROTO=static +ONBOOT=yes +TYPE=OVSBridge +PROMISC=yes""".format(bridge) + peer_dns = 'no' + for param, value in ifcfg_params.items(): + if value: + bridge_content += "\n{}={}".format(param, value) + if param == 'DNS1' or param == 'DNS2': + peer_dns = 'yes' + bridge_content += "\n{}={}".format('PEERDNS', peer_dns) + + logging.debug("New interface file content:\n{}".format(if_content)) + logging.debug("New bridge file content:\n{}".format(bridge_content)) + with open(if_file, 'w') as fh: + fh.write(if_content) + with open(ovs_file, 'w') as fh: + fh.write(bridge_content) + logging.info("New network ifcfg files written") + logging.info("Restarting Linux networking") + try: + subprocess.check_call(['systemctl', 'restart', 'network']) + except subprocess.CalledProcessError: + logging.error("Failed to restart Linux networking") + raise diff --git a/apex/network/network_environment.py b/apex/network/network_environment.py new file mode 100644 index 00000000..c2e9991a --- /dev/null +++ b/apex/network/network_environment.py @@ -0,0 +1,218 @@ +############################################################################## +# Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import re + +import yaml + +from apex.settings.network_settings import NetworkSettings +from apex.common.constants import ( + CONTROLLER, + COMPUTE, + ADMIN_NETWORK, + TENANT_NETWORK, + STORAGE_NETWORK, + EXTERNAL_NETWORK, + API_NETWORK +) + +HEAT_NONE = 'OS::Heat::None' +PORTS = '/ports' +# Resources defined by : +EXTERNAL_RESOURCES = {'OS::TripleO::Network::External': None, + 'OS::TripleO::Network::Ports::ExternalVipPort': PORTS, + 'OS::TripleO::Controller::Ports::ExternalPort': PORTS, + 'OS::TripleO::Compute::Ports::ExternalPort': PORTS} +TENANT_RESOURCES = {'OS::TripleO::Network::Tenant': None, + 'OS::TripleO::Controller::Ports::TenantPort': PORTS, + 'OS::TripleO::Compute::Ports::TenantPort': PORTS} +STORAGE_RESOURCES = {'OS::TripleO::Network::Storage': None, + 'OS::TripleO::Network::Ports::StorageVipPort': PORTS, + 'OS::TripleO::Controller::Ports::StoragePort': PORTS, + 'OS::TripleO::Compute::Ports::StoragePort': PORTS} +API_RESOURCES = {'OS::TripleO::Network::InternalApi': None, + 'OS::TripleO::Network::Ports::InternalApiVipPort': PORTS, + 'OS::TripleO::Controller::Ports::InternalApiPort': PORTS, + 'OS::TripleO::Compute::Ports::InternalApiPort': PORTS} + +# A list of flags that will be set to true when IPv6 is enabled +IPV6_FLAGS = ["NovaIPv6", "MongoDbIPv6", "CorosyncIPv6", "CephIPv6", + "RabbitIPv6", "MemcachedIPv6"] + +reg = 'resource_registry' +param_def = 'parameter_defaults' + + +class NetworkEnvironment(dict): + """ + This class creates a Network Environment to be used in TripleO Heat + Templates. + + The class builds upon an existing network-environment file and modifies + based on a NetworkSettings object. + """ + def __init__(self, net_settings, filename, compute_pre_config=False, + controller_pre_config=False): + """ + Create Network Environment according to Network Settings + """ + init_dict = {} + if isinstance(filename, str): + with open(filename, 'r') as net_env_fh: + init_dict = yaml.safe_load(net_env_fh) + + super().__init__(init_dict) + if not isinstance(net_settings, NetworkSettings): + raise NetworkEnvException('Invalid Network Settings object') + + self._set_tht_dir() + + nets = net_settings['networks'] + + admin_cidr = nets[ADMIN_NETWORK]['cidr'] + admin_prefix = str(admin_cidr.prefixlen) + self[param_def]['ControlPlaneSubnetCidr'] = admin_prefix + self[param_def]['ControlPlaneDefaultRoute'] = \ + nets[ADMIN_NETWORK]['installer_vm']['ip'] + self[param_def]['EC2MetadataIp'] = \ + nets[ADMIN_NETWORK]['installer_vm']['ip'] + self[param_def]['DnsServers'] = net_settings['dns_servers'] + + if EXTERNAL_NETWORK in net_settings.enabled_network_list: + external_cidr = net_settings.get_network(EXTERNAL_NETWORK)['cidr'] + self[param_def]['ExternalNetCidr'] = str(external_cidr) + external_vlan = self._get_vlan(net_settings.get_network( + EXTERNAL_NETWORK)) + if isinstance(external_vlan, int): + self[param_def]['NeutronExternalNetworkBridge'] = '""' + self[param_def]['ExternalNetworkVlanID'] = external_vlan + external_range = net_settings.get_network(EXTERNAL_NETWORK)[ + 'overcloud_ip_range'] + self[param_def]['ExternalAllocationPools'] = \ + [{'start': str(external_range[0]), + 'end': str(external_range[1])}] + self[param_def]['ExternalInterfaceDefaultRoute'] = \ + net_settings.get_network(EXTERNAL_NETWORK)['gateway'] + + if external_cidr.version == 6: + postfix = '/external_v6.yaml' + else: + postfix = '/external.yaml' + else: + postfix = '/noop.yaml' + + # apply resource registry update for EXTERNAL_RESOURCES + self._config_resource_reg(EXTERNAL_RESOURCES, postfix) + + if TENANT_NETWORK in net_settings.enabled_network_list: + tenant_range = nets[TENANT_NETWORK]['overcloud_ip_range'] + self[param_def]['TenantAllocationPools'] = \ + [{'start': str(tenant_range[0]), + 'end': str(tenant_range[1])}] + tenant_cidr = nets[TENANT_NETWORK]['cidr'] + self[param_def]['TenantNetCidr'] = str(tenant_cidr) + if tenant_cidr.version == 6: + postfix = '/tenant_v6.yaml' + # set overlay_ip_version option in Neutron ML2 config + self[param_def]['NeutronOverlayIPVersion'] = "6" + else: + postfix = '/tenant.yaml' + + tenant_vlan = self._get_vlan(nets[TENANT_NETWORK]) + if isinstance(tenant_vlan, int): + self[param_def]['TenantNetworkVlanID'] = tenant_vlan + else: + postfix = '/noop.yaml' + + # apply resource registry update for TENANT_RESOURCES + self._config_resource_reg(TENANT_RESOURCES, postfix) + + if STORAGE_NETWORK in net_settings.enabled_network_list: + storage_range = nets[STORAGE_NETWORK]['overcloud_ip_range'] + self[param_def]['StorageAllocationPools'] = \ + [{'start': str(storage_range[0]), + 'end': str(storage_range[1])}] + storage_cidr = nets[STORAGE_NETWORK]['cidr'] + self[param_def]['StorageNetCidr'] = str(storage_cidr) + if storage_cidr.version == 6: + postfix = '/storage_v6.yaml' + else: + postfix = '/storage.yaml' + storage_vlan = self._get_vlan(nets[STORAGE_NETWORK]) + if isinstance(storage_vlan, int): + self[param_def]['StorageNetworkVlanID'] = storage_vlan + else: + postfix = '/noop.yaml' + + # apply resource registry update for STORAGE_RESOURCES + self._config_resource_reg(STORAGE_RESOURCES, postfix) + + if API_NETWORK in net_settings.enabled_network_list: + api_range = nets[API_NETWORK]['overcloud_ip_range'] + self[param_def]['InternalApiAllocationPools'] = \ + [{'start': str(api_range[0]), + 'end': str(api_range[1])}] + api_cidr = nets[API_NETWORK]['cidr'] + self[param_def]['InternalApiNetCidr'] = str(api_cidr) + if api_cidr.version == 6: + postfix = '/internal_api_v6.yaml' + else: + postfix = '/internal_api.yaml' + api_vlan = self._get_vlan(nets[API_NETWORK]) + if isinstance(api_vlan, int): + self[param_def]['InternalApiNetworkVlanID'] = api_vlan + else: + postfix = '/noop.yaml' + + # apply resource registry update for API_RESOURCES + self._config_resource_reg(API_RESOURCES, postfix) + + # Set IPv6 related flags to True. Not that we do not set those to False + # when IPv4 is configured, we'll use the default or whatever the user + # may have set. + if net_settings.get_ip_addr_family() == 6: + for flag in IPV6_FLAGS: + self[param_def][flag] = True + + def _get_vlan(self, network): + if isinstance(network['nic_mapping'][CONTROLLER]['vlan'], int): + return network['nic_mapping'][CONTROLLER]['vlan'] + elif isinstance(network['nic_mapping'][COMPUTE]['vlan'], int): + return network['nic_mapping'][COMPUTE]['vlan'] + else: + return 'native' + + def _set_tht_dir(self): + self.tht_dir = None + for key, prefix in TENANT_RESOURCES.items(): + if prefix is None: + prefix = '' + m = re.split('%s/\w+\.yaml' % prefix, self[reg][key]) + if m is not None and len(m) > 1: + self.tht_dir = m[0] + break + if not self.tht_dir: + raise NetworkEnvException('Unable to parse THT Directory') + + def _config_resource_reg(self, resources, postfix): + for key, prefix in resources.items(): + if prefix is None: + if postfix == '/noop.yaml': + self[reg][key] = HEAT_NONE + continue + prefix = '' + self[reg][key] = self.tht_dir + prefix + postfix + + +class NetworkEnvException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return self.value diff --git a/apex/overcloud/__init__.py b/apex/overcloud/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apex/overcloud/config.py b/apex/overcloud/config.py new file mode 100644 index 00000000..6e116de2 --- /dev/null +++ b/apex/overcloud/config.py @@ -0,0 +1,76 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +""" +Utilities for generating overcloud configuration +""" + +import logging +import os + +from jinja2 import Environment +from jinja2 import FileSystemLoader +from apex.common.exceptions import ApexDeployException + + +def create_nic_template(network_settings, deploy_settings, role, template_dir, + target_dir): + """ + Creates NIC heat template files + :param ns: Network settings + :param ds: Deploy Settings + :param role: controller or compute + :param template_dir: directory where base templates are stored + :param target_dir: to store rendered nic template + :return: + """ + # TODO(trozet): rather than use Jinja2 to build these files, use with py + if role not in ['controller', 'compute']: + raise ApexDeployException("Invalid type for overcloud node: {" + "}".format(type)) + logging.info("Creating template for {}".format(role)) + template_file = 'nics-template.yaml.jinja2' + nets = network_settings.get('networks') + env = Environment(loader=FileSystemLoader(template_dir), autoescape=True) + template = env.get_template(template_file) + ds = deploy_settings.get('deploy_options') + ext_net = 'br-ex' + ovs_dpdk_br = '' + if ds['dataplane'] == 'fdio': + nets['tenant']['nic_mapping'][role]['phys_type'] = 'vpp_interface' + if ds['sdn_controller'] == 'opendaylight': + nets['external'][0]['nic_mapping'][role]['phys_type'] = \ + 'vpp_interface' + ext_net = 'vpp_interface' + elif ds['dataplane'] == 'ovs_dpdk': + ovs_dpdk_br = 'br-phy' + if (ds.get('performance', {}).get(role.title(), {}).get('vpp', {}) + .get('uio-driver')): + nets['tenant']['nic_mapping'][role]['uio-driver'] =\ + ds['performance'][role.title()]['vpp']['uio-driver'] + if ds['sdn_controller'] == 'opendaylight': + nets['external'][0]['nic_mapping'][role]['uio-driver'] =\ + ds['performance'][role.title()]['vpp']['uio-driver'] + if (ds.get('performance', {}).get(role.title(), {}).get('vpp', {}) + .get('interface-options')): + nets['tenant']['nic_mapping'][role]['interface-options'] =\ + ds['performance'][role.title()]['vpp']['interface-options'] + + template_output = template.render( + nets=nets, + role=role, + external_net_af=network_settings.get_ip_addr_family(), + external_net_type=ext_net, + ovs_dpdk_bridge=ovs_dpdk_br) + + logging.debug("Template output: {}".format(template_output)) + target = os.path.join(target_dir, "{}.yaml".format(role)) + with open(target, "w") as f: + f.write(template_output) + logging.info("Wrote template {}".format(target)) diff --git a/apex/overcloud/overcloud_deploy.py b/apex/overcloud/overcloud_deploy.py new file mode 100644 index 00000000..3c108464 --- /dev/null +++ b/apex/overcloud/overcloud_deploy.py @@ -0,0 +1,556 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import base64 +import fileinput +import logging +import os +import re +import shutil +import uuid +import struct +import time + +from apex.common import constants as con +from apex.common.exceptions import ApexDeployException +from apex.common import parsers +from apex.virtual import virtual_utils as virt_utils +from cryptography.hazmat.primitives import serialization as \ + crypto_serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.backends import default_backend as \ + crypto_default_backend + + +SDN_FILE_MAP = { + 'opendaylight': { + 'sfc': 'opendaylight_sfc.yaml', + 'vpn': 'neutron-bgpvpn-opendaylight.yaml', + 'gluon': 'gluon.yaml', + 'vpp': { + 'odl_vpp_netvirt': 'neutron-opendaylight-netvirt-vpp.yaml', + 'default': 'neutron-opendaylight-honeycomb.yaml' + }, + 'default': 'neutron-opendaylight.yaml', + }, + 'onos': { + 'sfc': 'neutron-onos-sfc.yaml', + 'default': 'neutron-onos.yaml' + }, + 'ovn': 'neutron-ml2-ovn.yaml', + False: { + 'vpp': 'neutron-ml2-vpp.yaml', + 'dataplane': ('ovs_dpdk', 'neutron-ovs-dpdk.yaml') + } +} + +OTHER_FILE_MAP = { + 'tacker': 'enable_tacker.yaml', + 'congress': 'enable_congress.yaml', + 'barometer': 'enable_barometer.yaml', + 'rt_kvm': 'enable_rt_kvm.yaml' +} + +OVS_PERF_MAP = { + 'HostCpusList': 'dpdk_cores', + 'NeutronDpdkCoreList': 'pmd_cores', + 'NeutronDpdkSocketMemory': 'socket_memory', + 'NeutronDpdkMemoryChannels': 'memory_channels' +} + +OVS_NSH_KMOD_RPM = "openvswitch-kmod-2.6.1-1.el7.centos.x86_64.rpm" +OVS_NSH_RPM = "openvswitch-2.6.1-1.el7.centos.x86_64.rpm" +ODL_NETVIRT_VPP_RPM = "/root/opendaylight-7.0.0-0.1.20170531snap665.el7" \ + ".noarch.rpm" + + +def build_sdn_env_list(ds, sdn_map, env_list=None): + if env_list is None: + env_list = list() + for k, v in sdn_map.items(): + if ds['sdn_controller'] == k or (k in ds and ds[k] is True): + if isinstance(v, dict): + env_list.extend(build_sdn_env_list(ds, v)) + else: + env_list.append(os.path.join(con.THT_ENV_DIR, v)) + elif isinstance(v, tuple): + if ds[k] == v[0]: + env_list.append(os.path.join(con.THT_ENV_DIR, v[1])) + if len(env_list) == 0: + try: + env_list.append(os.path.join( + con.THT_ENV_DIR, sdn_map[ds['sdn_controller']]['default'])) + except KeyError: + logging.warning("Unable to find default file for SDN") + + return env_list + + +def create_deploy_cmd(ds, ns, inv, tmp_dir, + virtual, env_file='opnfv-environment.yaml'): + + logging.info("Creating deployment command") + deploy_options = [env_file, 'network-environment.yaml'] + ds_opts = ds['deploy_options'] + deploy_options += build_sdn_env_list(ds_opts, SDN_FILE_MAP) + + # TODO(trozet): make sure rt kvm file is in tht dir + for k, v in OTHER_FILE_MAP.items(): + if k in ds_opts and ds_opts[k]: + deploy_options.append(os.path.join(con.THT_ENV_DIR, v)) + + if ds_opts['ceph']: + prep_storage_env(ds, tmp_dir) + deploy_options.append(os.path.join(con.THT_ENV_DIR, + 'storage-environment.yaml')) + if ds['global_params']['ha_enabled']: + deploy_options.append(os.path.join(con.THT_ENV_DIR, + 'puppet-pacemaker.yaml')) + + if virtual: + deploy_options.append('virtual-environment.yaml') + else: + deploy_options.append('baremetal-environment.yaml') + + nodes = inv['nodes'] + num_control = 0 + num_compute = 0 + for node in nodes: + if node['capabilities'] == 'profile:control': + num_control += 1 + elif node['capabilities'] == 'profile:compute': + num_compute += 1 + else: + # TODO(trozet) do we want to allow capabilities to not exist? + logging.error("Every node must include a 'capabilities' key " + "tagged with either 'profile:control' or " + "'profile:compute'") + raise ApexDeployException("Node missing capabilities " + "key: {}".format(node)) + if num_control == 0 or num_compute == 0: + logging.error("Detected 0 control or compute nodes. Control nodes: " + "{}, compute nodes{}".format(num_control, num_compute)) + raise ApexDeployException("Invalid number of control or computes") + cmd = "openstack overcloud deploy --templates --timeout {} " \ + "--libvirt-type kvm".format(con.DEPLOY_TIMEOUT) + # build cmd env args + for option in deploy_options: + cmd += " -e {}".format(option) + cmd += " --ntp-server {}".format(ns['ntp'][0]) + cmd += " --control-scale {}".format(num_control) + cmd += " --compute-scale {}".format(num_compute) + cmd += ' --control-flavor control --compute-flavor compute' + logging.info("Deploy command set: {}".format(cmd)) + + with open(os.path.join(tmp_dir, 'deploy_command'), 'w') as fh: + fh.write(cmd) + return cmd + + +def prep_image(ds, img, tmp_dir, root_pw=None): + """ + Locates sdn image and preps for deployment. + :param ds: deploy settings + :param img: sdn image + :param tmp_dir: dir to store modified sdn image + :param root_pw: password to configure for overcloud image + :return: None + """ + # TODO(trozet): Come up with a better way to organize this logic in this + # function + logging.info("Preparing image: {} for deployment".format(img)) + if not os.path.isfile(img): + logging.error("Missing SDN image {}".format(img)) + raise ApexDeployException("Missing SDN image file: {}".format(img)) + + ds_opts = ds['deploy_options'] + virt_cmds = list() + sdn = ds_opts['sdn_controller'] + # we need this due to rhbz #1436021 + # fixed in systemd-219-37.el7 + if sdn is not False: + logging.info("Neutron openvswitch-agent disabled") + virt_cmds.extend([{ + con.VIRT_RUN_CMD: + "rm -f /etc/systemd/system/multi-user.target.wants/" + "neutron-openvswitch-agent.service"}, + { + con.VIRT_RUN_CMD: + "rm -f /usr/lib/systemd/system/neutron-openvswitch-agent" + ".service" + }]) + + if ds_opts['vpn']: + virt_cmds.append({con.VIRT_RUN_CMD: "systemctl enable zrpcd"}) + logging.info("ZRPC and Quagga enabled") + + dataplane = ds_opts['dataplane'] + if dataplane == 'ovs_dpdk' or dataplane == 'fdio': + logging.info("Enabling kernel modules for dpdk") + # file to module mapping + uio_types = { + os.path.join(tmp_dir, 'vfio_pci.modules'): 'vfio_pci', + os.path.join(tmp_dir, 'uio_pci_generic.modules'): 'uio_pci_generic' + } + for mod_file, mod in uio_types: + with open(mod_file, 'w') as fh: + fh.write('#!/bin/bash\n') + fh.write('exec /sbin/modprobe {}'.format(mod)) + fh.close() + + virt_cmds.extend([ + {con.VIRT_UPLOAD: "{}:/etc/sysconfig/modules/".format( + mod_file)}, + {con.VIRT_RUN_CMD: "chmod 0755 /etc/sysconfig/modules/" + "{}".format(os.path.basename(mod_file))} + ]) + if root_pw: + pw_op = "password:{}".format(root_pw) + virt_cmds.append({con.VIRT_PW: pw_op}) + if ds_opts['sfc'] and dataplane == 'ovs': + virt_cmds.extend([ + {con.VIRT_RUN_CMD: "yum -y install " + "/root/ovs/rpm/rpmbuild/RPMS/x86_64/" + "{}".format(OVS_NSH_KMOD_RPM)}, + {con.VIRT_RUN_CMD: "yum upgrade -y " + "/root/ovs/rpm/rpmbuild/RPMS/x86_64/" + "{}".format(OVS_NSH_RPM)} + ]) + if dataplane == 'fdio': + # Patch neutron with using OVS external interface for router + # and add generic linux NS interface driver + virt_cmds.append( + {con.VIRT_RUN_CMD: "cd /usr/lib/python2.7/site-packages && patch " + "-p1 < neutron-patch-NSDriver.patch"}) + + if sdn == 'opendaylight': + if ds_opts['odl_version'] != con.DEFAULT_ODL_VERSION: + virt_cmds.extend([ + {con.VIRT_RUN_CMD: "yum -y remove opendaylight"}, + {con.VIRT_RUN_CMD: "yum -y install /root/{}/*".format( + con.DEFAULT_ODL_VERSION)}, + {con.VIRT_RUN_CMD: "rm -rf /etc/puppet/modules/opendaylight"}, + {con.VIRT_RUN_CMD: "cd /etc/puppet/modules && tar xzf " + "/root/puppet-opendaylight-" + "{}.tar.gz".format(ds_opts['odl_version'])} + ]) + elif sdn == 'opendaylight' and 'odl_vpp_netvirt' in ds_opts \ + and ds_opts['odl_vpp_netvirt']: + virt_cmds.extend([ + {con.VIRT_RUN_CMD: "yum -y remove opendaylight"}, + {con.VIRT_RUN_CMD: "yum -y install /root/{}/*".format( + ODL_NETVIRT_VPP_RPM)} + ]) + + if sdn == 'ovn': + virt_cmds.extend([ + {con.VIRT_RUN_CMD: "cd /root/ovs28 && yum update -y " + "*openvswitch*"}, + {con.VIRT_RUN_CMD: "cd /root/ovs28 && yum downgrade -y " + "*openvswitch*"} + ]) + + tmp_oc_image = os.path.join(tmp_dir, 'overcloud-full.qcow2') + shutil.copyfile(img, tmp_oc_image) + logging.debug("Temporary overcloud image stored as: {}".format( + tmp_oc_image)) + virt_utils.virt_customize(virt_cmds, tmp_oc_image) + logging.info("Overcloud image customization complete") + + +def make_ssh_key(): + """ + Creates public and private ssh keys with 1024 bit RSA encryption + :return: private, public key + """ + key = rsa.generate_private_key( + backend=crypto_default_backend(), + public_exponent=65537, + key_size=1024 + ) + + private_key = key.private_bytes( + crypto_serialization.Encoding.PEM, + crypto_serialization.PrivateFormat.PKCS8, + crypto_serialization.NoEncryption()) + public_key = key.public_key().public_bytes( + crypto_serialization.Encoding.OpenSSH, + crypto_serialization.PublicFormat.OpenSSH + ) + pub_key = re.sub('ssh-rsa\s*', '', public_key.decode('utf-8')) + return private_key.decode('utf-8'), pub_key + + +def prep_env(ds, ns, opnfv_env, net_env, tmp_dir): + """ + Creates modified opnfv/network environments for deployment + :param ds: deploy settings + :param ns: network settings + :param opnfv_env: file path for opnfv-environment file + :param net_env: file path for network-environment file + :param tmp_dir: Apex tmp dir + :return: + """ + + logging.info("Preparing opnfv-environment and network-environment files") + ds_opts = ds['deploy_options'] + tmp_opnfv_env = os.path.join(tmp_dir, os.path.basename(opnfv_env)) + shutil.copyfile(opnfv_env, tmp_opnfv_env) + tenant_nic_map = ns['networks']['tenant']['nic_mapping'] + tenant_ctrl_nic = tenant_nic_map['controller']['members'][0] + tenant_comp_nic = tenant_nic_map['compute']['members'][0] + + # SSH keys + private_key, public_key = make_ssh_key() + + # Make easier/faster variables to index in the file editor + if 'performance' in ds_opts: + perf = True + # vpp + if 'vpp' in ds_opts['performance']['Compute']: + perf_vpp_comp = ds_opts['performance']['Compute']['vpp'] + else: + perf_vpp_comp = None + if 'vpp' in ds_opts['performance']['Controller']: + perf_vpp_ctrl = ds_opts['performance']['Controller']['vpp'] + else: + perf_vpp_ctrl = None + + # ovs + if 'ovs' in ds_opts['performance']['Compute']: + perf_ovs_comp = ds_opts['performance']['Compute']['ovs'] + else: + perf_ovs_comp = None + + # kernel + if 'kernel' in ds_opts['performance']['Compute']: + perf_kern_comp = ds_opts['performance']['Compute']['kernel'] + else: + perf_kern_comp = None + else: + perf = False + + # Modify OPNFV environment + for line in fileinput.input(tmp_opnfv_env, inplace=True): + line = line.strip('\n') + if 'CloudDomain' in line: + print(" CloudDomain: {}".format(ns['domain_name'])) + elif ds_opts['sdn_controller'] == 'opendaylight' and \ + 'odl_vpp_routing_node' in ds_opts and ds_opts[ + 'odl_vpp_routing_node'] != 'dvr': + if 'opendaylight::vpp_routing_node' in line: + print(" opendaylight::vpp_routing_node: ${}.${}".format( + ds_opts['odl_vpp_routing_node'], ns['domain_name'])) + elif 'ControllerExtraConfig' in line: + print(" ControllerExtraConfig:\n " + "tripleo::profile::base::neutron::agents::honeycomb" + "::interface_role_mapping: ['{}:tenant-" + "interface]'".format(tenant_ctrl_nic)) + elif 'NovaComputeExtraConfig' in line: + print(" NovaComputeExtraConfig:\n " + "tripleo::profile::base::neutron::agents::honeycomb" + "::interface_role_mapping: ['{}:tenant-" + "interface]'".format(tenant_comp_nic)) + else: + print(line) + + elif not ds_opts['sdn_controller'] and ds_opts['dataplane'] == 'fdio': + if 'NeutronVPPAgentPhysnets' in line: + print(" NeutronVPPAgentPhysnets: 'datacentre:{}'".format( + tenant_ctrl_nic)) + else: + print(line) + elif perf: + line_printed = False + for role in 'NovaCompute', 'Controller': + if role == 'NovaCompute': + perf_opts = perf_vpp_comp + else: + perf_opts = perf_vpp_ctrl + cfg = "{}ExtraConfig".format(role) + if cfg in line and perf_opts: + if 'main-core' in perf_opts: + print(" {}:\n" + " fdio::vpp_cpu_main_core: '{}'" + "".format(cfg, perf_opts['main-core'])) + line_printed = True + break + elif 'corelist-workers' in perf_vpp_comp: + print(" {}:\n" + " fdio::vpp_cpu_corelist_workers: '{}'" + "".format(cfg, perf_opts['corelist-workers'])) + line_printed = True + break + + # kernel args + # (FIXME) use compute's kernel settings for all nodes for now. + if 'ComputeKernelArgs' in line and perf_kern_comp: + kernel_args = '' + for k, v in perf_kern_comp.items(): + kernel_args += "{}={}".format(k, v) + if kernel_args: + print("ComputeKernelArgs: '{}'".format(kernel_args)) + line_printed = True + elif ds_opts['dataplane'] == 'ovs_dpdk' and perf_ovs_comp: + for k, v in OVS_PERF_MAP.items(): + if k in line and v in perf_ovs_comp: + print(" {}: {}".format(k, perf_ovs_comp[v])) + line_printed = True + + if not line_printed: + print(line) + elif 'replace_private_key' in line: + print(" key: '{}'".format(private_key)) + elif 'replace_public_key' in line: + print(" key: '{}'".format(public_key)) + else: + print(line) + + logging.info("opnfv-environment file written to {}".format(tmp_opnfv_env)) + + # Modify Network environment + for line in fileinput.input(net_env, inplace=True): + line = line.strip('\n') + if ds_opts['dataplane'] == 'ovs_dpdk': + if 'ComputeExtraConfigPre' in line: + print(' OS::TripleO::ComputeExtraConfigPre: ' + './ovs-dpdk-preconfig.yaml') + else: + print(line) + elif perf and perf_kern_comp: + if 'resource_registry' in line: + print("resource_registry:\n" + " OS::TripleO::NodeUserData: first-boot.yaml") + elif 'NovaSchedulerDefaultFilters' in line: + print(" NovaSchedulerDefaultFilters: 'RamFilter," + "ComputeFilter,AvailabilityZoneFilter," + "ComputeCapabilitiesFilter,ImagePropertiesFilter," + "NUMATopologyFilter'") + else: + print(line) + else: + print(line) + + logging.info("network-environment file written to {}".format(net_env)) + + +def generate_ceph_key(): + key = os.urandom(16) + header = struct.pack(' to + facilitate modification of the correct image. + """ + bash_str = 'performance_options=(\n' + deploy_options = self['deploy_options'] + for role, settings in deploy_options['performance'].items(): + for category, options in settings.items(): + for key, value in options.items(): + bash_str += "\"{} {} {} {}\"\n".format(role, + category, + key, + value) + bash_str += ')\n' + bash_str += '\n' + bash_str += 'performance_roles=(\n' + for role in self['deploy_options']['performance']: + bash_str += role + '\n' + bash_str += ')\n' + bash_str += '\n' + + return bash_str + + def _dump_deploy_options_array(self): + """ + Creates deploy settings array in bash syntax. + """ + bash_str = '' + for key, value in self['deploy_options'].items(): + if not isinstance(value, bool): + bash_str += "deploy_options_array[{}]=\"{}\"\n".format(key, + value) + else: + bash_str += "deploy_options_array[{}]={}\n".format(key, + value) + return bash_str + + +class DeploySettingsException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return self.value diff --git a/apex/settings/network_settings.py b/apex/settings/network_settings.py new file mode 100644 index 00000000..14870078 --- /dev/null +++ b/apex/settings/network_settings.py @@ -0,0 +1,327 @@ +############################################################################## +# Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import ipaddress +import logging +from copy import copy + +import yaml + +from apex.common import utils +from apex.common.constants import ( + CONTROLLER, + COMPUTE, + ROLES, + DOMAIN_NAME, + DNS_SERVERS, + NTP_SERVER, + ADMIN_NETWORK, + EXTERNAL_NETWORK, + OPNFV_NETWORK_TYPES, +) +from apex.network import ip_utils + + +class NetworkSettings(dict): + """ + This class parses APEX network settings yaml file into an object. It + generates or detects all missing fields for deployment. + + The resulting object will be used later to generate network environment + file as well as configuring post deployment networks. + + Currently the parsed object is dumped into a bash global definition file + for deploy.sh consumption. This object will later be used directly as + deployment script move to python. + """ + def __init__(self, filename): + init_dict = {} + if isinstance(filename, str): + with open(filename, 'r') as network_settings_file: + init_dict = yaml.safe_load(network_settings_file) + else: + # assume input is a dict to build from + init_dict = filename + super().__init__(init_dict) + + if 'apex' in self: + # merge two dicts Non-destructively + def merge(pri, sec): + for key, val in sec.items(): + if key in pri: + if isinstance(val, dict): + merge(pri[key], val) + # else + # do not overwrite what's already there + else: + pri[key] = val + # merge the apex specific config into the first class settings + merge(self, copy(self['apex'])) + + self.enabled_network_list = [] + self.nics = {COMPUTE: {}, CONTROLLER: {}} + self.nics_specified = {COMPUTE: False, CONTROLLER: False} + self._validate_input() + + def get_network(self, network): + if network == EXTERNAL_NETWORK and self['networks'][network]: + for net in self['networks'][network]: + if 'public' in net: + return net + + raise NetworkSettingsException("The external network, " + "'public', should be defined " + "when external networks are " + "enabled") + else: + return self['networks'][network] + + def _validate_input(self): + """ + Validates the network settings file and populates all fields. + + NetworkSettingsException will be raised if validation fails. + """ + if not self['networks'].get(ADMIN_NETWORK, {}).get('enabled', False): + raise NetworkSettingsException("You must enable admin network " + "and configure it explicitly or " + "use auto-detection") + + for network in OPNFV_NETWORK_TYPES: + if network in self['networks']: + _network = self.get_network(network) + if _network.get('enabled', True): + logging.info("{} enabled".format(network)) + self._config_required_settings(network) + nicmap = _network['nic_mapping'] + self._validate_overcloud_nic_order(network) + iface = nicmap[CONTROLLER]['members'][0] + self._config_ip_range(network=network, + interface=iface, + ip_range='overcloud_ip_range', + start_offset=21, end_offset=21) + self.enabled_network_list.append(network) + # TODO self._config_optional_settings(network) + else: + logging.info("{} disabled, will collapse with " + "admin network".format(network)) + else: + logging.info("{} is not in specified, will collapse with " + "admin network".format(network)) + + if 'dns-domain' not in self: + self['domain_name'] = DOMAIN_NAME + else: + self['domain_name'] = self['dns-domain'] + self['dns_servers'] = self.get('dns_nameservers', DNS_SERVERS) + self['ntp_servers'] = self.get('ntp', NTP_SERVER) + + def _validate_overcloud_nic_order(self, network): + """ + Detects if nic order is specified per profile (compute/controller) + for network + + If nic order is specified in a network for a profile, it should be + specified for every network with that profile other than admin network + + Duplicate nic names are also not allowed across different networks + + :param network: network to detect if nic order present + :return: None + """ + for role in ROLES: + _network = self.get_network(network) + _nicmap = _network.get('nic_mapping', {}) + _role = _nicmap.get(role, {}) + interfaces = _role.get('members', []) + + if interfaces: + interface = interfaces[0] + if not isinstance(_role.get('vlan', 'native'), int) and \ + any(y == interface for x, y in self.nics[role].items()): + raise NetworkSettingsException( + "Duplicate {} already specified for " + "another network".format(interface)) + self.nics[role][network] = interface + self.nics_specified[role] = True + logging.info("{} nic order specified for network {" + "}".format(role, network)) + else: + raise NetworkSettingsException( + "Interface members are not supplied for {} network " + "for the {} role. Please add nic assignments" + "".format(network, role)) + + def _config_required_settings(self, network): + """ + Configures either CIDR or bridged_interface setting + + cidr takes precedence if both cidr and bridged_interface are specified + for a given network. + + When using bridged_interface, we will detect network setting on the + given NIC in the system. The resulting config in settings object will + be an ipaddress.network object, replacing the NIC name. + """ + _network = self.get_network(network) + # if vlan not defined then default it to native + if network is not ADMIN_NETWORK: + for role in ROLES: + if 'vlan' not in _network['nic_mapping'][role]: + _network['nic_mapping'][role]['vlan'] = 'native' + + cidr = _network.get('cidr') + + if cidr: + cidr = ipaddress.ip_network(_network['cidr']) + _network['cidr'] = cidr + logging.info("{}_cidr: {}".format(network, cidr)) + elif 'installer_vm' in _network: + ucloud_if_list = _network['installer_vm']['members'] + # If cidr is not specified, we need to know if we should find + # IPv6 or IPv4 address on the interface + ip = ipaddress.ip_address(_network['installer_vm']['ip']) + nic_if = ip_utils.get_interface(ucloud_if_list[0], ip.version) + if nic_if: + logging.info("{}_bridged_interface: {}". + format(network, nic_if)) + else: + raise NetworkSettingsException( + "Auto detection failed for {}: Unable to find valid " + "ip for interface {}".format(network, ucloud_if_list[0])) + + else: + raise NetworkSettingsException( + "Auto detection failed for {}: either installer_vm " + "members or cidr must be specified".format(network)) + + # undercloud settings + if network == ADMIN_NETWORK: + provisioner_ip = _network['installer_vm']['ip'] + iface = _network['installer_vm']['members'][0] + if not provisioner_ip: + _network['installer_vm']['ip'] = self._gen_ip(network, 1) + self._config_ip_range(network=network, interface=iface, + ip_range='dhcp_range', + start_offset=2, count=9) + self._config_ip_range(network=network, interface=iface, + ip_range='introspection_range', + start_offset=11, count=9) + elif network == EXTERNAL_NETWORK: + provisioner_ip = _network['installer_vm']['ip'] + iface = _network['installer_vm']['members'][0] + if not provisioner_ip: + _network['installer_vm']['ip'] = self._gen_ip(network, 1) + self._config_ip_range(network=network, interface=iface, + ip_range='floating_ip_range', + end_offset=2, count=20) + + gateway = _network['gateway'] + interface = _network['installer_vm']['ip'] + self._config_gateway(network, gateway, interface) + + def _config_ip_range(self, network, ip_range, interface=None, + start_offset=None, end_offset=None, count=None): + """ + Configures IP range for a given setting. + If the setting is already specified, no change will be made. + The spec for start_offset, end_offset and count are identical to + ip_utils.get_ip_range. + """ + _network = self.get_network(network) + if ip_range not in _network: + cidr = _network.get('cidr') + _ip_range = ip_utils.get_ip_range(start_offset=start_offset, + end_offset=end_offset, + count=count, + cidr=cidr, + interface=interface) + _network[ip_range] = _ip_range.split(',') + + logging.info("Config IP Range: {} {}".format(network, ip_range)) + + def _gen_ip(self, network, offset): + """ + Generate and ip offset within the given network + """ + _network = self.get_network(network) + cidr = _network.get('cidr') + ip = ip_utils.get_ip(offset, cidr) + logging.info("Config IP: {} {}".format(network, ip)) + return ip + + def _config_optional_settings(self, network): + """ + Configures optional settings: + - admin_network: + - provisioner_ip + - dhcp_range + - introspection_range + - public_network: + - provisioner_ip + - floating_ip_range + - gateway + """ + if network == ADMIN_NETWORK: + # FIXME: _config_ip function does not exist! + self._config_ip(network, None, 'provisioner_ip', 1) + self._config_ip_range(network=network, + ip_range='dhcp_range', + start_offset=2, count=9) + self._config_ip_range(network=network, + ip_range='introspection_range', + start_offset=11, count=9) + elif network == EXTERNAL_NETWORK: + # FIXME: _config_ip function does not exist! + self._config_ip(network, None, 'provisioner_ip', 1) + self._config_ip_range(network=network, + ip_range='floating_ip_range', + end_offset=2, count=20) + self._config_gateway(network) + + def _config_gateway(self, network, gateway, interface): + """ + Configures gateway setting for a given network. + + If cidr is specified, we always use the first address in the address + space for gateway. Otherwise, we detect the system gateway. + """ + _network = self.get_network(network) + if not gateway: + cidr = _network.get('cidr') + if cidr: + _gateway = ip_utils.get_ip(1, cidr) + else: + _gateway = ip_utils.find_gateway(interface) + + if _gateway: + _network['gateway'] = _gateway + else: + raise NetworkSettingsException("Failed to set gateway") + + logging.info("Config Gateway: {} {}".format(network, gateway)) + + def get_ip_addr_family(self,): + """ + Returns IP address family for current deployment. + + If any enabled network has IPv6 CIDR, the deployment is classified as + IPv6. + """ + return max([ + ipaddress.ip_network(self.get_network(n)['cidr']).version + for n in self.enabled_network_list]) + + +class NetworkSettingsException(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return self.value diff --git a/apex/tests/__init__.py b/apex/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apex/tests/config/inventory.yaml b/apex/tests/config/inventory.yaml new file mode 100644 index 00000000..2abe0fc9 --- /dev/null +++ b/apex/tests/config/inventory.yaml @@ -0,0 +1,57 @@ +--- +nodes: + node1: + mac_address: "00:25:B5:cc:00:1e" + ipmi_ip: 72.30.8.69 + ipmi_user: admin + ipmi_pass: octopus + pm_type: "pxe_ipmitool" + cpus: 2 + memory: 8192 + disk: 40 + arch: "x86_64" + capabilities: "profile:control" + node2: + mac_address: "00:25:B5:cc:00:5d" + ipmi_ip: 72.30.8.78 + ipmi_user: admin + ipmi_pass: octopus + pm_type: "pxe_ipmitool" + cpus: 2 + memory: 8192 + disk: 40 + arch: "x86_64" + capabilities: "profile:control" + node3: + mac_address: "00:25:B5:cc:00:1d" + ipmi_ip: 72.30.8.67 + ipmi_user: admin + ipmi_pass: octopus + pm_type: "pxe_ipmitool" + cpus: 2 + memory: 8192 + disk: 40 + arch: "x86_64" + capabilities: "profile:control" + node4: + mac_address: "00:25:B5:cc:00:3c" + ipmi_ip: 72.30.8.76 + ipmi_user: admin + ipmi_pass: octopus + pm_type: "pxe_ipmitool" + cpus: 2 + memory: 8192 + disk: 40 + arch: "x86_64" + capabilities: "profile:compute" + node5: + mac_address: "00:25:B5:cc:00:5b" + ipmi_ip: 72.30.8.71 + ipmi_user: admin + ipmi_pass: octopus + pm_type: "pxe_ipmitool" + cpus: 2 + memory: 8192 + disk: 40 + arch: "x86_64" + capabilities: "profile:compute" diff --git a/apex/tests/constants.py b/apex/tests/constants.py new file mode 100644 index 00000000..47e63e2c --- /dev/null +++ b/apex/tests/constants.py @@ -0,0 +1,12 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +TEST_CONFIG_DIR = 'config' +TEST_BUILD_DIR = 'build' +TEST_PLAYBOOK_DIR = 'playbooks' diff --git a/apex/tests/playbooks/test_playbook.yaml b/apex/tests/playbooks/test_playbook.yaml new file mode 100644 index 00000000..800d8fde --- /dev/null +++ b/apex/tests/playbooks/test_playbook.yaml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + tasks: + - debug: + msg: "Test playbook" diff --git a/apex/tests/smoke_tests/execute_smoke_tests.sh b/apex/tests/smoke_tests/execute_smoke_tests.sh new file mode 100755 index 00000000..27f95251 --- /dev/null +++ b/apex/tests/smoke_tests/execute_smoke_tests.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +python ~/snaps/snaps/test_runner.py -e ~stack/overcloudrc -n external -c -a -i -f -k -l INFO &> ~stack/smoke-tests.out \ No newline at end of file diff --git a/apex/tests/smoke_tests/execute_tests.yml b/apex/tests/smoke_tests/execute_tests.yml new file mode 100644 index 00000000..5042d230 --- /dev/null +++ b/apex/tests/smoke_tests/execute_tests.yml @@ -0,0 +1,11 @@ +--- +- hosts: all + become: yes + become_method: sudo + become_user: root + + tasks: + - name: Copy execute_smoke_tests.sh + copy: src=execute_smoke_tests.sh dest=~/execute_smoke_tests.sh mode=0755 + - name: Execute Tests + command: sh ~/execute_smoke_tests.sh | tee ~/unit_tests.out \ No newline at end of file diff --git a/apex/tests/smoke_tests/prepare_undercloud.yml b/apex/tests/smoke_tests/prepare_undercloud.yml new file mode 100644 index 00000000..7ad769c0 --- /dev/null +++ b/apex/tests/smoke_tests/prepare_undercloud.yml @@ -0,0 +1,9 @@ +--- +- hosts: all + become: yes + become_method: sudo + become_user: root + + tasks: + - git: repo=https://gerrit.opnfv.org/gerrit/snaps dest=~/snaps + - command: pip install -e ~/snaps/ diff --git a/apex/tests/smoke_tests/smoke_tests.yml b/apex/tests/smoke_tests/smoke_tests.yml new file mode 100644 index 00000000..b67c194f --- /dev/null +++ b/apex/tests/smoke_tests/smoke_tests.yml @@ -0,0 +1,3 @@ +--- +- include: prepare_undercloud.yml +- include: execute_tests.yml \ No newline at end of file diff --git a/apex/tests/test_apex_clean.py b/apex/tests/test_apex_clean.py new file mode 100644 index 00000000..d0b87917 --- /dev/null +++ b/apex/tests/test_apex_clean.py @@ -0,0 +1,41 @@ +############################################################################## +# Copyright (c) 2016 Tim Rozet (Red Hat) +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import mock +import pyipmi +import pyipmi.chassis +from mock import patch +from nose import tools + +from apex import clean_nodes + + +class TestClean(object): + @classmethod + def setup_class(klass): + """This method is run once for each class before any tests are run""" + + @classmethod + def teardown_class(klass): + """This method is run once for each class _after_ all tests are run""" + + def setUp(self): + """This method is run once before _each_ test method is executed""" + + def teardown(self): + """This method is run once after _each_ test method is executed""" + + def test_clean(self): + with mock.patch.object(pyipmi.Session, 'establish') as mock_method: + with patch.object(pyipmi.chassis.Chassis, + 'chassis_control_power_down') as mock_method2: + clean_nodes('apex/tests/config/inventory.yaml') + + tools.assert_equal(mock_method.call_count, 5) + tools.assert_equal(mock_method2.call_count, 5) diff --git a/apex/tests/test_apex_common_utils.py b/apex/tests/test_apex_common_utils.py new file mode 100644 index 00000000..357ad1b0 --- /dev/null +++ b/apex/tests/test_apex_common_utils.py @@ -0,0 +1,59 @@ +############################################################################## +# Copyright (c) 2016 Dan Radez (Red Hat) +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import ipaddress +import nose.tools +import os + +from apex.common import utils +from apex.settings.network_settings import NetworkSettings +from apex.tests.constants import ( + TEST_CONFIG_DIR, + TEST_PLAYBOOK_DIR) + +NET_SETS = os.path.join(TEST_CONFIG_DIR, 'network', 'network_settings.yaml') + + +class TestCommonUtils(object): + @classmethod + def setup_class(klass): + """This method is run once for each class before any tests are run""" + + @classmethod + def teardown_class(klass): + """This method is run once for each class _after_ all tests are run""" + + def setUp(self): + """This method is run once before _each_ test method is executed""" + + def teardown(self): + """This method is run once after _each_ test method is executed""" + + def test_str2bool(self): + nose.tools.assert_equal(utils.str2bool(True), True) + nose.tools.assert_equal(utils.str2bool(False), False) + nose.tools.assert_equal(utils.str2bool("True"), True) + nose.tools.assert_equal(utils.str2bool("YES"), True) + + def test_parse_yaml(self): + nose.tools.assert_is_instance(utils.parse_yaml(NET_SETS), dict) + + def test_dict_to_string(self): + net_settings = NetworkSettings(NET_SETS) + output = utils.dict_objects_to_str(net_settings) + nose.tools.assert_is_instance(output, dict) + for k, v in output.items(): + nose.tools.assert_is_instance(k, str) + nose.tools.assert_not_is_instance(v, ipaddress.IPv4Address) + + def test_run_ansible(self): + playbook = 'apex/tests/playbooks/test_playbook.yaml' + nose.tools.assert_equal( + utils.run_ansible(None, os.path.join(playbook), + dry_run=True), None) diff --git a/apex/tests/test_apex_deploy_settings.py b/apex/tests/test_apex_deploy_settings.py new file mode 100644 index 00000000..312c1f3a --- /dev/null +++ b/apex/tests/test_apex_deploy_settings.py @@ -0,0 +1,101 @@ +############################################################################## +# Copyright (c) 2016 Dan Radez (Red Hat) +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# https://docs.python.org/3/library/io.html +import os +import tempfile + +from nose.tools import assert_equal +from nose.tools import assert_is_instance +from nose.tools import assert_raises + +from apex.settings.deploy_settings import DeploySettings +from apex.settings.deploy_settings import DeploySettingsException +from apex.tests.constants import TEST_CONFIG_DIR + +deploy_files = ('deploy_settings.yaml', + 'os-nosdn-nofeature-noha.yaml', + 'os-nosdn-ovs_dpdk-noha.yaml', + 'os-ocl-nofeature-ha.yaml', + 'os-odl-bgpvpn-ha.yaml', + 'os-odl-bgpvpn-noha.yaml', + 'os-odl-nofeature-ha.yaml', + 'os-nosdn-nofeature-ha.yaml', + 'os-nosdn-ovs_dpdk-ha.yaml', + 'os-nosdn-performance-ha.yaml', + 'os-odl-nofeature-ha.yaml', + 'os-onos-nofeature-ha.yaml', + 'os-onos-sfc-ha.yaml') + +test_deploy_content = ( + 'global_params:', + 'deploy_options: string', + """deploy_options: string +global_params:""", + """global_params: +deploy_options: + error: error +""", + """global_params: +deploy_options: + performance: string +""", + """global_params: +deploy_options: + dataplane: invalid +""", + """global_params: +deploy_options: + performance: + Controller: + error: error +""", + """global_params: +deploy_options: + performance: + InvalidRole: + error: error +""",) + + +class TestIpUtils(object): + @classmethod + def setup_class(klass): + """This method is run once for each class before any tests are run""" + + @classmethod + def teardown_class(klass): + """This method is run once for each class _after_ all tests are run""" + + def setUp(self): + """This method is run once before _each_ test method is executed""" + + def teardown(self): + """This method is run once after _each_ test method is executed""" + + def test_init(self): + for f in deploy_files: + ds = DeploySettings(os.path.join(TEST_CONFIG_DIR, 'deploy', f)) + ds = DeploySettings(ds) + + def test__validate_settings(self): + for c in test_deploy_content: + try: + f = tempfile.NamedTemporaryFile(mode='w') + f.write(c) + f.flush() + assert_raises(DeploySettingsException, + DeploySettings, f.name) + finally: + f.close() + + def test_exception(self): + e = DeploySettingsException("test") + print(e) + assert_is_instance(e, DeploySettingsException) diff --git a/apex/tests/test_apex_inventory.py b/apex/tests/test_apex_inventory.py new file mode 100644 index 00000000..ed95c53c --- /dev/null +++ b/apex/tests/test_apex_inventory.py @@ -0,0 +1,69 @@ +############################################################################## +# Copyright (c) 2016 Dan Radez (Red Hat) +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import os +import sys +from io import StringIO + +from nose.tools import assert_equal +from nose.tools import assert_is_instance +from nose.tools import assert_raises +from nose.tools import assert_regexp_matches + +from apex import Inventory +from apex.inventory.inventory import InventoryException +from apex.tests.constants import TEST_CONFIG_DIR + +inventory_files = ('intel_pod2_settings.yaml', + 'nokia_pod1_settings.yaml', + 'pod_example_settings.yaml') + +files_dir = os.path.join(TEST_CONFIG_DIR, 'inventory') + + +class TestInventory(object): + @classmethod + def setup_class(klass): + """This method is run once for each class before any tests are run""" + + @classmethod + def teardown_class(klass): + """This method is run once for each class _after_ all tests are run""" + + def setUp(self): + """This method is run once before _each_ test method is executed""" + + def teardown(self): + """This method is run once after _each_ test method is executed""" + + def test_init(self): + for f in inventory_files: + i = Inventory(os.path.join(files_dir, f)) + assert_equal(i.dump_instackenv_json(), None) + + # test virtual + i = Inventory(i, virtual=True) + assert_equal(i.dump_instackenv_json(), None) + + # Remove nodes to violate HA node count + while len(i['nodes']) >= 5: + i['nodes'].pop() + assert_raises(InventoryException, + Inventory, i) + + # Remove nodes to violate non-HA node count + while len(i['nodes']) >= 2: + i['nodes'].pop() + assert_raises(InventoryException, + Inventory, i, ha=False) + + def test_exception(self): + e = InventoryException("test") + print(e) + assert_is_instance(e, InventoryException) diff --git a/apex/tests/test_apex_ip_utils.py b/apex/tests/test_apex_ip_utils.py new file mode 100644 index 00000000..04a1b2bb --- /dev/null +++ b/apex/tests/test_apex_ip_utils.py @@ -0,0 +1,132 @@ +############################################################################## +# Copyright (c) 2016 Dan Radez (Red Hat) +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import ipaddress +import re +from ipaddress import IPv4Address +from ipaddress import ip_network + +from nose.tools import assert_equal +from nose.tools import assert_false +from nose.tools import assert_is_instance +from nose.tools import assert_raises +from nose.tools import assert_regexp_matches +from nose.tools import assert_true + +from apex.network.ip_utils import IPUtilsException +from apex.network.ip_utils import _validate_ip_range +from apex.network.ip_utils import find_gateway +from apex.network.ip_utils import get_interface +from apex.network.ip_utils import get_ip +from apex.network.ip_utils import get_ip_range + +ip4_pattern = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') +ip4_range_pattern = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3},\d{1,' + '3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') + + +def get_default_gateway_linux(): + """Read the default gateway directly from /proc.""" + with open("/proc/net/route") as fh: + for line in fh: + fields = line.strip().split() + if fields[2] not in ('00000000', 'Gateway'): + return fields[0] + + +class TestIpUtils(object): + @classmethod + def setup_class(klass): + """This method is run once for each class before any tests are run""" + klass.iface_name = get_default_gateway_linux() + iface = get_interface(klass.iface_name) + klass.iface = iface + + @classmethod + def teardown_class(klass): + """This method is run once for each class _after_ all tests are run""" + + def setUp(self): + """This method is run once before _each_ test method is executed""" + + def teardown(self): + """This method is run once after _each_ test method is executed""" + + def test_get_interface(self): + assert_equal(get_interface(''), None) + assert_equal(get_interface('notreal'), None) + assert_is_instance(get_interface(self.iface_name, + address_family=4), + IPv4Address) + # can't enable this until there's a v6 address on the ci hosts + # assert_is_instance(get_interface( + # self.iface_name, + # address_family=6), IPv6Address) + assert_raises(IPUtilsException, + get_interface, self.iface_name, 0) + + def test_find_gateway(self): + assert_is_instance(find_gateway(self.iface), str) + iface_virbr0 = get_interface('virbr0') + assert_equal(find_gateway(iface_virbr0), None) + + def test_get_ip(self): + cidr = ipaddress.ip_network("10.10.10.0/24") + assert_equal(get_ip(1, cidr=cidr), "10.10.10.1") + assert_raises(IPUtilsException, get_ip, 1000, interface=self.iface) + assert_regexp_matches(get_ip(1, interface=self.iface), ip4_pattern) + assert_raises(IPUtilsException, get_ip, 1) + + def test_get_ip_range_raises(self): + assert_raises(IPUtilsException, get_ip_range) + assert_raises(IPUtilsException, get_ip_range, interface=self.iface) + + def test_get_ip_range_with_interface(self): + assert_regexp_matches(get_ip_range(interface=self.iface, + start_offset=1, end_offset=20), + ip4_range_pattern) + assert_regexp_matches(get_ip_range(interface=self.iface, + start_offset=1, count=10), + ip4_range_pattern) + assert_regexp_matches(get_ip_range(interface=self.iface, end_offset=20, + count=10), ip4_range_pattern) + + def test_get_ip_range_with_cidr(self): + cidr = ip_network('10.10.10.0/24') + assert_raises(IPUtilsException, get_ip_range, cidr=cidr) + assert_regexp_matches(get_ip_range(cidr=cidr, start_offset=1, + end_offset=20), ip4_pattern) + assert_regexp_matches(get_ip_range(cidr=cidr, start_offset=1, + count=10), ip4_pattern) + assert_regexp_matches(get_ip_range(cidr=cidr, end_offset=20, + count=10), ip4_pattern) + + def test__validate_ip_range(self): + cidr = ip_network('10.10.10.0/24') + assert_true(_validate_ip_range( + start_ip=ipaddress.IPv4Address('10.10.10.1'), + end_ip=ipaddress.IPv4Address('10.10.10.10'), + cidr=cidr)) + assert_false(_validate_ip_range( + start_ip=ipaddress.IPv4Address('10.10.10.10'), + end_ip=ipaddress.IPv4Address('10.10.10.1'), + cidr=cidr)) + assert_false(_validate_ip_range( + start_ip=ipaddress.IPv4Address('10.10.0.1'), + end_ip=ipaddress.IPv4Address('10.10.10.10'), + cidr=cidr)) + assert_false(_validate_ip_range( + start_ip=ipaddress.IPv4Address('10.10.10.1'), + end_ip=ipaddress.IPv4Address('10.10.11.10'), + cidr=cidr)) + + def test_exception(self): + e = IPUtilsException("test") + print(e) + assert_is_instance(e, IPUtilsException) diff --git a/apex/tests/test_apex_network_environment.py b/apex/tests/test_apex_network_environment.py new file mode 100644 index 00000000..5047adbb --- /dev/null +++ b/apex/tests/test_apex_network_environment.py @@ -0,0 +1,169 @@ +############################################################################## +# Copyright (c) 2016 Dan Radez (Red Hat) +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import os + +from copy import copy + +from nose.tools import assert_equal +from nose.tools import assert_is_instance +from nose.tools import assert_not_equal +from nose.tools import assert_raises + +from apex.common.constants import ( + EXTERNAL_NETWORK, + TENANT_NETWORK, + STORAGE_NETWORK, + API_NETWORK, + NET_ENV_FILE) +from apex import NetworkEnvironment +from apex.network.network_environment import NetworkEnvException +from apex import NetworkSettings +from apex.tests.constants import TEST_CONFIG_DIR +from apex.tests.constants import TEST_BUILD_DIR + + +class TestNetworkEnvironment(object): + @classmethod + def setup_class(klass): + """This method is run once for each class before any tests are run""" + klass.ns = NetworkSettings( + os.path.join(TEST_CONFIG_DIR, 'network/network_settings.yaml')) + klass.ns_vlans = NetworkSettings( + os.path.join(TEST_CONFIG_DIR, + 'network/network_settings_vlans.yaml')) + klass.ns_ipv6 = NetworkSettings( + os.path.join(TEST_CONFIG_DIR, 'network/network_settings_v6.yaml')) + + @classmethod + def teardown_class(klass): + """This method is run once for each class _after_ all tests are run""" + + def setUp(self): + """This method is run once before _each_ test method is executed""" + + def teardown(self): + """This method is run once after _each_ test method is executed""" + + def test_init(self): + assert_raises(NetworkEnvException, NetworkEnvironment, + None, os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + + def test_netenv_settings_external_network_vlans(self): + # test vlans + ne = NetworkEnvironment(self.ns_vlans, + os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + assert_equal(ne['parameter_defaults']['NeutronExternalNetworkBridge'], + '""') + assert_equal(ne['parameter_defaults']['ExternalNetworkVlanID'], 501) + + def test_netenv_settings_external_network_ipv6(self): + # Test IPv6 + ne = NetworkEnvironment(self.ns_ipv6, + os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + regstr = ne['resource_registry']['OS::TripleO::Network::External'] + assert_equal(regstr.split('/')[-1], 'external_v6.yaml') + + def test_netenv_settings_external_network_removed(self): + ns = copy(self.ns) + # Test removing EXTERNAL_NETWORK + ns.enabled_network_list.remove(EXTERNAL_NETWORK) + ne = NetworkEnvironment(ns, os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + regstr = ne['resource_registry']['OS::TripleO::Network::External'] + assert_equal(regstr.split('/')[-1], 'OS::Heat::None') + + def test_netenv_settings_tenant_network_vlans(self): + # test vlans + ne = NetworkEnvironment(self.ns_vlans, + os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + assert_equal(ne['parameter_defaults']['TenantNetworkVlanID'], 401) + +# Apex is does not support v6 tenant networks +# Though there is code that would fire if a +# v6 cidr was passed in, just uncomment this to +# cover that code +# def test_netenv_settings_tenant_network_v6(self): +# # Test IPv6 +# ne = NetworkEnvironment(self.ns_ipv6, +# '../build/network-environment.yaml') +# regstr = ne['resource_registry'][next(iter(TENANT_RESOURCES.keys()))] +# assert_equal(regstr.split('/')[-1], 'tenant_v6.yaml') + + def test_netenv_settings_tenant_network_removed(self): + ns = copy(self.ns) + # Test removing TENANT_NETWORK + ns.enabled_network_list.remove(TENANT_NETWORK) + ne = NetworkEnvironment(ns, os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + regstr = ne['resource_registry']['OS::TripleO::Network::Tenant'] + assert_equal(regstr.split('/')[-1], 'OS::Heat::None') + + def test_netenv_settings_storage_network_vlans(self): + # test vlans + ne = NetworkEnvironment(self.ns_vlans, + os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + assert_equal(ne['parameter_defaults']['StorageNetworkVlanID'], 201) + + def test_netenv_settings_storage_network_v6(self): + # Test IPv6 + ne = NetworkEnvironment(self.ns_ipv6, + os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + regstr = ne['resource_registry']['OS::TripleO::Network::Storage'] + assert_equal(regstr.split('/')[-1], 'storage_v6.yaml') + + def test_netenv_settings_storage_network_removed(self): + ns = copy(self.ns) + # Test removing STORAGE_NETWORK + ns.enabled_network_list.remove(STORAGE_NETWORK) + ne = NetworkEnvironment(ns, os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + regstr = ne['resource_registry']['OS::TripleO::Network::Storage'] + assert_equal(regstr.split('/')[-1], 'OS::Heat::None') + + def test_netenv_settings_api_network_v4(self): + ns = copy(self.ns_vlans) + ns['networks'][API_NETWORK]['enabled'] = True + ns['networks'][API_NETWORK]['cidr'] = '10.11.12.0/24' + ns = NetworkSettings(ns) + # test vlans + ne = NetworkEnvironment(ns, os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + assert_equal(ne['parameter_defaults']['InternalApiNetworkVlanID'], 101) + + def test_netenv_settings_api_network_vlans(self): + ns = copy(self.ns_vlans) + ns['networks'][API_NETWORK]['enabled'] = True + ns = NetworkSettings(ns) + # test vlans + ne = NetworkEnvironment(ns, os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + assert_equal(ne['parameter_defaults']['InternalApiNetworkVlanID'], 101) + + def test_netenv_settings_api_network_v6(self): + # Test IPv6 + ne = NetworkEnvironment(self.ns_ipv6, + os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + regstr = ne['resource_registry']['OS::TripleO::Network::InternalApi'] + assert_equal(regstr.split('/')[-1], 'internal_api_v6.yaml') + + def test_netenv_settings_api_network_removed(self): + ns = copy(self.ns) + # API_NETWORK is not in the default network settings file + ne = NetworkEnvironment(ns, os.path.join(TEST_BUILD_DIR, NET_ENV_FILE)) + regstr = ne['resource_registry']['OS::TripleO::Network::InternalApi'] + assert_equal(regstr.split('/')[-1], 'OS::Heat::None') + + def test_numa_configs(self): + ne = NetworkEnvironment(self.ns, + os.path.join(TEST_BUILD_DIR, NET_ENV_FILE), + compute_pre_config=True, + controller_pre_config=True) + assert_is_instance(ne, dict) + assert_not_equal(ne, {}) + + def test_exception(self): + e = NetworkEnvException("test") + print(e) + assert_is_instance(e, NetworkEnvException) diff --git a/apex/tests/test_apex_network_settings.py b/apex/tests/test_apex_network_settings.py new file mode 100644 index 00000000..adff8cff --- /dev/null +++ b/apex/tests/test_apex_network_settings.py @@ -0,0 +1,156 @@ +############################################################################## +# Copyright (c) 2016 Dan Radez (Red Hat) +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import os + +from nose.tools import ( + assert_equal, + assert_is_instance, + assert_raises +) + +from apex.common.constants import ( + EXTERNAL_NETWORK, + STORAGE_NETWORK, + ADMIN_NETWORK, +) +from apex import NetworkSettings +from apex.settings.network_settings import NetworkSettingsException +from apex.tests.constants import TEST_CONFIG_DIR + +files_dir = os.path.join(TEST_CONFIG_DIR, 'network') + + +class TestNetworkSettings(object): + @classmethod + def setup_class(klass): + """This method is run once for each class before any tests are run""" + + @classmethod + def teardown_class(klass): + """This method is run once for each class _after_ all tests are run""" + + def setUp(self): + """This method is run once before _each_ test method is executed""" + + def teardown(self): + """This method is run once after _each_ test method is executed""" + + def test_init(self): + assert_is_instance( + NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')), + NetworkSettings) + + def test_init_vlans(self): + assert_is_instance( + NetworkSettings(os.path.join(files_dir, + 'network_settings_vlans.yaml')), + NetworkSettings) + +# TODO, v6 test is stuck + # def test_init_v6(self): + # assert_is_instance( + # NetworkSettings(files_dir+'network_settings_v6.yaml', True), + # NetworkSettings) + + def test_init_admin_disabled_or_missing(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + # remove admin, apex section will re-add it + ns['networks'].pop('admin', None) + assert_raises(NetworkSettingsException, NetworkSettings, ns) + # remove admin and apex + ns.pop('apex', None) + ns['networks'].pop('admin', None) + assert_raises(NetworkSettingsException, NetworkSettings, ns) + + def test_init_collapse_storage(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + # remove storage + ns['networks'].pop('storage', None) + assert_is_instance(NetworkSettings(ns), NetworkSettings) + + def test_init_missing_dns_domain(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + # remove storage + ns.pop('dns-domain', None) + assert_is_instance(NetworkSettings(ns), NetworkSettings) + + def test_get_network_settings(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + assert_is_instance(ns, NetworkSettings) + for role in ['controller', 'compute']: + nic_index = 0 + print(ns.nics) + for network in ns.enabled_network_list: + nic = 'eth' + str(nic_index) + assert_equal(ns.nics[role][network], nic) + nic_index += 1 + + def test_get_enabled_networks(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + assert_is_instance(ns.enabled_network_list, list) + + def test_invalid_nic_members(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + storage_net_nicmap = ns['networks'][STORAGE_NETWORK]['nic_mapping'] + # set duplicate nic + storage_net_nicmap['controller']['members'][0] = 'eth0' + assert_raises(NetworkSettingsException, NetworkSettings, ns) + # remove nic members + storage_net_nicmap['controller']['members'] = [] + assert_raises(NetworkSettingsException, NetworkSettings, ns) + + def test_missing_vlan(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + storage_net_nicmap = ns['networks'][STORAGE_NETWORK]['nic_mapping'] + # remove vlan from storage net + storage_net_nicmap['compute'].pop('vlan', None) + assert_is_instance(NetworkSettings(ns), NetworkSettings) + +# TODO +# need to manipulate interfaces some how +# maybe for ip_utils to return something to pass this +# def test_admin_auto_detect(self): +# ns = NetworkSettings(files_dir+'network_settings.yaml') +# # remove cidr to force autodetection +# ns['networks'][ADMIN_NETWORK].pop('cidr', None) +# assert_is_instance(NetworkSettings(ns), NetworkSettings) + + def test_admin_fail_auto_detect(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + # remove cidr and installer_vm to fail autodetect + ns['networks'][ADMIN_NETWORK].pop('cidr', None) + ns['networks'][ADMIN_NETWORK].pop('installer_vm', None) + assert_raises(NetworkSettingsException, NetworkSettings, ns) + + def test_exception(self): + e = NetworkSettingsException("test") + print(e) + assert_is_instance(e, NetworkSettingsException) + + def test_config_ip(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + # set the provisioner ip to None to force _gen_ip to generate one + ns['networks'][ADMIN_NETWORK]['installer_vm']['ip'] = None + ns['networks'][EXTERNAL_NETWORK][0]['installer_vm']['ip'] = None + # Now rebuild network settings object and check for repopulated values + ns = NetworkSettings(ns) + assert_equal(ns['networks'][ADMIN_NETWORK]['installer_vm']['ip'], + '192.0.2.1') + assert_equal(ns['networks'][EXTERNAL_NETWORK][0]['installer_vm']['ip'], + '192.168.37.1') + + def test_config_gateway(self): + ns = NetworkSettings(os.path.join(files_dir, 'network_settings.yaml')) + # set the gateway ip to None to force _config_gateway to generate one + ns['networks'][EXTERNAL_NETWORK][0]['gateway'] = None + # Now rebuild network settings object and check for a repopulated value + ns = NetworkSettings(ns) + assert_equal(ns['networks'][EXTERNAL_NETWORK][0]['gateway'], + '192.168.37.1') diff --git a/apex/undercloud/__init__.py b/apex/undercloud/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apex/undercloud/undercloud.py b/apex/undercloud/undercloud.py new file mode 100644 index 00000000..7efc2cb3 --- /dev/null +++ b/apex/undercloud/undercloud.py @@ -0,0 +1,206 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import libvirt +import logging +import os +import shutil +import time + +from apex.virtual import virtual_utils as virt_utils +from apex.virtual import configure_vm as vm_lib +from apex.common import constants +from apex.common import utils + + +class ApexUndercloudException(Exception): + pass + + +class Undercloud: + """ + This class represents an Apex Undercloud VM + """ + def __init__(self, image_path, root_pw=None, external_network=False): + self.ip = None + self.root_pw = root_pw + self.external_net = external_network + self.volume = os.path.join(constants.LIBVIRT_VOLUME_PATH, + 'undercloud.qcow2') + self.image_path = image_path + self.vm = None + if Undercloud._get_vm(): + logging.error("Undercloud VM already exists. Please clean " + "before creating") + raise ApexUndercloudException("Undercloud VM already exists!") + self.create() + + @staticmethod + def _get_vm(): + conn = libvirt.open('qemu:///system') + try: + vm = conn.lookupByName('undercloud') + return vm + except libvirt.libvirtError: + logging.debug("No undercloud VM exists") + + def create(self): + networks = ['admin'] + if self.external_net: + networks.append('external') + self.vm = vm_lib.create_vm(name='undercloud', + image=self.volume, + baremetal_interfaces=networks, + direct_boot='overcloud-full', + kernel_args=['console=ttyS0', + 'root=/dev/sda'], + default_network=True) + self.setup_volumes() + self.inject_auth() + + def _set_ip(self): + ip_out = self.vm.interfaceAddresses( + libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, 0) + if ip_out: + for (name, val) in ip_out.items(): + for ipaddr in val['addrs']: + if ipaddr['type'] == libvirt.VIR_IP_ADDR_TYPE_IPV4: + self.ip = ipaddr['addr'] + return True + + def start(self): + """ + Start Undercloud VM + :return: None + """ + if self.vm.isActive(): + logging.info("Undercloud already started") + else: + logging.info("Starting undercloud") + self.vm.create() + # give 10 seconds to come up + time.sleep(10) + # set IP + for x in range(5): + if self._set_ip(): + logging.info("Undercloud started. IP Address: {}".format( + self.ip)) + break + logging.debug("Did not find undercloud IP in {} " + "attempts...".format(x)) + time.sleep(10) + else: + logging.error("Cannot find IP for Undercloud") + raise ApexUndercloudException( + "Unable to find IP for undercloud. Check if VM booted " + "correctly") + + def configure(self, net_settings, playbook, apex_temp_dir): + """ + Configures undercloud VM + :return: + """ + # TODO(trozet): If undercloud install fails we can add a retry + logging.info("Configuring Undercloud...") + # run ansible + ansible_vars = Undercloud.generate_config(net_settings) + ansible_vars['apex_temp_dir'] = apex_temp_dir + utils.run_ansible(ansible_vars, playbook, host=self.ip, user='stack') + logging.info("Undercloud installed!") + + def setup_volumes(self): + for img_file in ('overcloud-full.vmlinuz', 'overcloud-full.initrd', + 'undercloud.qcow2'): + src_img = os.path.join(self.image_path, img_file) + dest_img = os.path.join(constants.LIBVIRT_VOLUME_PATH, img_file) + if not os.path.isfile(src_img): + raise ApexUndercloudException( + "Required source file does not exist:{}".format(src_img)) + if os.path.exists(dest_img): + os.remove(dest_img) + shutil.copyfile(src_img, dest_img) + + # TODO(trozet):check if resize needed right now size is 50gb + # there is a lib called vminspect which has some dependencies and is + # not yet available in pip. Consider switching to this lib later. + # execute ansible playbook + + def inject_auth(self): + virt_ops = list() + # virt-customize keys/pws + if self.root_pw: + pw_op = "password:{}".format(self.root_pw) + virt_ops.append({constants.VIRT_PW: pw_op}) + # ssh key setup + virt_ops.append({constants.VIRT_RUN_CMD: + 'mkdir -p /root/.ssh'}) + virt_ops.append({constants.VIRT_UPLOAD: + '/root/.ssh/id_rsa.pub:/root/.ssh/authorized_keys'}) + run_cmds = [ + 'chmod 600 /root/.ssh/authorized_keys', + 'restorecon /root/.ssh/authorized_keys', + 'cp /root/.ssh/authorized_keys /home/stack/.ssh/', + 'chown stack:stack /home/stack/.ssh/authorized_keys', + 'chmod 600 /home/stack/.ssh/authorized_keys' + ] + for cmd in run_cmds: + virt_ops.append({constants.VIRT_RUN_CMD: cmd}) + virt_utils.virt_customize(virt_ops, self.volume) + + @staticmethod + def generate_config(ns): + """ + Generates a dictionary of settings for configuring undercloud + :param ns: network settings to derive undercloud settings + :return: dictionary of settings + """ + + ns_admin = ns['networks']['admin'] + intro_range = ns['apex']['networks']['admin']['introspection_range'] + config = dict() + config['undercloud_config'] = [ + "enable_ui false", + "undercloud_update_packages false", + "undercloud_debug false", + "undercloud_hostname undercloud.{}".format(ns['dns-domain']), + "local_ip {}/{}".format(str(ns_admin['installer_vm']['ip']), + str(ns_admin['cidr']).split('/')[1]), + "network_gateway {}".format(str(ns_admin['installer_vm']['ip'])), + "network_cidr {}".format(str(ns_admin['cidr'])), + "dhcp_start {}".format(str(ns_admin['dhcp_range'][0])), + "dhcp_end {}".format(str(ns_admin['dhcp_range'][1])), + "inspection_iprange {}".format(','.join(intro_range)) + ] + + config['ironic_config'] = [ + "disk_utils iscsi_verify_attempts 30", + "disk_partitioner check_device_max_retries 40" + ] + + config['nova_config'] = [ + "dns_domain {}".format(ns['dns-domain']), + "dhcp_domain {}".format(ns['dns-domain']) + ] + + config['neutron_config'] = [ + "dns_domain {}".format(ns['dns-domain']), + ] + # FIXME(trozet): possible bug here with not using external network + ns_external = ns['networks']['external'][0] + config['external_network'] = { + "vlan": ns_external['installer_vm']['vlan'], + "ip": ns_external['installer_vm']['ip'], + "prefix": str(ns_external['cidr']).split('/')[1], + "enabled": ns_external['enabled'] + } + + # FIXME (trozet): for now hardcoding aarch64 to false + config['aarch64'] = False + + return config diff --git a/apex/virtual/__init__.py b/apex/virtual/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apex/virtual/configure_vm.py b/apex/virtual/configure_vm.py new file mode 100755 index 00000000..3af7d1e8 --- /dev/null +++ b/apex/virtual/configure_vm.py @@ -0,0 +1,206 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import libvirt +import logging +import math +import os +import random + +MAX_NUM_MACS = math.trunc(0xff / 2) + + +def generate_baremetal_macs(count=1): + """Generate an Ethernet MAC address suitable for baremetal testing.""" + # NOTE(dprince): We generate our own bare metal MAC address's here + # instead of relying on libvirt so that we can ensure the + # locally administered bit is set low. (The libvirt default is + # to set the 2nd MSB high.) This effectively allows our + # fake baremetal VMs to more accurately behave like real hardware + # and fixes issues with bridge/DHCP configurations which rely + # on the fact that bridges assume the MAC address of the lowest + # attached NIC. + # MACs generated for a given machine will also be in sequential + # order, which matches how most BM machines are laid out as well. + # Additionally we increment each MAC by two places. + macs = [] + + if count > MAX_NUM_MACS: + raise ValueError("The MAX num of MACS supported is %i." % MAX_NUM_MACS) + + base_nums = [0x00, + random.randint(0x00, 0xff), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + base_mac = ':'.join(map(lambda x: "%02x" % x, base_nums)) + + start = random.randint(0x00, 0xff) + if (start + (count * 2)) > 0xff: + # leave room to generate macs in sequence + start = 0xff - count * 2 + for num in range(0, count * 2, 2): + mac = start + num + macs.append(base_mac + ":" + ("%02x" % mac)) + return macs + + +def create_vm_storage(domain, vol_path='/var/lib/libvirt/images'): + volume_name = domain + '.qcow2' + stgvol_xml = """ + + {} + 0 + 41 + + + {} + + 107 + 107 + 0744 + + + + """.format(volume_name, os.path.join(vol_path, volume_name)) + + conn = libvirt.open('qemu:///system') + pool = conn.storagePoolLookupByName('default') + if pool is None: + raise Exception("Default libvirt storage pool missing") + # TODO(trozet) create default storage pool + + if pool.isActive() == 0: + pool.create() + try: + vol = pool.storageVolLookupByName(volume_name) + vol.wipe(0) + vol.delete(0) + except libvirt.libvirtError as e: + if e.get_error_code() != libvirt.VIR_ERR_NO_STORAGE_VOL: + raise + new_vol = pool.createXML(stgvol_xml) + if new_vol is None: + raise Exception("Unable to create new volume") + logging.debug("Created new storage volume: {}".format(volume_name)) + + +def create_vm(name, image, diskbus='sata', baremetal_interfaces=['admin'], + arch='x86_64', engine='kvm', memory=8192, bootdev='network', + cpus=4, nic_driver='virtio', macs=[], direct_boot=None, + kernel_args=None, default_network=False, + template_dir='/usr/share/opnfv-apex'): + # TODO(trozet): fix name here to be image since it is full path of qcow2 + create_vm_storage(name) + with open(os.path.join(template_dir, 'domain.xml'), 'r') as f: + source_template = f.read() + imagefile = os.path.realpath(image) + memory = int(memory) * 1024 + params = { + 'name': name, + 'imagefile': imagefile, + 'engine': engine, + 'arch': arch, + 'memory': str(memory), + 'cpus': str(cpus), + 'bootdev': bootdev, + 'network': '', + 'enable_serial_console': '', + 'direct_boot': '', + 'kernel_args': '', + 'user_interface': '', + } + + # Configure the bus type for the target disk device + params['diskbus'] = diskbus + nicparams = { + 'nicdriver': nic_driver, + } + if default_network: + params['network'] = """ + + + + + """ % nicparams + else: + params['network'] = '' + while len(macs) < len(baremetal_interfaces): + macs += generate_baremetal_macs(1) + + params['bm_network'] = "" + for bm_interface, mac in zip(baremetal_interfaces, macs): + bm_interface_params = { + 'bminterface': bm_interface, + 'bmmacaddress': mac, + 'nicdriver': nic_driver, + } + params['bm_network'] += """ + + + + + + """ % bm_interface_params + + params['enable_serial_console'] = """ + + + + + + + """ + if direct_boot: + params['direct_boot'] = """ + /var/lib/libvirt/images/%(direct_boot)s.vmlinuz + /var/lib/libvirt/images/%(direct_boot)s.initrd + """ % {'direct_boot': direct_boot} + if kernel_args: + params['kernel_args'] = """ + %s + """ % ' '.join(kernel_args) + + if arch == 'aarch64': + + params['direct_boot'] += """ + /usr/share/AAVMF/AAVMF_CODE.fd + /var/lib/libvirt/qemu/nvram/centos7.0_VARS.fd + """ + params['user_interface'] = """ + +
+ + + + + + + + + +
+ + """ + else: + params['user_interface'] = """ + + + + """ + + libvirt_template = source_template % params + logging.debug("libvirt template is {}".format(libvirt_template)) + conn = libvirt.open('qemu:///system') + vm = conn.defineXML(libvirt_template) + logging.info("Created machine %s with UUID %s" % (name, vm.UUIDString())) + return vm diff --git a/apex/virtual/virtual_utils.py b/apex/virtual/virtual_utils.py new file mode 100644 index 00000000..5ebb0582 --- /dev/null +++ b/apex/virtual/virtual_utils.py @@ -0,0 +1,140 @@ +############################################################################## +# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import copy +import iptc +import logging +import os +import pprint +import subprocess + +from apex.common import utils +from apex.virtual import configure_vm as vm_lib +from virtualbmc import manager as vbmc_lib + +DEFAULT_RAM = 8192 +DEFAULT_PM_PORT = 6230 +DEFAULT_USER = 'admin' +DEFAULT_PASS = 'password' +DEFAULT_VIRT_IP = '192.168.122.1' + + +def generate_inventory(target_file, ha_enabled=False, num_computes=1, + controller_ram=DEFAULT_RAM, arch='x86_64', + compute_ram=DEFAULT_RAM, vcpus=4): + """ + Generates inventory file for virtual deployments + :param target_file: + :param ha_enabled: + :param num_computes: + :param controller_ram: + :param arch: + :param compute_ram: + :param vcpus: + :return: + """ + + node = {'mac_address': '', + 'ipmi_ip': DEFAULT_VIRT_IP, + 'ipmi_user': DEFAULT_USER, + 'ipmi_pass': DEFAULT_PASS, + 'pm_type': 'pxe_ipmitool', + 'pm_port': '', + 'cpu': vcpus, + 'memory': DEFAULT_RAM, + 'disk': 41, + 'arch': arch, + 'capabilities': '' + } + + inv_output = {'nodes': {}} + if ha_enabled: + num_ctrlrs = 3 + else: + num_ctrlrs = 1 + + for idx in range(num_ctrlrs + num_computes): + tmp_node = copy.deepcopy(node) + tmp_node['mac_address'] = vm_lib.generate_baremetal_macs(1)[0] + tmp_node['pm_port'] = DEFAULT_PM_PORT + idx + if idx < num_ctrlrs: + tmp_node['capabilities'] = 'profile:control' + tmp_node['memory'] = controller_ram + else: + tmp_node['capabilities'] = 'profile:compute' + tmp_node['memory'] = compute_ram + inv_output['nodes']['node{}'.format(idx)] = copy.deepcopy(tmp_node) + + utils.dump_yaml(inv_output, target_file) + + logging.info('Virtual environment file created: {}'.format(target_file)) + + +def host_setup(node): + """ + Handles configuring vmbc and firewalld/iptables + :param node: dictionary of domain names and ports for ipmi + :return: + """ + vbmc_manager = vbmc_lib.VirtualBMCManager() + for name, port in node.items(): + vbmc_manager.add(username=DEFAULT_USER, password=DEFAULT_PASS, + port=port, address=DEFAULT_VIRT_IP, domain_name=name, + libvirt_uri='qemu:///system', + libvirt_sasl_password=False, + libvirt_sasl_username=False) + + # TODO(trozet): add support for firewalld + subprocess.call(['systemctl', 'stop', 'firewalld']) + + # iptables rule + rule = iptc.Rule() + rule.protocol = 'udp' + match = rule.create_match('udp') + match.dport = str(port) + rule.add_match(match) + rule.target = iptc.Target(rule, "ACCEPT") + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), "INPUT") + chain.insert_rule(rule) + try: + subprocess.check_call(['vbmc', 'start', name]) + logging.debug("Started vbmc for domain {}".format(name)) + except subprocess.CalledProcessError: + logging.error("Failed to start vbmc for {}".format(name)) + raise + logging.debug('vmbcs setup: {}'.format(vbmc_manager.list())) + + +def virt_customize(ops, target): + """ + Helper function to virt customize disks + :param ops: list of of operations and arguments + :param target: target disk to modify + :return: None + """ + logging.info("Virt customizing target disk: {}".format(target)) + virt_cmd = ['virt-customize'] + for op in ops: + for op_cmd, op_arg in op.items(): + virt_cmd.append(op_cmd) + virt_cmd.append(op_arg) + virt_cmd.append('-a') + virt_cmd.append(target) + if not os.path.isfile(target): + raise FileNotFoundError + my_env = os.environ.copy() + my_env['LIBGUESTFS_BACKEND'] = 'direct' + logging.debug("Virt-customizing with: \n{}".format(virt_cmd)) + try: + logging.debug(subprocess.check_output(virt_cmd, env=my_env, + stderr=subprocess.STDOUT)) + except subprocess.CalledProcessError as e: + logging.error("Error executing virt-customize: {}".format( + pprint.pformat(e.output))) + raise diff --git a/build/Makefile b/build/Makefile index 43eae37a..74e72657 100644 --- a/build/Makefile +++ b/build/Makefile @@ -79,7 +79,7 @@ $(RPMREL): rpmbuild --clean -ba rpm_specs/opnfv-apex-release.spec $(RPM_DIR_ARGS) -D "_release $(shell echo $(RELEASE) | tr -d '_-')" $(BUILD_DIR)/opnfv-apex-common.tar.gz: - pushd ../ && git archive --format=tar.gz --prefix=opnfv-apex-common-$(RPMVERS)/ HEAD > $(BUILD_DIR)/opnfv-apex-common.tar.gz + pushd ../ && git archive --format=tar.gz --prefix=opnfv-apex-$(RPMVERS)/ HEAD > $(BUILD_DIR)/opnfv-apex-common.tar.gz .PHONY: common-rpm-check common-rpm-check: $(BUILD_DIR)/opnfv-apex-common.tar.gz @@ -99,14 +99,7 @@ $(RPMCOM): .PHONY: python-tests python-tests: - # clean previous coverage data - rm -rf ../tests/.coverage - rm -rf ../tests/htmlcov - # run nose tests - cd ../tests && PYTHONPATH=../lib/python/ nosetests-3.4 . --with-coverage --cover-package apex --cover-package apex_python_utils --cover-html --cover-min-percentage 90 - - # generate reports - cd ../tests && coverage3 report --include '*lib/python/*' -m + tox -e py35 ####################### # PYTHON PEP8 CHECK # @@ -114,8 +107,7 @@ python-tests: .PHONY: python-pep8-check python-pep8-check: - pep8 ../lib/python - pep8 ../tests + tox -e pep8 ############# # YAMLLINT # diff --git a/build/domain.xml b/build/domain.xml new file mode 100644 index 00000000..57a67d87 --- /dev/null +++ b/build/domain.xml @@ -0,0 +1,34 @@ + + %(name)s + %(memory)s + %(cpus)s + + + hvm + + + %(direct_boot)s + %(kernel_args)s + + + + + + + + destroy + restart + restart + + + + + + + + %(network)s + %(bm_network)s + %(enable_serial_console)s + %(user_interface)s + + diff --git a/build/rpm_specs/opnfv-apex-common.spec b/build/rpm_specs/opnfv-apex-common.spec index ccb100f3..42bc42f9 100644 --- a/build/rpm_specs/opnfv-apex-common.spec +++ b/build/rpm_specs/opnfv-apex-common.spec @@ -1,7 +1,9 @@ -Name: opnfv-apex-common +%global srcname opnfv-apex + +Name: python3-%{srcname} Version: 5.0 Release: %{_release} -Summary: Scripts for OPNFV deployment using RDO Manager +Summary: Scripts for OPNFV deployment using Apex Group: System Environment License: Apache 2.0 @@ -13,23 +15,25 @@ BuildRequires: python-docutils python34-devel Requires: opnfv-apex-sdn opnfv-apex-undercloud openvswitch qemu-kvm bridge-utils libguestfs-tools libvirt-python Requires: initscripts net-tools iputils iproute iptables python34 python34-yaml python34-jinja2 python3-ipmi python2-virtualbmc Requires: ipxe-roms-qemu >= 20160127-1 +Requires: libvirt-devel %description -Scripts for OPNFV deployment using RDO Manager +Scripts for OPNFV deployment using Apex https://wiki.opnfv.org/apex %prep -%setup -q +%autosetup -n %{srcname}-%{version} %build rst2html docs/release/installation/index.rst docs/release/installation/installation-instructions.html rst2html docs/release/release-notes/release-notes.rst docs/release/release-notes/release-notes.html +%py3_build %global __python %{__python3} %install mkdir -p %{buildroot}%{_bindir}/ -install ci/deploy.sh %{buildroot}%{_bindir}/opnfv-deploy +%py3_install install ci/clean.sh %{buildroot}%{_bindir}/opnfv-clean install ci/util.sh %{buildroot}%{_bindir}/opnfv-util @@ -37,67 +41,10 @@ mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d/ install build/bash_completion_apex %{buildroot}%{_sysconfdir}/bash_completion.d/apex mkdir -p %{buildroot}%{_sysconfdir}/opnfv-apex/ -install config/deploy/os-nosdn-nofeature-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-nofeature-noha.yaml -install config/deploy/os-nosdn-bar-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-bar-noha.yaml -install config/deploy/os-nosdn-bar-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-bar-ha.yaml -install config/deploy/os-nosdn-fdio-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-fdio-noha.yaml -install config/deploy/os-nosdn-fdio-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-fdio-ha.yaml -install config/deploy/os-nosdn-ovs_dpdk-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-ovs_dpdk-noha.yaml -install config/deploy/os-nosdn-nofeature-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-nofeature-ha.yaml -install config/deploy/os-nosdn-performance-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-performance-ha.yaml -install config/deploy/os-nosdn-ovs_dpdk-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-ovs_dpdk-ha.yaml -install config/deploy/os-nosdn-kvm-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-kvm-ha.yaml -install config/deploy/os-nosdn-kvm-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-kvm-noha.yaml -install config/deploy/os-nosdn-kvm_ovs_dpdk-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-kvm_ovs_dpdk-ha.yaml -install config/deploy/os-nosdn-kvm_ovs_dpdk-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-nosdn-kvm_ovs_dpdk-noha.yaml -install config/deploy/os-odl-bgpvpn-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-bgpvpn-ha.yaml -install config/deploy/os-odl-bgpvpn-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-bgpvpn-noha.yaml -install config/deploy/os-odl-sfc-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-sfc-ha.yaml -install config/deploy/os-odl-sfc-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-sfc-noha.yaml -install config/deploy/os-odl-fdio-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-fdio-noha.yaml -install config/deploy/os-odl_netvirt-fdio-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl_netvirt-fdio-noha.yaml -install config/deploy/os-odl-fdio-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-fdio-ha.yaml -install config/deploy/os-odl-fdio-dvr-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-fdio-dvr-ha.yaml -install config/deploy/os-odl-fdio-dvr-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-fdio-dvr-noha.yaml -install config/deploy/os-odl-nofeature-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-nofeature-ha.yaml -install config/deploy/os-odl-nofeature-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-nofeature-noha.yaml -install config/deploy/os-odl-ovs_dpdk-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-ovs_dpdk-ha.yaml -install config/deploy/os-odl-ovs_dpdk-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-ovs_dpdk-noha.yaml -install config/deploy/os-odl-gluon-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-gluon-noha.yaml -install config/deploy/os-ovn-nofeature-noha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-ovn-nofeature-noha.yaml -install config/deploy/os-onos-nofeature-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-onos-nofeature-ha.yaml -install config/deploy/os-onos-sfc-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-onos-sfc-ha.yaml -install config/deploy/os-ocl-nofeature-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-ocl-nofeature-ha.yaml -install config/network/network_settings.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/network_settings.yaml -install config/network/network_settings_v6.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/network_settings_v6.yaml -install config/network/network_settings_vpp.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/network_settings_vpp.yaml - - -mkdir -p %{buildroot}%{_var}/opt/opnfv/lib/python/apex -install lib/common-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/configure-deps-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/parse-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/virtual-setup-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/undercloud-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/overcloud-deploy-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/post-install-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/utility-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/configure-vm %{buildroot}%{_var}/opt/opnfv/lib/ -install lib/python/apex_python_utils.py %{buildroot}%{_var}/opt/opnfv/lib/python/ -mkdir -p %{buildroot}%{python3_sitelib}/apex/ -install lib/python/apex/__init__.py %{buildroot}%{python3_sitelib}/apex/ -install lib/python/apex/deploy_settings.py %{buildroot}%{python3_sitelib}/apex/ -install lib/python/apex/ip_utils.py %{buildroot}%{python3_sitelib}/apex/ -install lib/python/apex/inventory.py %{buildroot}%{python3_sitelib}/apex/ -install lib/python/apex/network_environment.py %{buildroot}%{python3_sitelib}/apex/ -install lib/python/apex/network_settings.py %{buildroot}%{python3_sitelib}/apex/ -install lib/python/apex/clean.py %{buildroot}%{python3_sitelib}/apex/ -mkdir -p %{buildroot}%{python3_sitelib}/apex/common -install lib/python/apex/common/__init__.py %{buildroot}%{python3_sitelib}/apex/common/ -install lib/python/apex/common/constants.py %{buildroot}%{python3_sitelib}/apex/common/ -install lib/python/apex/common/utils.py %{buildroot}%{python3_sitelib}/apex/common/ -mkdir -p %{buildroot}%{_var}/opt/opnfv/lib/installer/onos/ -install lib/installer/domain.xml %{buildroot}%{_var}/opt/opnfv/lib/installer/ +cp -f %{buildroot}%{_datadir}/opnfv-apex/config/deploy/* %{buildroot}%{_sysconfdir}/opnfv-apex/ +cp -f %{buildroot}%{_datadir}/opnfv-apex/config/network/* %{buildroot}%{_sysconfdir}/opnfv-apex/ +rm -f %{buildroot}%{_sysconfdir}/opnfv-apex/os-odl-csit-noha.yaml +rm -f %{buildroot}%{_sysconfdir}/opnfv-apex/deploy_settings.yaml mkdir -p %{buildroot}%{_docdir}/opnfv/ install LICENSE.rst %{buildroot}%{_docdir}/opnfv/ @@ -111,22 +58,13 @@ install config/network/network_settings_vpp.yaml %{buildroot}%{_docdir}/opnfv/ne install config/inventory/pod_example_settings.yaml %{buildroot}%{_docdir}/opnfv/inventory.yaml.example %files -%defattr(644, root, root, -) +%{python3_sitelib}/apex/ +%{python3_sitelib}/apex-*.egg-info +%defattr(644, root, root, 644) %attr(755,root,root) %{_bindir}/opnfv-deploy %attr(755,root,root) %{_bindir}/opnfv-clean %attr(755,root,root) %{_bindir}/opnfv-util -%{_var}/opt/opnfv/lib/common-functions.sh -%{_var}/opt/opnfv/lib/configure-deps-functions.sh -%{_var}/opt/opnfv/lib/parse-functions.sh -%{_var}/opt/opnfv/lib/virtual-setup-functions.sh -%{_var}/opt/opnfv/lib/undercloud-functions.sh -%{_var}/opt/opnfv/lib/overcloud-deploy-functions.sh -%{_var}/opt/opnfv/lib/post-install-functions.sh -%{_var}/opt/opnfv/lib/utility-functions.sh -%attr(755,root,root) %{_var}/opt/opnfv/lib/configure-vm -%{_var}/opt/opnfv/lib/python/ -%{python3_sitelib}/apex/ -%{_var}/opt/opnfv/lib/installer/domain.xml +%{_datadir}/opnfv-apex/ %{_sysconfdir}/bash_completion.d/apex %{_sysconfdir}/opnfv-apex/os-nosdn-nofeature-noha.yaml %{_sysconfdir}/opnfv-apex/os-nosdn-bar-noha.yaml @@ -160,6 +98,7 @@ install config/inventory/pod_example_settings.yaml %{buildroot}%{_docdir}/opnfv/ %{_sysconfdir}/opnfv-apex/os-onos-sfc-ha.yaml %{_sysconfdir}/opnfv-apex/os-ocl-nofeature-ha.yaml %{_sysconfdir}/opnfv-apex/network_settings.yaml +%{_sysconfdir}/opnfv-apex/network_settings_vlans.yaml %{_sysconfdir}/opnfv-apex/network_settings_v6.yaml %{_sysconfdir}/opnfv-apex/network_settings_vpp.yaml %doc %{_docdir}/opnfv/LICENSE.rst @@ -173,6 +112,8 @@ install config/inventory/pod_example_settings.yaml %{buildroot}%{_docdir}/opnfv/ %doc %{_docdir}/opnfv/inventory.yaml.example %changelog +* Mon Aug 14 2017 Tim Rozet - 5.0-4 +- Updated for python refactoring * Mon May 08 2017 Dan Radez - 5.0-3 - adding configure-vm * Tue Apr 11 2017 Dan Radez - 5.0-2 diff --git a/build/variables.sh b/build/variables.sh index d37be0d0..6f8a6696 100644 --- a/build/variables.sh +++ b/build/variables.sh @@ -14,7 +14,7 @@ QUAGGA_RPMS_DIR=${BUILD_DIR}/quagga_build_dir CACHE_DIR="$(dirname ${BUILD_ROOT})/.cache" CACHE_HISTORY=".cache_history" PATCHES_DIR="${BUILD_ROOT}/patches" -BUILD_UTILS="$(dirname ${BUILD_ROOT})/lib/python/build_utils.py" +BUILD_UTILS="$(dirname ${BUILD_ROOT})/apex/build/build_utils.py" rdo_images_uri=${RDO_IMAGES_URI:-https://images.rdoproject.org/ocata/delorean/current-tripleo/stable/} @@ -52,4 +52,4 @@ fdio_pkgs=( 'http://artifacts.opnfv.org/apex/danube/fdio_common_rpms/vpp-plugins-17.04.1-3~ge3b7ad7~b72.x86_64.rpm' ) -honeycomb_pkg='http://artifacts.opnfv.org/apex/danube/fdio_common_rpms/honeycomb-1.17.04.1-2073.noarch.rpm' \ No newline at end of file +honeycomb_pkg='http://artifacts.opnfv.org/apex/danube/fdio_common_rpms/honeycomb-1.17.04.1-2073.noarch.rpm' diff --git a/ci/build.py b/ci/build.py deleted file mode 100644 index a17b21bd..00000000 --- a/ci/build.py +++ /dev/null @@ -1,234 +0,0 @@ -############################################################################## -# Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import argparse -import logging -import os -import subprocess -import sys -import uuid -import yaml - -CACHE_JOURNAL = 'cache_journal.yaml' -TMP_CACHE = '.cache' -BUILD_ROOT = 'build' -BUILD_LOG_FILE = './apex_build.log' - -class ApexBuildException(Exception): - pass - - -def create_build_parser(): - build_parser = argparse.ArgumentParser() - build_parser.add_argument('--debug', action='store_true', default=False, - help="Turn on debug messages") - build_parser.add_argument('-l', '--log-file', - default=BUILD_LOG_FILE, - dest='log_file', help="Log file to log to") - build_parser.add_argument('-c', '--cache-dir', - dest='cache_dir', - default=None, - help='Directory to store cache') - build_parser.add_argument('--iso', action='store_true', - default=False, - help='Build ISO image') - build_parser.add_argument('--rpms', action='store_true', - default=False, - help='Build RPMs') - build_parser.add_argument('-r', '--release', - dest='build_version', - help='Version to apply to build ' - 'artifact label') - - return build_parser - - -def get_journal(cache_dir): - """ - Search for the journal file and returns its contents - :param cache_dir: cache storage directory where journal file is - :return: content of journal file - """ - journal_file = "{}/{}".format(cache_dir, CACHE_JOURNAL) - if os.path.isfile(journal_file) is False: - logging.info("Journal file not found {}, skipping cache search".format( - journal_file)) - else: - with open(journal_file, 'r') as fh: - cache_journal = yaml.safe_load(fh) - assert isinstance(cache_journal, list) - return cache_journal - - -def get_cache_file(cache_dir): - """ - Searches for a valid cache entry in the cache journal - :param cache_dir: directory where cache and journal are located - :return: name of valid cache file - """ - cache_journal = get_journal(cache_dir) - if cache_journal is not None: - valid_cache = cache_journal[-1] - if os.path.isfile(valid_cache): - return valid_cache - - -def unpack_cache(cache_dest, cache_dir=None): - if cache_dir is None: - logging.info("Cache directory not provided, skipping cache unpack") - return - elif os.path.isdir(cache_dir) is False: - logging.info("Cache Directory does not exist, skipping cache unpack") - return - else: - logging.info("Cache Directory Found: {}".format(cache_dir)) - cache_file = get_cache_file(cache_dir) - if cache_file is None: - logging.info("No cache file detected, skipping cache unpack") - return - logging.info("Unpacking Cache {}".format(cache_file)) - if not os.path.exists(cache_dest): - os.makedirs(cache_dest) - try: - subprocess.check_call(["tar", "xvf", cache_file, "-C", cache_dest]) - except subprocess.CalledProcessError: - logging.warning("Cache unpack failed") - return - logging.info("Cache unpacked, contents are: {}", - os.listdir(cache_dest)) - - -def build(build_root, version, iso=False, rpms=False): - if iso: - make_targets = ['iso'] - elif rpms: - make_targets = ['rpms'] - else: - make_targets = ['images', 'rpms-check'] - if version is not None: - make_args = ['RELEASE={}'.format(version)] - else: - make_args = [] - logging.info('Building targets: {}'.format(make_targets)) - try: - output = subprocess.check_output(["make"] + make_args + ["-C", - build_root] + make_targets) - logging.info(output) - except subprocess.CalledProcessError as e: - logging.error("Failed to build Apex artifacts") - logging.error(e.output) - raise e - - -def build_cache(cache_source, cache_dir): - """ - Tar up new cache with unique name and store it in cache storage - directory. Also update journal file with new cache entry. - :param cache_source: source files to tar up when building cache file - :param cache_dir: cache storage location - :return: None - """ - if cache_dir is None: - logging.info("No cache dir specified, will not build cache") - return - cache_name = 'apex-cache-{}.tgz'.format(str(uuid.uuid4())) - cache_full_path = os.path.join(cache_dir, cache_name) - os.makedirs(cache_dir, exist_ok=True) - try: - subprocess.check_call(['tar', '--atime-preserve', '--dereference', - '-caf', cache_full_path, '-C', cache_source, - '.']) - except BaseException as e: - logging.error("Unable to build new cache tarball") - if os.path.isfile(cache_full_path): - os.remove(cache_full_path) - raise e - if os.path.isfile(cache_full_path): - logging.info("Cache Build Complete") - # update journal - cache_entries = get_journal(cache_dir) - if cache_entries is None: - cache_entries = [cache_name] - else: - cache_entries.append(cache_name) - journal_file = os.path.join(cache_dir, CACHE_JOURNAL) - with open(journal_file, 'w') as fh: - yaml.safe_dump(cache_entries, fh, default_flow_style=False) - logging.info("Journal updated with new entry: {}".format(cache_name)) - else: - logging.warning("Cache file did not build correctly") - - -def prune_cache(cache_dir): - """ - Remove older cache entries if there are more than 2 - :param cache_dir: Cache storage directory - :return: None - """ - if cache_dir is None: - return - cache_modified_flag = False - cache_entries = get_journal(cache_dir) - while len(cache_entries) > 2: - logging.debug("Will remove older cache entries") - cache_to_rm = cache_entries[0] - cache_full_path = os.path.join(cache_dir, cache_to_rm) - if os.path.isfile(cache_full_path): - try: - os.remove(cache_full_path) - cache_entries.pop(0) - cache_modified_flag = True - except os.EX_OSERR: - logging.warning("Failed to remove cache file: {}".format( - cache_full_path)) - break - - else: - logging.debug("No more cache cleanup necessary") - - if cache_modified_flag: - logging.debug("Updating cache journal") - journal_file = os.path.join(cache_dir, CACHE_JOURNAL) - with open(journal_file, 'w') as fh: - yaml.safe_dump(cache_entries, fh, default_flow_style=False) - -if __name__ == '__main__': - parser = create_build_parser() - args = parser.parse_args(sys.argv[1:]) - if args.debug: - log_level = logging.DEBUG - else: - log_level = logging.INFO - os.makedirs(os.path.dirname(args.log_file), exist_ok=True) - formatter = '%(asctime)s %(levelname)s: %(message)s' - logging.basicConfig(filename=args.log_file, - format=formatter, - datefmt='%m/%d/%Y %I:%M:%S %p', - level=log_level) - console = logging.StreamHandler() - console.setLevel(log_level) - console.setFormatter(logging.Formatter(formatter)) - logging.getLogger('').addHandler(console) - apex_root = os.path.split(os.getcwd())[0] - for root, dirs, files in os.walk(apex_root): - if BUILD_ROOT in dirs: - apex_root = root - apex_build_root = os.path.join(apex_root, BUILD_ROOT) - if os.path.isdir(apex_build_root): - cache_tmp_dir = os.path.join(apex_root, TMP_CACHE) - else: - logging.error("You must execute this script inside of the Apex " - "local code repository") - raise ApexBuildException("Invalid path for apex root: {}. Must be " - "invoked from within Apex code directory.". - format(apex_root)) - unpack_cache(cache_tmp_dir, args.cache_dir) - build(apex_build_root, args.build_version, args.iso, args.rpms) - build_cache(cache_tmp_dir, args.cache_dir) - prune_cache(args.cache_dir) diff --git a/ci/build.sh b/ci/build.sh index 5cd2c28d..113f35d6 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -13,4 +13,4 @@ set -e rpm -q ansible || sudo yum -y install ansible ansible-playbook --become -i "localhost," -c local $DIR/../lib/ansible/playbooks/build_dependencies.yml -vvv make -C $DIR/../build clean -python3 $DIR/build.py $@ +python3 $DIR/../apex/build.py $@ diff --git a/ci/clean.sh b/ci/clean.sh index fba1f126..e35b95b1 100755 --- a/ci/clean.sh +++ b/ci/clean.sh @@ -12,23 +12,11 @@ #author: Dan Radez (dradez@redhat.com) #author: Tim Rozet (trozet@redhat.com) -# Use default if no param passed -BASE=${BASE:-'/var/opt/opnfv'} -IMAGES=${IMAGES:-"$BASE/images"} -LIB=${LIB:-"$BASE/lib"} reset=$(tput sgr0 || echo "") blue=$(tput setaf 4 || echo "") red=$(tput setaf 1 || echo "") green=$(tput setaf 2 || echo "") -##LIBRARIES -for lib in common-functions parse-functions; do - if ! source $LIB/${lib}.sh; then - echo "Failed to source $LIB/${lib}.sh" - exit 1 - fi -done - vm_index=4 ovs_bridges="br-admin br-tenant br-external br-storage" ovs_bridges+=" br-private br-public" # Legacy names, remove in E river @@ -37,6 +25,102 @@ ovs_bridges+=" br-private br-public" # Legacy names, remove in E river OPNFV_NETWORK_TYPES+=" admin tenant external storage api" OPNFV_NETWORK_TYPES+=" admin_network private_network public_network storage_network api_network" # Legecy names, remove in E river +##detach interface from OVS and set the network config correctly +##params: bridge to detach from +##assumes only 1 real interface attached to OVS +function detach_interface_from_ovs { + local bridge + local port_output ports_no_orig + local net_path + local if_ip if_mask if_gw if_prefix + local if_metric if_dns1 if_dns2 + + net_path=/etc/sysconfig/network-scripts/ + if [[ -z "$1" ]]; then + return 1 + else + bridge=$1 + fi + + # if no interfaces attached then return + if ! ovs-vsctl list-ports ${bridge} | grep -Ev "vnet[0-9]*"; then + return 0 + fi + + # look for .orig ifcfg files to use + port_output=$(ovs-vsctl list-ports ${bridge} | grep -Ev "vnet[0-9]*") + while read -r line; do + if [ -z "$line" ]; then + continue + elif [ -e ${net_path}/ifcfg-${line}.orig ]; then + mv -f ${net_path}/ifcfg-${line}.orig ${net_path}/ifcfg-${line} + elif [ -e ${net_path}/ifcfg-${bridge} ]; then + if_ip=$(sed -n 's/^IPADDR=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) + if_mask=$(sed -n 's/^NETMASK=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) + if_gw=$(sed -n 's/^GATEWAY=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) + if_metric=$(sed -n 's/^METRIC=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) + if_dns1=$(sed -n 's/^DNS1=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) + if_dns2=$(sed -n 's/^DNS2=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) + + if [ -z "$if_mask" ]; then + if_prefix=$(sed -n 's/^PREFIX=[^0-9]*\([0-9][0-9]*\)[^0-9]*$/\1/p' ${net_path}/ifcfg-${bridge}) + if_mask=$(prefix2mask ${if_prefix}) + fi + + if [[ -z "$if_ip" || -z "$if_mask" ]]; then + echo "ERROR: IPADDR or PREFIX/NETMASK missing for ${bridge} and no .orig file for interface ${line}" + return 1 + fi + + # create if cfg + echo "DEVICE=${line} +IPADDR=${if_ip} +NETMASK=${if_mask} +BOOTPROTO=static +ONBOOT=yes +TYPE=Ethernet +NM_CONTROLLED=no +PEERDNS=no" > ${net_path}/ifcfg-${line} + + if [ -n "$if_gw" ]; then + echo "GATEWAY=${if_gw}" >> ${net_path}/ifcfg-${line} + fi + + if [ -n "$if_metric" ]; then + echo "METRIC=${if_metric}" >> ${net_path}/ifcfg-${line} + fi + + if [[ -n "$if_dns1" || -n "$if_dns2" ]]; then + sed -i '/PEERDNS/c\PEERDNS=yes' ${net_path}/ifcfg-${line} + + if [ -n "$if_dns1" ]; then + echo "DNS1=${if_dns1}" >> ${net_path}/ifcfg-${line} + fi + + if [ -n "$if_dns2" ]; then + echo "DNS2=${if_dns2}" >> ${net_path}/ifcfg-${line} + fi + fi + break + else + echo "ERROR: Real interface ${line} attached to bridge, but no interface or ${bridge} ifcfg file exists" + return 1 + fi + + done <<< "$port_output" + + # modify the bridge ifcfg file + # to remove IP params + sudo sed -i 's/IPADDR=.*//' ${net_path}/ifcfg-${bridge} + sudo sed -i 's/NETMASK=.*//' ${net_path}/ifcfg-${bridge} + sudo sed -i 's/GATEWAY=.*//' ${net_path}/ifcfg-${bridge} + sudo sed -i 's/DNS1=.*//' ${net_path}/ifcfg-${bridge} + sudo sed -i 's/DNS2=.*//' ${net_path}/ifcfg-${bridge} + sudo sed -i 's/METRIC=.*//' ${net_path}/ifcfg-${bridge} + sudo sed -i 's/PEERDNS=.*//' ${net_path}/ifcfg-${bridge} + + sudo systemctl restart network +} display_usage() { echo -e "Usage:\n$0 [arguments] \n" @@ -47,7 +131,7 @@ display_usage() { ##params: $@ the entire command line is passed ##usage: parse_cmd_line() "$@" parse_cmdline() { - echo -e "\n\n${blue}This script is used to deploy the Apex Installer and Provision OPNFV Target System${reset}\n\n" + echo -e "\n\n${blue}This script is used to clean an Apex environment${reset}\n\n" echo "Use -h to display help" sleep 2 @@ -79,7 +163,13 @@ parse_cmdline "$@" if [ -n "$INVENTORY_FILE" ]; then echo -e "${blue}INFO: Parsing inventory file...${reset}" - if ! python3 -B $LIB/python/apex_python_utils.py clean -f ${INVENTORY_FILE}; then + # hack for now (until we switch fully over to clean.py) to tell if + # we should install apex from python or if rpm is being used + if ! rpm -q opnfv-apex-common > /dev/null; then + pushd ../ && python3 setup.py install > /dev/null + popd + fi + if ! python3 -m apex.clean -f ${INVENTORY_FILE}; then echo -e "${red}WARN: Unable to shutdown all nodes! Please check /var/log/apex.log${reset}" else echo -e "${blue}INFO: Node shutdown complete...${reset}" diff --git a/ci/deploy.sh b/ci/deploy.sh index f1a807f7..0ba0c74b 100755 --- a/ci/deploy.sh +++ b/ci/deploy.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ############################################################################## # Copyright (c) 2015 Tim Rozet (Red Hat), Dan Radez (Red Hat) and others. # @@ -12,245 +12,11 @@ # author: Dan Radez (dradez@redhat.com) # author: Tim Rozet (trozet@redhat.com) # -# Based on RDO Manager http://www.rdoproject.org set -e - -##VARIABLES -reset=$(tput sgr0 || echo "") -blue=$(tput setaf 4 || echo "") -red=$(tput setaf 1 || echo "") -green=$(tput setaf 2 || echo "") - -interactive="FALSE" -ping_site="8.8.8.8" -dnslookup_site="www.google.com" -post_config="TRUE" -debug="FALSE" - -ovs_rpm_name=openvswitch-2.6.1-1.el7.centos.x86_64.rpm -ovs_kmod_rpm_name=openvswitch-kmod-2.6.1-1.el7.centos.x86_64.rpm - -declare -i CNT -declare UNDERCLOUD -declare -A deploy_options_array -declare -a performance_options -declare -A NET_MAP - -APEX_TMP_DIR=$(python3 -c "import tempfile; print(tempfile.mkdtemp())") -SSH_OPTIONS=(-o StrictHostKeyChecking=no -o GlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null -o LogLevel=error) -DEPLOY_OPTIONS="" -BASE=${BASE:-'/var/opt/opnfv'} -IMAGES=${IMAGES:-"$BASE/images"} -LIB=${LIB:-"$BASE/lib"} -OPNFV_NETWORK_TYPES="admin tenant external storage api" -ENV_FILE="opnfv-environment.yaml" - -VM_CPUS=4 -VM_RAM=8 -VM_COMPUTES=1 - -# Netmap used to map networks to OVS bridge names -NET_MAP['admin']="br-admin" -NET_MAP['tenant']="br-tenant" -NET_MAP['external']="br-external" -NET_MAP['storage']="br-storage" -NET_MAP['api']="br-api" -ext_net_type="interface" -ip_address_family=4 - -# Libraries -lib_files=( -$LIB/common-functions.sh -$LIB/configure-deps-functions.sh -$LIB/parse-functions.sh -$LIB/virtual-setup-functions.sh -$LIB/undercloud-functions.sh -$LIB/overcloud-deploy-functions.sh -$LIB/post-install-functions.sh -$LIB/utility-functions.sh -) -for lib_file in ${lib_files[@]}; do - if ! source $lib_file; then - echo -e "${red}ERROR: Failed to source $lib_file${reset}" - exit 1 - fi -done - -display_usage() { - echo -e "Usage:\n$0 [arguments] \n" - echo -e " --deploy-settings | -d : Full path to deploy settings yaml file. Optional. Defaults to null" - echo -e " --inventory | -i : Full path to inventory yaml file. Required only for baremetal" - echo -e " --net-settings | -n : Full path to network settings file. Optional." - echo -e " --ping-site | -p : site to use to verify IP connectivity. Optional. Defaults to 8.8.8.8" - echo -e " --dnslookup-site : site to use to verify DNS resolution. Optional. Defaults to www.google.com" - echo -e " --virtual | -v : Virtualize overcloud nodes instead of using baremetal." - echo -e " --no-post-config : disable Post Install configuration." - echo -e " --debug : enable debug output." - echo -e " --interactive : enable interactive deployment mode which requires user to confirm steps of deployment." - echo -e " --virtual-cpus : Number of CPUs to use per Overcloud VM in a virtual deployment (defaults to 4)." - echo -e " --virtual-computes : Number of Virtual Compute nodes to create and use during deployment (defaults to 1 for noha and 2 for ha)." - echo -e " --virtual-default-ram : Amount of default RAM to use per Overcloud VM in GB (defaults to 8)." - echo -e " --virtual-compute-ram : Amount of RAM to use per Overcloud Compute VM in GB (defaults to 8). Overrides --virtual-default-ram arg for computes" -} - -##translates the command line parameters into variables -##params: $@ the entire command line is passed -##usage: parse_cmd_line() "$@" -parse_cmdline() { - echo -e "\n\n${blue}This script is used to deploy the Apex Installer and Provision OPNFV Target System${reset}\n\n" - echo "Use -h to display help" - - while [ "${1:0:1}" = "-" ] - do - case "$1" in - -h|--help) - display_usage - exit 0 - ;; - -d|--deploy-settings) - DEPLOY_SETTINGS_FILE=$2 - echo "Deployment Configuration file: $2" - shift 2 - ;; - -i|--inventory) - INVENTORY_FILE=$2 - shift 2 - ;; - -n|--net-settings) - NETSETS=$2 - echo "Network Settings Configuration file: $2" - shift 2 - ;; - -e|--environment-file) - ENV_FILE=$2 - echo "Base OOO Environment file: $2" - shift 2 - ;; - -p|--ping-site) - ping_site=$2 - echo "Using $2 as the ping site" - shift 2 - ;; - --dnslookup-site) - dnslookup_site=$2 - echo "Using $2 as the dnslookup site" - shift 2 - ;; - -v|--virtual) - virtual="TRUE" - echo "Executing a Virtual Deployment" - shift 1 - ;; - --no-post-config ) - post_config="FALSE" - echo "Post install configuration disabled" - shift 1 - ;; - --debug ) - debug="TRUE" - echo "Enable debug output" - shift 1 - ;; - --interactive ) - interactive="TRUE" - echo "Interactive mode enabled" - shift 1 - ;; - --virtual-cpus ) - VM_CPUS=$2 - echo "Number of CPUs per VM set to $VM_CPUS" - shift 2 - ;; - --virtual-default-ram ) - VM_RAM=$2 - echo "Amount of Default RAM per VM set to $VM_RAM" - shift 2 - ;; - --virtual-computes ) - VM_COMPUTES=$2 - echo "Virtual Compute nodes set to $VM_COMPUTES" - shift 2 - ;; - --virtual-compute-ram ) - VM_COMPUTE_RAM=$2 - echo "Virtual Compute RAM set to $VM_COMPUTE_RAM" - shift 2 - ;; - *) - display_usage - exit 1 - ;; - esac - done - sleep 2 - - if [[ -z "$NETSETS" ]]; then - echo -e "${red}ERROR: You must provide a network_settings file with -n.${reset}" - exit 1 - fi - - # inventory file usage validation - if [[ -n "$virtual" ]]; then - if [[ -n "$INVENTORY_FILE" ]]; then - echo -e "${red}ERROR: You should not specify an inventory file with virtual deployments${reset}" - exit 1 - else - INVENTORY_FILE="$APEX_TMP_DIR/inventory-virt.yaml" - fi - elif [[ -z "$INVENTORY_FILE" ]]; then - echo -e "${red}ERROR: You must specify an inventory file for baremetal deployments! Exiting...${reset}" - exit 1 - elif [[ ! -f "$INVENTORY_FILE" ]]; then - echo -e "{$red}ERROR: Inventory File: ${INVENTORY_FILE} does not exist! Exiting...${reset}" - exit 1 - fi - - if [[ -z "$DEPLOY_SETTINGS_FILE" || ! -f "$DEPLOY_SETTINGS_FILE" ]]; then - echo -e "${red}ERROR: Deploy Settings: ${DEPLOY_SETTINGS_FILE} does not exist! Exiting...${reset}" - exit 1 - fi - - if [[ ! -z "$NETSETS" && ! -f "$NETSETS" ]]; then - echo -e "${red}ERROR: Network Settings: ${NETSETS} does not exist! Exiting...${reset}" - exit 1 - fi - -} - -main() { - parse_cmdline "$@" - if [ -n "$DEPLOY_SETTINGS_FILE" ]; then - echo -e "${blue}INFO: Parsing deploy settings file...${reset}" - parse_deploy_settings - fi - echo -e "${blue}INFO: Parsing network settings file...${reset}" - parse_network_settings - if ! configure_deps; then - echo -e "${red}Dependency Validation Failed, Exiting.${reset}" - exit 1 - fi - #Correct the time on the server prior to launching any VMs - if ntpdate $ntp_server; then - hwclock --systohc - else - echo "${blue}WARNING: ntpdate failed to update the time on the server. ${reset}" - fi - setup_undercloud_vm - if [ "$virtual" == "TRUE" ]; then - setup_virtual_baremetal $VM_CPUS $VM_RAM - fi - parse_inventory_file - configure_undercloud - overcloud_deploy - if [ "$post_config" == "TRUE" ]; then - if ! configure_post_install; then - echo -e "${red}ERROR:Post Install Configuration Failed, Exiting.${reset}" - exit 1 - else - echo -e "${blue}INFO: Post Install Configuration Complete${reset}" - fi - fi -} - -main "$@" +yum -y install python34 python34-devel libvirt-devel python34-pip python-tox ansible +mkdir -p /home/jenkins-ci/tmp +mv -f .build /home/jenkins-ci/tmp/ +pip3 install --upgrade --force-reinstall . +mv -f /home/jenkins-ci/tmp/.build . +opnfv-deploy $@ diff --git a/ci/run_smoke_tests.sh b/ci/run_smoke_tests.sh index 7cbd390d..517822ef 100755 --- a/ci/run_smoke_tests.sh +++ b/ci/run_smoke_tests.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -source ../lib/utility-functions.sh - export ANSIBLE_HOST_KEY_CHECKING=False ./dev_dep_check.sh diff --git a/ci/util.sh b/ci/util.sh index 1a931d0b..a9df0213 100755 --- a/ci/util.sh +++ b/ci/util.sh @@ -2,12 +2,88 @@ # Utility script used to interact with a deployment # @author Tim Rozet (trozet@redhat.com) -BASE=${BASE:-'/var/opt/opnfv'} -IMAGES=${IMAGES:-"$BASE/images"} -LIB=${LIB:-"$BASE/lib"} VALID_CMDS="undercloud overcloud opendaylight debug-stack mock-detached -h --help" +SSH_OPTIONS=(-o StrictHostKeyChecking=no -o GlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null -o LogLevel=error) -source $LIB/utility-functions.sh +##connects to undercloud +##params: user to login with, command to execute on undercloud (optional) +function undercloud_connect { + local user=$1 + + if [ -z "$1" ]; then + echo "Missing required argument: user to login as to undercloud" + return 1 + fi + + if [ -z "$2" ]; then + ssh ${SSH_OPTIONS[@]} ${user}@$(get_undercloud_ip) + else + ssh ${SSH_OPTIONS[@]} -T ${user}@$(get_undercloud_ip) "$2" + fi +} + +##outputs the Undercloud's IP address +##params: none +function get_undercloud_ip { + echo $(arp -an | grep $(virsh domiflist undercloud | grep default |\ + awk '{print $5}') | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") +} + +##connects to overcloud nodes +##params: node to login to, command to execute on overcloud (optional) +function overcloud_connect { + local node + local node_output + local node_ip + + if [ -z "$1" ]; then + echo "Missing required argument: overcloud node to login to" + return 1 + elif ! echo "$1" | grep -E "(controller|compute)[0-9]+" > /dev/null; then + echo "Invalid argument: overcloud node to login to must be in the format: \ +controller or compute" + return 1 + fi + + node_output=$(undercloud_connect "stack" "source stackrc; nova list") + node=$(echo "$1" | sed -E 's/([a-zA-Z]+)([0-9]+)/\1-\2/') + + node_ip=$(echo "$node_output" | grep "$node" | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") + + if [ "$node_ip" == "" ]; then + echo -e "Unable to find IP for ${node} in \n${node_output}" + return 1 + fi + + if [ -z "$2" ]; then + ssh ${SSH_OPTIONS[@]} heat-admin@${node_ip} + else + ssh ${SSH_OPTIONS[@]} -T heat-admin@${node_ip} "$2" + fi +} + +##connects to opendaylight karaf console +##params: None +function opendaylight_connect { + local opendaylight_ip + opendaylight_ip=$(undercloud_connect "stack" "cat overcloudrc | grep SDN_CONTROLLER_IP | grep -Eo [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") + + if [ "$opendaylight_ip" == "" ]; then + echo -e "Unable to find IP for OpenDaylight in overcloudrc" + return 1 + else + echo -e "Connecting to ODL Karaf console. Default password is 'karaf'" + fi + + ssh -p 8101 ${SSH_OPTIONS[@]} karaf@${opendaylight_ip} +} + +##outputs heat stack deployment failures +##params: none +function debug_stack { + source ~/stackrc + openstack stack failures list overcloud --long +} resolve_cmd() { local given=$1 diff --git a/lib/ansible/playbooks/build_dependencies.yml b/lib/ansible/playbooks/build_dependencies.yml index dec8ab7c..afe12b74 100644 --- a/lib/ansible/playbooks/build_dependencies.yml +++ b/lib/ansible/playbooks/build_dependencies.yml @@ -11,15 +11,17 @@ libguestfs-tools,bsdtar,libvirt,yum-utils, python2-oslo-config,python2-debtcollector, make, python34-pip, python-virtualenv,libguestfs-tools-c, - supermin,supermin5,perl-Sys-Guestfs,python-libguestfs + supermin,supermin5,perl-Sys-Guestfs,python-libguestfs, + libvirt-devel,python34-docutils,python-docutils - name: Install Virtualization group yum: name: "@Virtualization Host" - - name: Install python ipmi from OPNFV artifacts - yum: - name: 'http://artifacts.opnfv.org/apex/dependencies/python3-ipmi-0.3.0-1.noarch.rpm' + - pip: + name: python-ipmi + executable: pip3.4 - pip: name: tox + executable: pip3.4 - pip: name: gitpython executable: pip3.4 diff --git a/lib/ansible/playbooks/configure_undercloud.yml b/lib/ansible/playbooks/configure_undercloud.yml new file mode 100644 index 00000000..7b236624 --- /dev/null +++ b/lib/ansible/playbooks/configure_undercloud.yml @@ -0,0 +1,116 @@ +--- +- hosts: all + tasks: + - name: Generate SSH key for stack if missing + shell: test -e ~/.ssh/id_rsa || ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa + - name: Fix ssh key for stack + shell: restorecon -r /home/stack + become: yes + - file: + path: /home/stack/nics + state: directory + owner: stack + group: stack + mode: 0775 + - copy: + src: /root/.ssh/id_rsa.pub + dest: /home/stack/jumphost_id_rsa.pub + owner: stack + owner: stack + mode: 0644 + - copy: + src: "{{ apex_temp_dir }}/{{ item }}.yaml" + dest: "/home/stack/nics/{{ item }}.yaml" + owner: stack + group: stack + mode: 0644 + with_items: + - controller + - compute + - lineinfile: + path: /etc/sudoers + regexp: 'Defaults\s*requiretty' + state: absent + become: yes + - name: openstack-configs undercloud + shell: openstack-config --set undercloud.conf DEFAULT {{ item }} + with_items: "{{ undercloud_config }}" + - name: openstack-configs ironic + shell: openstack-config --set /etc/ironic/ironic.conf {{ item }} + become: yes + with_items: "{{ ironic_config }}" + - name: openstack-configs undercloud aarch64 + shell: openstack-config --set undercloud.conf DEFAULT ipxe_enabled false + when: "{{ aarch64 }}" + - lineinfile: + path: /usr/lib/python2.7/site-packages/ironic/common/pxe_utils.py + regexp: '_link_ip_address_pxe_configs' + line: '_link_mac_pxe_configs(task)' + when: "{{ aarch64 }}" + - name: undercloud install + shell: openstack undercloud install &> apex-undercloud-install.log + become: yes + become_user: stack + - name: openstack-configs nova + shell: openstack-config --set /etc/nova/nova.conf DEFAULT {{ item }} + become: yes + with_items: "{{ nova_config }}" + - name: restart nova services + service: + name: "{{ item }}" + state: restarted + enabled: yes + with_items: + - openstack-nova-conductor + - openstack-nova-compute + - openstack-nova-api + - openstack-nova-scheduler + - name: openstack-configs neutron + shell: openstack-config --set /etc/neutron/neutron.conf DEFAULT {{ item }} + become: yes + with_items: "{{ neutron_config }}" + - name: restart neutron services + service: + name: "{{ item }}" + state: restarted + enabled: yes + with_items: + - neutron-server + - neutron-dhcp-agent + - name: configure external network vlan ifcfg + template: + src: external_vlan_ifcfg.yml.j2 + dest: "/etc/sysconfig/network-scripts/ifcfg-vlan{{ external_network.vlan }}" + owner: root + group: root + mode: 0644 + become: yes + when: + - external_network.vlan != "native" + - external_network.enabled + - name: bring up vlan ifcfg + shell: "ifup vlan{{ external_network.vlan }}" + become: yes + when: + - external_network.vlan != "native" + - external_network.enabled + - name: assign IP to native eth2 + shell: ip a a {{ external_network.ip }}/{{ external_network.prefix }} dev eth2 + become: yes + when: + - external_network.vlan == "native" + - external_network.enabled + - name: bring up eth2 + shell: ip link set up dev eth2 + when: + - external_network.vlan == "native" + - external_network.enabled + become: yes + - name: fetch storage environment file + fetch: + src: /usr/share/openstack-tripleo-heat-templates/environments/storage-environment.yaml + dest: "{{ apex_temp_dir }}/" + flat: yes + +- include: undercloud_aarch64.yml + when: aarch64 diff --git a/lib/ansible/playbooks/deploy_dependencies.yml b/lib/ansible/playbooks/deploy_dependencies.yml new file mode 100644 index 00000000..77231622 --- /dev/null +++ b/lib/ansible/playbooks/deploy_dependencies.yml @@ -0,0 +1,66 @@ +--- +- hosts: localhost + tasks: + - sysctl: + name: net.ipv4.ip_forward + state: present + value: 1 + sysctl_set: yes + - systemd: + name: dhcpd + state: stopped + enabled: no + ignore_errors: yes + - systemd: + name: libvirtd + state: started + enabled: yes + - systemd: + name: openvswitch + state: started + enabled: yes + - virt_net: + command: define + name: default + xml: '{{ lookup("template", "virsh_network_default.xml.j2") }}' + state: active + autostart: yes + - openvswitch_bridge: + bridge: 'br-{{ item }}' + state: present + with_items: '{{ virsh_enabled_networks }}' + - virt_net: + command: define + name: '{{ item }}' + xml: '{{ lookup("template", "virsh_network_ovs.xml.j2") }}' + autostart: yes + with_items: '{{ virsh_enabled_networks }}' + - virt_net: + command: create + name: '{{ item }}' + with_items: '{{ virsh_enabled_networks }}' + - virt_pool: + name: default + command: define + autostart: yes + state: active + xml: '{{ lookup("template", "virsh_pool.xml.j2") }}' + - lineinfile: + path: /etc/modprobe.d/kvm_intel.conf + line: 'options kvm-intel nested=1' + create: yes + when: ansible_architecture == "x86_64" + - modprobe: + name: "{{ item }}" + state: present + with_items: + - kvm + - kvm_intel + when: ansible_architecture == "x86_64" + - name: Generate SSH key for root if missing + shell: test -e ~/.ssh/id_rsa || ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa + - name: Manually patch vmbc to work with python3.x + lineinfile: + line: " conn.defineXML(ET.tostring(tree, encoding='unicode'))" + regexp: "tostring" + path: /usr/lib/python3.4/site-packages/virtualbmc/vbmc.py diff --git a/lib/ansible/playbooks/deploy_overcloud.yml b/lib/ansible/playbooks/deploy_overcloud.yml new file mode 100644 index 00000000..76bbbc67 --- /dev/null +++ b/lib/ansible/playbooks/deploy_overcloud.yml @@ -0,0 +1,68 @@ +--- +- hosts: all + tasks: + - name: Copy all files to undercloud + copy: + src: "{{ apex_temp_dir }}/{{ item }}" + dest: "/home/stack/{{ item }}" + owner: stack + group: stack + mode: 0644 + with_items: + - network-environment.yaml + - instackenv.json + - opnfv-environment.yaml + - overcloud-full.qcow2 + - deploy_command + - virtual-environment.yaml + - baremetal-environment.yaml + - copy: + src: "{{ apex_temp_dir }}/storage-environment.yaml" + dest: /usr/share/openstack-tripleo-heat-templates/environments/storage-environment.yaml + owner: root + group: root + mode: 0664 + - systemd: + name: openstack-swift-proxy + state: restarted + enabled: yes + become: yes + - name: Upload glance images + shell: "{{ stackrc }} && openstack overcloud image upload" + become: yes + become_user: stack + - name: Import inventory (baremetal) + shell: "{{ stackrc }} && {{ item }}" + with_items: + - openstack overcloud node import instackenv.json + - openstack overcloud node introspect --all-manageable --provide + when: not virtual + - name: Import inventory (virtual) + shell: "{{ stackrc }} && openstack overcloud node import --provide instackenv.json" + when: virtual + - name: Set flavors + shell: '{{ stackrc }} && openstack flavor set --property "cpu_arch"="x86_64" {{ item }}' + with_items: + - baremetal + - control + - compute + - name: Configure DNS server for ctlplane network + shell: "{{ stackrc }} && openstack subnet set ctlplane-subnet {{ dns_server_args }}" + - name: Execute Overcloud Deployment + shell: "{{ stackrc }} && bash deploy_command" + - name: Show Keystone output + shell: "{{ overcloudrc }} && {{ item }}" + when: debug + with_items: + - openstack endpoint list + - openstack service list + - name: Get overcloud nodes and IPs + shell: "{{ stackrc }} && openstack server list -f json" + register: nova_list + - name: Write nova list output to file + local_action: copy content="{{ nova_list.stdout }}" dest="{{ apex_temp_dir }}/nova_output" + - name: Fetch overcloudrc + fetch: + src: /home/stack/overcloudrc + dest: "{{ apex_temp_dir }}/" + flat: yes diff --git a/lib/ansible/playbooks/post_deploy_overcloud.yml b/lib/ansible/playbooks/post_deploy_overcloud.yml new file mode 100644 index 00000000..fdf70240 --- /dev/null +++ b/lib/ansible/playbooks/post_deploy_overcloud.yml @@ -0,0 +1,45 @@ +--- +- hosts: all + tasks: + - name: Bring up br-phy for OVS DPDK + shell: ifup br-phy + when: + - dataplane == 'ovs_dpdk' + - "'compute' in ansible_hostname" + become: yes + - name: Restart OVS Agent for DPDK + shell: systemctl restart neutron-openvswitch-agent + when: + - dataplane == 'ovs_dpdk' + - "'compute' in ansible_hostname" + - sdn == false + - name: SFC config workaround + file: + src: /etc/neutron/networking_sfc.conf + dest: /etc/neutron/conf.d/neutron-server/networking_sfc.conf + state: link + become: yes + when: + - sfc + - "'controller' in ansible_hostname" + - name: Ensure ZRPCD is up + systemd: + name: zrpcd + state: started + enabled: yes + become: yes + when: + - vpn + - "'controller-0' in ansible_hostname" + - name: VSPERF build base machine + shell: /build_base_machine.sh + args: + chdir: /var/opt/vsperf/systems/ + become: yes + when: + - vsperf + - "'compute-0' in ansible_hostname" + - name: Fetch logs from node + fetch: + src: /var/log/messages + dest: "{{ apex_temp_dir }}" diff --git a/lib/ansible/playbooks/post_deploy_undercloud.yml b/lib/ansible/playbooks/post_deploy_undercloud.yml new file mode 100644 index 00000000..ba0746b2 --- /dev/null +++ b/lib/ansible/playbooks/post_deploy_undercloud.yml @@ -0,0 +1,118 @@ +--- +- hosts: all + tasks: + - name: Enable ssh to overcloud nodes from jumphost + shell: "cat /home/stack/jumphost_id_rsa.pub | ssh -T {{ SSH_OPTIONS }} heat-admin@{{ item.value }} 'cat >> ~/.ssh/authorized_keys'" + with_dict: "{{ overcloud_nodes }}" + become: yes + become_user: stack + - name: Configure external network + shell: "{{ overcloudrc }} && {{ item }}" + with_items: "{{ external_network_cmds }}" + - name: Configure gluon networks + shell: "{{ overcloudrc }} && {{ item }}" + when: gluon + with_items: + - openstack network create gluon-network --share --provider-network-type vxlan + - openstack subnet create gluon-subnet --no-gateway --no-dhcp --network GluonNetwork --subnet-range 0.0.0.0/1 + - name: Find admin project id + shell: "{{ overcloudrc }} && openstack project list | grep admin | awk '{print $2}'" + register: os_project_id + - name: Inject OS_PROJECT_ID and OS_TENANT_NAME into overcloudrc + lineinfile: + line: "{{ item }}" + path: /home/stack/overcloudrc + with_items: + - "export OS_PROJECT_ID={{ os_project_id.stdout }}" + - "export OS_TENANT_NAME=admin" + - name: Install Docker + yum: + name: docker + state: present + when: yardstick or dovetail + become: yes + - systemd: + name: docker + state: started + enabled: yes + when: yardstick or dovetail + become: yes + - name: Pull yardstick docker image + docker_image: + name: opnfv/yardstick + when: yardstick + become: yes + - name: Pull dovetail docker image + docker_image: + name: opnfv/dovetail + when: dovetail + become: yes + - name: Register SDN VIP + shell: "{{ stackrc }} && neutron port-list | grep control_virtual_ip | grep -Eo '([0-9]+\\.){3}[0-9]+'" + register: sdn_vip + become: yes + become_user: stack + when: sdn != false + - name: Write SDN controller VIP to overcloudrc + lineinfile: + line: "export SDN_CONTROLLER_IP={{ sdn_vip.stdout }}" + regexp: 'SDN_CONTROLLER_IP' + path: "/home/stack/{{ item }}" + when: sdn != false + with_items: + - overcloudrc + - overcloudrc.v3 + - name: Undercloud NAT - MASQUERADE interface + iptables: + table: nat + chain: POSTROUTING + out_interface: eth0 + jump: MASQUERADE + when: + - virtual + - not external_network_ipv6 + become: yes + - name: Undercloud NAT - MASQUERADE interface with subnet + iptables: + table: nat + chain: POSTROUTING + out_interface: eth0 + jump: MASQUERADE + source: "{{ external_cidr }}" + when: + - virtual + - not external_network_ipv6 + become: yes + - name: Undercloud NAT - Allow Forwarding + iptables: + chain: FORWARD + in_interface: eth2 + jump: ACCEPT + when: + - virtual + - not external_network_ipv6 + become: yes + - name: Undercloud NAT - Allow Stateful Forwarding + iptables: + chain: FORWARD + in_interface: eth2 + jump: ACCEPT + source: "{{ external_cidr }}" + ctstate: ESTABLISHED,RELATED + when: + - virtual + - not external_network_ipv6 + become: yes + - name: Undercloud NAT - Save iptables + shell: service iptables save + become: yes + when: + - virtual + - not external_network_ipv6 + - name: Create congress datasources + shell: "{{ overcloudrc }} && openstack congress datasource create {{ item }}" + become: yes + become_user: stack + when: congress + with_items: "{{ congress_datasources }}" + ignore_errors: yes diff --git a/lib/ansible/playbooks/templates/external_vlan_ifcfg.yml.j2 b/lib/ansible/playbooks/templates/external_vlan_ifcfg.yml.j2 new file mode 100644 index 00000000..c478a7d9 --- /dev/null +++ b/lib/ansible/playbooks/templates/external_vlan_ifcfg.yml.j2 @@ -0,0 +1,9 @@ +DEVICE=vlan{{ external_network.vlan }} +ONBOOT=yes +DEVICETYPE=ovs +TYPE=OVSIntPort +BOOTPROTO=static +IPADDR={{ external_network.ip }} +PREFIX={{ external_network.prefix }} +OVS_BRIDGE=br-ctlplane +OVS_OPTIONS="tag={{ external_network.vlan }}" diff --git a/lib/ansible/playbooks/templates/virsh_network_default.xml.j2 b/lib/ansible/playbooks/templates/virsh_network_default.xml.j2 new file mode 100644 index 00000000..d7241d0c --- /dev/null +++ b/lib/ansible/playbooks/templates/virsh_network_default.xml.j2 @@ -0,0 +1,10 @@ + + default + + + + + + + + diff --git a/lib/ansible/playbooks/templates/virsh_network_ovs.xml.j2 b/lib/ansible/playbooks/templates/virsh_network_ovs.xml.j2 new file mode 100644 index 00000000..75a06eea --- /dev/null +++ b/lib/ansible/playbooks/templates/virsh_network_ovs.xml.j2 @@ -0,0 +1,6 @@ + + {{ item }} + + + + diff --git a/lib/ansible/playbooks/templates/virsh_pool.xml.j2 b/lib/ansible/playbooks/templates/virsh_pool.xml.j2 new file mode 100644 index 00000000..f6ea498a --- /dev/null +++ b/lib/ansible/playbooks/templates/virsh_pool.xml.j2 @@ -0,0 +1,6 @@ + + default + + /var/lib/libvirt/images + + diff --git a/lib/ansible/playbooks/undercloud_aarch64.yml b/lib/ansible/playbooks/undercloud_aarch64.yml new file mode 100644 index 00000000..5b607c3e --- /dev/null +++ b/lib/ansible/playbooks/undercloud_aarch64.yml @@ -0,0 +1,49 @@ +--- +- hosts: all + tasks: + - name: aarch64 configuration + block: + - shell: yum -y reinstall grub2-efi shim + - copy: + src: /boot/efi/EFI/centos/grubaa64.efi + dest: /tftpboot/grubaa64.efi + remote_src: yes + - file: + path: /tftpboot/EFI/centos + state: directory + mode: 0755 + - copy: + content: | + set default=master + set timeout=5 + set hidden_timeout_quiet=false + menuentry "master" { + configfile /tftpboot/\\\$net_default_ip.conf + } + dest: /tftpboot/EFI/centos/grub.cfg + mode: 0644 + - shell: 'openstack-config --set /etc/ironic/ironic.conf pxe uefi_pxe_config_template $pybasedir/drivers/modules/pxe_grub_config.template' + - shell: 'openstack-config --set /etc/ironic/ironic.conf pxe uefi_pxe_bootfile_name grubaa64.efi' + - systemd: + name: openstack-ironic-conductor + state: restarted + enabled: yes + - replace: + path: /usr/lib/python2.7/site-packages/ironic/drivers/modules/pxe_grub_config.template + regexp: 'linuxefi' + replace: 'linux' + - replace: + path: /usr/lib/python2.7/site-packages/ironic/drivers/modules/pxe_grub_config.template + regexp: 'initrdefi' + replace: 'initrd' + - lineinfile: + path: /tftpboot/map-file + insertafter: EOF + state: present + line: '' + - shell: "echo 'r ^/EFI/centos/grub.cfg-(.*) /tftpboot/pxelinux.cfg/\\1' | sudo tee --append /tftpboot/map-file" + - systemd: + name: xinetd + state: restarted + enabled: yes + become: yes diff --git a/lib/common-functions.sh b/lib/common-functions.sh deleted file mode 100644 index 709dbf97..00000000 --- a/lib/common-functions.sh +++ /dev/null @@ -1,308 +0,0 @@ -#!/usr/bin/env bash -############################################################################## -# Copyright (c) 2015 Tim Rozet (Red Hat), Dan Radez (Red Hat) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -# Common Functions used by OPNFV Apex -# author: Tim Rozet (trozet@redhat.com) - -##converts subnet mask to prefix -##params: subnet mask -function prefix2mask { - # Number of args to shift, 255..255, first non-255 byte, zeroes - set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0 - [ $1 -gt 1 ] && shift $1 || shift - echo ${1-0}.${2-0}.${3-0}.${4-0} -} - -##find ip of interface -##params: interface name, address family -function find_ip { - local af - if [[ -z "$1" ]]; then - return 1 - fi - if [[ -z "$2" ]]; then - af=4 - else - af=$2 - fi - - python3 -B $LIB/python/apex_python_utils.py find-ip -i $1 -af $af -} - -##attach interface to OVS and set the network config correctly -##params: bride to attach to, interface to attach, network type (optional) -##external indicates attaching to a external interface -function attach_interface_to_ovs { - local bridge interface - local if_ip if_mask if_gw if_file ovs_file if_prefix - local if_metric if_dns1 if_dns2 - - if [[ -z "$1" || -z "$2" ]]; then - return 1 - else - bridge=$1 - interface=$2 - fi - - if ovs-vsctl list-ports ${bridge} | grep ${interface}; then - return 0 - fi - - if_file=/etc/sysconfig/network-scripts/ifcfg-${interface} - ovs_file=/etc/sysconfig/network-scripts/ifcfg-${bridge} - - if [ -e "$if_file" ]; then - if_ip=$(sed -n 's/^IPADDR=\(.*\)$/\1/p' ${if_file}) - if_mask=$(sed -n 's/^NETMASK=\(.*\)$/\1/p' ${if_file}) - if_gw=$(sed -n 's/^GATEWAY=\(.*\)$/\1/p' ${if_file}) - if_metric=$(sed -n 's/^METRIC=\(.*\)$/\1/p' ${if_file}) - if_dns1=$(sed -n 's/^DNS1=\(.*\)$/\1/p' ${if_file}) - if_dns2=$(sed -n 's/^DNS2=\(.*\)$/\1/p' ${if_file}) - else - echo "ERROR: ifcfg file missing for ${interface}" - return 1 - fi - - if [ -z "$if_mask" ]; then - # we can look for PREFIX here, then convert it to NETMASK - if_prefix=$(sed -n 's/^PREFIX=[^0-9]*\([0-9][0-9]*\)[^0-9]*$/\1/p' ${if_file}) - if_mask=$(prefix2mask ${if_prefix}) - fi - - if [[ -z "$if_ip" || -z "$if_mask" ]]; then - echo "ERROR: IPADDR or NETMASK/PREFIX missing for ${interface}" - return 1 - elif [[ -z "$if_gw" && "$3" == "external" ]]; then - echo "ERROR: GATEWAY missing for ${interface}, which is external" - return 1 - fi - - # move old config file to .orig - mv -f ${if_file} ${if_file}.orig - echo "DEVICE=${interface} -DEVICETYPE=ovs -TYPE=OVSPort -PEERDNS=no -BOOTPROTO=static -NM_CONTROLLED=no -ONBOOT=yes -OVS_BRIDGE=${bridge} -PROMISC=yes" > ${if_file} - - - # create bridge cfg - echo "DEVICE=${bridge} -DEVICETYPE=ovs -IPADDR=${if_ip} -NETMASK=${if_mask} -BOOTPROTO=static -ONBOOT=yes -TYPE=OVSBridge -PROMISC=yes -PEERDNS=no" > ${ovs_file} - - if [ -n "$if_gw" ]; then - echo "GATEWAY=${if_gw}" >> ${ovs_file} - fi - - if [ -n "$if_metric" ]; then - echo "METRIC=${if_metric}" >> ${ovs_file} - fi - - if [[ -n "$if_dns1" || -n "$if_dns2" ]]; then - sed -i '/PEERDNS/c\PEERDNS=yes' ${ovs_file} - - if [ -n "$if_dns1" ]; then - echo "DNS1=${if_dns1}" >> ${ovs_file} - fi - - if [ -n "$if_dns2" ]; then - echo "DNS2=${if_dns2}" >> ${ovs_file} - fi - fi - - sudo systemctl restart network -} - -##detach interface from OVS and set the network config correctly -##params: bridge to detach from -##assumes only 1 real interface attached to OVS -function detach_interface_from_ovs { - local bridge - local port_output ports_no_orig - local net_path - local if_ip if_mask if_gw if_prefix - local if_metric if_dns1 if_dns2 - - net_path=/etc/sysconfig/network-scripts/ - if [[ -z "$1" ]]; then - return 1 - else - bridge=$1 - fi - - # if no interfaces attached then return - if ! ovs-vsctl list-ports ${bridge} | grep -Ev "vnet[0-9]*"; then - return 0 - fi - - # look for .orig ifcfg files to use - port_output=$(ovs-vsctl list-ports ${bridge} | grep -Ev "vnet[0-9]*") - while read -r line; do - if [ -z "$line" ]; then - continue - elif [ -e ${net_path}/ifcfg-${line}.orig ]; then - mv -f ${net_path}/ifcfg-${line}.orig ${net_path}/ifcfg-${line} - elif [ -e ${net_path}/ifcfg-${bridge} ]; then - if_ip=$(sed -n 's/^IPADDR=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) - if_mask=$(sed -n 's/^NETMASK=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) - if_gw=$(sed -n 's/^GATEWAY=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) - if_metric=$(sed -n 's/^METRIC=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) - if_dns1=$(sed -n 's/^DNS1=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) - if_dns2=$(sed -n 's/^DNS2=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge}) - - if [ -z "$if_mask" ]; then - if_prefix=$(sed -n 's/^PREFIX=[^0-9]*\([0-9][0-9]*\)[^0-9]*$/\1/p' ${net_path}/ifcfg-${bridge}) - if_mask=$(prefix2mask ${if_prefix}) - fi - - if [[ -z "$if_ip" || -z "$if_mask" ]]; then - echo "ERROR: IPADDR or PREFIX/NETMASK missing for ${bridge} and no .orig file for interface ${line}" - return 1 - fi - - # create if cfg - echo "DEVICE=${line} -IPADDR=${if_ip} -NETMASK=${if_mask} -BOOTPROTO=static -ONBOOT=yes -TYPE=Ethernet -NM_CONTROLLED=no -PEERDNS=no" > ${net_path}/ifcfg-${line} - - if [ -n "$if_gw" ]; then - echo "GATEWAY=${if_gw}" >> ${net_path}/ifcfg-${line} - fi - - if [ -n "$if_metric" ]; then - echo "METRIC=${if_metric}" >> ${net_path}/ifcfg-${line} - fi - - if [[ -n "$if_dns1" || -n "$if_dns2" ]]; then - sed -i '/PEERDNS/c\PEERDNS=yes' ${net_path}/ifcfg-${line} - - if [ -n "$if_dns1" ]; then - echo "DNS1=${if_dns1}" >> ${net_path}/ifcfg-${line} - fi - - if [ -n "$if_dns2" ]; then - echo "DNS2=${if_dns2}" >> ${net_path}/ifcfg-${line} - fi - fi - break - else - echo "ERROR: Real interface ${line} attached to bridge, but no interface or ${bridge} ifcfg file exists" - return 1 - fi - - done <<< "$port_output" - - # modify the bridge ifcfg file - # to remove IP params - sudo sed -i 's/IPADDR=.*//' ${net_path}/ifcfg-${bridge} - sudo sed -i 's/NETMASK=.*//' ${net_path}/ifcfg-${bridge} - sudo sed -i 's/GATEWAY=.*//' ${net_path}/ifcfg-${bridge} - sudo sed -i 's/DNS1=.*//' ${net_path}/ifcfg-${bridge} - sudo sed -i 's/DNS2=.*//' ${net_path}/ifcfg-${bridge} - sudo sed -i 's/METRIC=.*//' ${net_path}/ifcfg-${bridge} - sudo sed -i 's/PEERDNS=.*//' ${net_path}/ifcfg-${bridge} - - sudo systemctl restart network -} - -# Update iptables rule for external network reach internet -# for virtual deployments -# params: external_cidr -function configure_undercloud_nat { - local external_cidr - if [[ -z "$1" ]]; then - return 1 - else - external_cidr=$1 - fi - - ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" < /dev/null; then - return 0 - else - return 1 - fi -} - -##verify internet connectivity -#params: none -function verify_internet { - if ping -c 2 $ping_site > /dev/null; then - if ping -c 2 $dnslookup_site > /dev/null; then - echo "${blue}Internet connectivity detected${reset}" - return 0 - else - echo "${red}Internet connectivity detected, but DNS lookup failed${reset}" - return 1 - fi - else - echo "${red}No internet connectivity detected${reset}" - return 1 - fi -} - -##tests if overcloud nodes have external connectivity -#params:none -function test_overcloud_connectivity { - for node in $(undercloud_connect stack ". stackrc && nova list" | grep -Eo "controller-[0-9]+|compute-[0-9]+" | tr -d -) ; do - if ! overcloud_connect $node "ping -c 2 $ping_site > /dev/null"; then - echo "${blue}Node ${node} was unable to ping site ${ping_site}${reset}" - return 1 - fi - done - echo "${blue}Overcloud external connectivity OK${reset}" -} - diff --git a/lib/configure-deps-functions.sh b/lib/configure-deps-functions.sh deleted file mode 100755 index 4c00fbf3..00000000 --- a/lib/configure-deps-functions.sh +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env bash -############################################################################## -# Copyright (c) 2015 Tim Rozet (Red Hat), Dan Radez (Red Hat) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -##download dependencies if missing and configure host -#params: none -function configure_deps { - if ! verify_internet; then - echo "${red}Will not download dependencies${reset}" - internet=false - fi - - # verify ip forwarding - if sysctl net.ipv4.ip_forward | grep 0; then - sudo sysctl -w net.ipv4.ip_forward=1 - sudo sh -c "echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf" - fi - - # ensure no dhcp server is running on jumphost - if ! sudo systemctl status dhcpd | grep dead; then - echo "${red}WARN: DHCP Server detected on jumphost, disabling...${reset}" - sudo systemctl stop dhcpd - sudo systemctl disable dhcpd - fi - - # ensure networks are configured - systemctl status libvirtd || systemctl start libvirtd - systemctl status openvswitch || systemctl start openvswitch - - # For baremetal we only need to create/attach Undercloud to admin and external - if [ "$virtual" == "FALSE" ]; then - virsh_enabled_networks="admin external" - else - virsh_enabled_networks=$enabled_network_list - fi - - # ensure default network is configured correctly - libvirt_dir="/usr/share/libvirt/networks" - virsh net-list --all | grep default || virsh net-define ${libvirt_dir}/default.xml - virsh net-list --all | grep -E "default\s+active" > /dev/null || virsh net-start default - virsh net-list --all | grep -E "default\s+active\s+yes" > /dev/null || virsh net-autostart --network default - - if [[ -z "$virtual" || "$virtual" == "FALSE" ]]; then - for network in ${enabled_network_list}; do - echo "${blue}INFO: Creating Virsh Network: $network & OVS Bridge: ${NET_MAP[$network]}${reset}" - ovs-vsctl list-br | grep "^${NET_MAP[$network]}$" > /dev/null || ovs-vsctl add-br ${NET_MAP[$network]} - virsh net-list --all | grep " $network " > /dev/null || (cat > ${libvirt_dir}/apex-virsh-net.xml && virsh net-define ${libvirt_dir}/apex-virsh-net.xml) << EOF - - $network - - - - -EOF - if ! (virsh net-list --all | grep " $network " > /dev/null); then - echo "${red}ERROR: unable to create network: ${network}${reset}" - exit 1; - fi - rm -f ${libvirt_dir}/apex-virsh-net.xml &> /dev/null; - virsh net-list | grep -E "$network\s+active" > /dev/null || virsh net-start $network - virsh net-list | grep -E "$network\s+active\s+yes" > /dev/null || virsh net-autostart --network $network - done - - echo -e "${blue}INFO: Bridges set: ${reset}" - ovs-vsctl list-br - - # bridge interfaces to correct OVS instances for baremetal deployment - for network in ${enabled_network_list}; do - if [[ "$network" != "admin" && "$network" != "external" ]]; then - continue - fi - this_interface=$(eval echo \${${network}_installer_vm_members}) - # check if this a bridged interface for this network - if [[ ! -z "$this_interface" || "$this_interface" != "none" ]]; then - if ! attach_interface_to_ovs ${NET_MAP[$network]} ${this_interface} ${network}; then - echo -e "${red}ERROR: Unable to bridge interface ${this_interface} to bridge ${NET_MAP[$network]} for enabled network: ${network}${reset}" - exit 1 - else - echo -e "${blue}INFO: Interface ${this_interface} bridged to bridge ${NET_MAP[$network]} for enabled network: ${network}${reset}" - fi - else - echo "${red}ERROR: Unable to determine interface to bridge to for enabled network: ${network}${reset}" - exit 1 - fi - done - else - # verify virtualbmc is installed for a virtual install - if ! rpm -q python2-virtualbmc; then - echo -e "${red}ERROR: Package python2-virtualbmc is required to do a virtual install.$reset" - exit 1 - fi - for network in ${OPNFV_NETWORK_TYPES}; do - if ! ovs-vsctl --may-exist add-br ${NET_MAP[$network]}; then - echo -e "${red}ERROR: Failed to create ovs bridge ${NET_MAP[$network]}${reset}" - exit 1 - fi - echo "${blue}INFO: Creating Virsh Network: $network${reset}" - virsh net-list --all | grep " $network " > /dev/null || (cat > ${libvirt_dir}/apex-virsh-net.xml && virsh net-define ${libvirt_dir}/apex-virsh-net.xml) << EOF - -$network - - - - -EOF - if ! (virsh net-list --all | grep $network > /dev/null); then - echo "${red}ERROR: unable to create network: ${network}${reset}" - exit 1; - fi - rm -f ${libvirt_dir}/apex-virsh-net.xml &> /dev/null; - virsh net-list | grep -E "$network\s+active" > /dev/null || virsh net-start $network - virsh net-list | grep -E "$network\s+active\s+yes" > /dev/null || virsh net-autostart --network $network - done - - echo -e "${blue}INFO: Bridges set: ${reset}" - ovs-vsctl list-br - fi - - echo -e "${blue}INFO: virsh networks set: ${reset}" - virsh net-list - - # ensure storage pool exists and is started - virsh pool-list --all | grep default > /dev/null || virsh pool-define-as --name default dir --target /var/lib/libvirt/images - virsh pool-list | grep -Eo "default\s+active" > /dev/null || (virsh pool-autostart default; virsh pool-start default) - - # Virt flag check is Arch dependent on x86 - if [ "$(uname -i)" == 'x86_64' ]; then - if ! egrep '^flags.*(vmx|svm)' /proc/cpuinfo > /dev/null; then - echo "${red}virtualization extensions not found, kvm kernel module insertion may fail.\n \ -Are you sure you have enabled vmx in your bios or hypervisor?${reset}" - fi - - if ! lsmod | grep kvm > /dev/null; then modprobe kvm; fi - if ! lsmod | grep kvm_intel > /dev/null; then modprobe kvm_intel; fi - - if ! lsmod | grep kvm > /dev/null; then - echo "${red}kvm kernel modules not loaded!${reset}" - return 1 - fi - - # try to enabled nested kvm - if [ "$virtual" == "TRUE" ]; then - nested_kvm=`cat /sys/module/kvm_intel/parameters/nested` - if [ "$nested_kvm" != "Y" ]; then - # try to enable nested kvm - echo 'options kvm-intel nested=1' > /etc/modprobe.d/kvm_intel.conf - if rmmod kvm_intel; then - modprobe kvm_intel - fi - nested_kvm=`cat /sys/module/kvm_intel/parameters/nested` - fi - if [ "$nested_kvm" != "Y" ]; then - echo "${red}Cannot enable nested kvm, falling back to qemu for deployment${reset}" - DEPLOY_OPTIONS+=" --libvirt-type qemu" - else - echo "${blue}Nested kvm enabled, deploying with kvm acceleration${reset}" - fi - fi - fi - - ##sshkeygen for root - if [ ! -e ~/.ssh/id_rsa.pub ]; then - ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa - fi - - echo "${blue}All dependencies installed and running${reset}" -} diff --git a/lib/configure-vm b/lib/configure-vm deleted file mode 100755 index 5cb45218..00000000 --- a/lib/configure-vm +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env python - -import argparse -import math -import os -import random - -import libvirt - -templatedir = os.getenv('LIB', '/var/opt/opnfv/lib') + '/installer/' - -MAX_NUM_MACS = math.trunc(0xff/2) - - -def generate_baremetal_macs(count=1): - """Generate an Ethernet MAC address suitable for baremetal testing.""" - # NOTE(dprince): We generate our own bare metal MAC address's here - # instead of relying on libvirt so that we can ensure the - # locally administered bit is set low. (The libvirt default is - # to set the 2nd MSB high.) This effectively allows our - # fake baremetal VMs to more accurately behave like real hardware - # and fixes issues with bridge/DHCP configurations which rely - # on the fact that bridges assume the MAC address of the lowest - # attached NIC. - # MACs generated for a given machine will also be in sequential - # order, which matches how most BM machines are laid out as well. - # Additionally we increment each MAC by two places. - macs = [] - - if count > MAX_NUM_MACS: - raise ValueError("The MAX num of MACS supported is %i." % MAX_NUM_MACS) - - base_nums = [0x00, - random.randint(0x00, 0xff), - random.randint(0x00, 0xff), - random.randint(0x00, 0xff), - random.randint(0x00, 0xff)] - base_mac = ':'.join(map(lambda x: "%02x" % x, base_nums)) - - start = random.randint(0x00, 0xff) - if (start + (count * 2)) > 0xff: - # leave room to generate macs in sequence - start = 0xff - count * 2 - for num in range(0, count*2, 2): - mac = start + num - macs.append(base_mac + ":" + ("%02x" % mac)) - return macs - -def main(): - parser = argparse.ArgumentParser( - description="Configure a kvm virtual machine for the seed image.") - parser.add_argument('--name', default='seed', - help='the name to give the machine in libvirt.') - parser.add_argument('--image', - help='Use a custom image file (must be qcow2).') - parser.add_argument('--diskbus', default='sata', - help='Choose an alternate bus type for the disk') - parser.add_argument('--baremetal-interface', nargs='+', default=['brbm'], - help='The interface which bare metal nodes will be connected to.') - parser.add_argument('--engine', default='kvm', - help='The virtualization engine to use') - parser.add_argument('--arch', default='i686', - help='The architecture to use') - parser.add_argument('--memory', default='2097152', - help="Maximum memory for the VM in KB.") - parser.add_argument('--cpus', default='1', - help="CPU count for the VM.") - parser.add_argument('--bootdev', default='hd', - help="What boot device to use (hd/network).") - parser.add_argument('--seed', default=False, action='store_true', - help='Create a seed vm with two interfaces.') - parser.add_argument('--ovsbridge', default="", - help='Place the seed public interface on this ovs bridge.') - parser.add_argument('--libvirt-nic-driver', default='virtio', - help='The libvirt network driver to use') - parser.add_argument('--enable-serial-console', action="store_true", - help='Enable a serial console') - parser.add_argument('--direct-boot', - help='Enable directboot to .{vmlinux & initrd}') - parser.add_argument('--kernel-arg', action="append", dest='kernel_args', - help='Kernel arguments, use multiple time for multiple args.') - parser.add_argument('--uri', default='qemu:///system', - help='The server uri with which to connect.') - args = parser.parse_args() - with file(templatedir + '/domain.xml', 'rb') as f: - source_template = f.read() - imagefile = '/var/lib/libvirt/images/seed.qcow2' - if args.image: - imagefile = args.image - imagefile = os.path.realpath(imagefile) - params = { - 'name': args.name, - 'imagefile': imagefile, - 'engine': args.engine, - 'arch': args.arch, - 'memory': args.memory, - 'cpus': args.cpus, - 'bootdev': args.bootdev, - 'network': '', - 'enable_serial_console': '', - 'direct_boot': '', - 'kernel_args': '', - 'user_interface': '', - } - if args.image is not None: - params['imagefile'] = args.image - - # Configure the bus type for the target disk device - params['diskbus'] = args.diskbus - nicparams = { - 'nicdriver': args.libvirt_nic_driver, - 'ovsbridge': args.ovsbridge, - } - if args.seed: - if args.ovsbridge: - params['network'] = """ - - - - - """ % nicparams - else: - params['network'] = """ - - - - - """ % nicparams - - macs = generate_baremetal_macs(len(args.baremetal_interface)) - - params['bm_network'] = "" - for bm_interface, mac in zip(args.baremetal_interface, macs): - bm_interface_params = { - 'bminterface': bm_interface, - 'bmmacaddress': mac, - 'nicdriver': args.libvirt_nic_driver, - } - params['bm_network'] += """ - - - - - - """ % bm_interface_params - - if args.enable_serial_console: - params['enable_serial_console'] = """ - - - - - - - """ - if args.direct_boot: - params['direct_boot'] = """ - /var/lib/libvirt/images/%(direct_boot)s.vmlinuz - /var/lib/libvirt/images/%(direct_boot)s.initrd - """ % { 'direct_boot': args.direct_boot } - if args.kernel_args: - params['kernel_args'] = """ - %s - """ % ' '.join(args.kernel_args) - - if args.arch == 'aarch64': - - params['direct_boot'] += """ - /usr/share/AAVMF/AAVMF_CODE.fd - /var/lib/libvirt/qemu/nvram/centos7.0_VARS.fd - """ - params['user_interface'] = """ - -
- - - - - - - - - -
- - """ - else: - params['user_interface'] = """ - - - - """ - - - libvirt_template = source_template % params - conn=libvirt.open(args.uri) - a = conn.defineXML(libvirt_template) - print ("Created machine %s with UUID %s" % (args.name, a.UUIDString())) - -if __name__ == '__main__': - main() diff --git a/lib/installer/domain.xml b/lib/installer/domain.xml deleted file mode 100644 index 57a67d87..00000000 --- a/lib/installer/domain.xml +++ /dev/null @@ -1,34 +0,0 @@ - - %(name)s - %(memory)s - %(cpus)s - - - hvm - - - %(direct_boot)s - %(kernel_args)s - - - - - - - - destroy - restart - restart - - - - - - - - %(network)s - %(bm_network)s - %(enable_serial_console)s - %(user_interface)s - - diff --git a/lib/overcloud-deploy-functions.sh b/lib/overcloud-deploy-functions.sh deleted file mode 100755 index b52d0c28..00000000 --- a/lib/overcloud-deploy-functions.sh +++ /dev/null @@ -1,503 +0,0 @@ -#!/usr/bin/env bash -############################################################################## -# Copyright (c) 2015 Tim Rozet (Red Hat), Dan Radez (Red Hat) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -##preping it for deployment and launch the deploy -##params: none -function overcloud_deploy { - local num_compute_nodes - local num_control_nodes - local dpdk_cores pmd_cores socket_mem ovs_dpdk_perf_flag ovs_option_heat_arr - declare -A ovs_option_heat_arr - - ovs_option_heat_arr['dpdk_cores']=HostCpusList - ovs_option_heat_arr['pmd_cores']=NeutronDpdkCoreList - ovs_option_heat_arr['socket_memory']=NeutronDpdkSocketMemory - ovs_option_heat_arr['memory_channels']=NeutronDpdkMemoryChannels - - # OPNFV Default Environment and Network settings - DEPLOY_OPTIONS+=" -e ${ENV_FILE}" - DEPLOY_OPTIONS+=" -e network-environment.yaml" - - # get number of nodes available in inventory - num_control_nodes=$(ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "grep -c profile:control /home/stack/instackenv.json") - num_compute_nodes=$(ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "grep -c profile:compute /home/stack/instackenv.json") - - # Custom Deploy Environment Templates - if [[ "${#deploy_options_array[@]}" -eq 0 || "${deploy_options_array['sdn_controller']}" == 'opendaylight' ]]; then - if [ "${deploy_options_array['sfc']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-sfc-opendaylight.yaml" - elif [ "${deploy_options_array['vpn']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-bgpvpn-opendaylight.yaml" - if [ "${deploy_options_array['gluon']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/services/gluon.yaml" - fi - elif [ "${deploy_options_array['vpp']}" == 'True' ]; then - if [ "${deploy_options_array['odl_vpp_netvirt']}" == "True" ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-opendaylight-netvirt-vpp.yaml" - elif [ "${deploy_options_array['odl_vpp_routing_node']}" == "dvr" ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-opendaylight-fdio-dvr.yaml" - else - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-opendaylight-honeycomb.yaml" - fi - else - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-opendaylight.yaml" - fi - SDN_IMAGE=opendaylight - elif [ "${deploy_options_array['sdn_controller']}" == 'opendaylight-external' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/opendaylight-external.yaml" - SDN_IMAGE=opendaylight - elif [ "${deploy_options_array['sdn_controller']}" == 'onos' ]; then - if [ "${deploy_options_array['sfc']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-onos-sfc.yaml" - else - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-onos.yaml" - fi - SDN_IMAGE=onos - elif [ "${deploy_options_array['sdn_controller']}" == 'ovn' ]; then - if [[ "$ha_enabled" == "True" ]]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-ml2-ovn-ha.yaml" - echo "${red}OVN HA support is not not supported... exiting.${reset}" - exit 1 - else - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-ml2-ovn.yaml" - fi - SDN_IMAGE=opendaylight - elif [ "${deploy_options_array['sdn_controller']}" == 'opencontrail' ]; then - echo -e "${red}ERROR: OpenContrail is currently unsupported...exiting${reset}" - exit 1 - elif [[ -z "${deploy_options_array['sdn_controller']}" || "${deploy_options_array['sdn_controller']}" == 'False' ]]; then - echo -e "${blue}INFO: SDN Controller disabled...will deploy nosdn scenario${reset}" - if [ "${deploy_options_array['vpp']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-ml2-vpp.yaml" - elif [ "${deploy_options_array['dataplane']}" == 'ovs_dpdk' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/neutron-ovs-dpdk.yaml" - fi - SDN_IMAGE=opendaylight - else - echo "${red}Invalid sdn_controller: ${deploy_options_array['sdn_controller']}${reset}" - echo "${red}Valid choices are opendaylight, opendaylight-external, onos, opencontrail, False, or null${reset}" - exit 1 - fi - - # Enable Tacker - if [ "${deploy_options_array['tacker']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/enable_tacker.yaml" - fi - - # Enable Congress - if [ "${deploy_options_array['congress']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/enable_congress.yaml" - fi - - # Enable Real Time Kernel (kvm4nfv) - if [ "${deploy_options_array['rt_kvm']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /home/stack/enable_rt_kvm.yaml" - fi - - # Enable Barometer service - if [ "${deploy_options_array['barometer']}" == 'True' ]; then - DEPLOY_OPTIONS+=" -e /usr/share/openstack-tripleo-heat-templates/environments/enable_barometer.yaml" - fi - -# Make sure the correct overcloud image is available - if [ ! -f $IMAGES/overcloud-full-${SDN_IMAGE}.qcow2 ]; then - echo "${red} $IMAGES/overcloud-full-${SDN_IMAGE}.qcow2 is required to execute your deployment." - echo "Please install the opnfv-apex package to provide this overcloud image for deployment.${reset}" - exit 1 - fi - - echo "Copying overcloud image to Undercloud" - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" "rm -f overcloud-full.qcow2" - scp ${SSH_OPTIONS[@]} $IMAGES/overcloud-full-${SDN_IMAGE}.qcow2 "stack@$UNDERCLOUD":overcloud-full.qcow2 - - # disable neutron openvswitch agent from starting - if [[ -n "${deploy_options_array['sdn_controller']}" && "${deploy_options_array['sdn_controller']}" != 'False' ]]; then - echo -e "${blue}INFO: Disabling neutron-openvswitch-agent from systemd${reset}" - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" < vfio_pci.modules -#!/bin/bash -exec /sbin/modprobe vfio_pci >/dev/null 2>&1 -EOF - - cat << EOF > uio_pci_generic.modules -#!/bin/bash -exec /sbin/modprobe uio_pci_generic >/dev/null 2>&1 -EOF - - LIBGUESTFS_BACKEND=direct virt-customize --upload vfio_pci.modules:/etc/sysconfig/modules/ \ - --upload uio_pci_generic.modules:/etc/sysconfig/modules/ \ - --run-command "chmod 0755 /etc/sysconfig/modules/vfio_pci.modules" \ - --run-command "chmod 0755 /etc/sysconfig/modules/uio_pci_generic.modules" \ - -a overcloud-full.qcow2 - - if [ "${deploy_options_array['dataplane']}" == 'ovs_dpdk' ]; then - sed -i "/OS::TripleO::ComputeExtraConfigPre:/c\ OS::TripleO::ComputeExtraConfigPre: ./ovs-dpdk-preconfig.yaml" network-environment.yaml - fi - -EOI - - elif [ "${deploy_options_array['dataplane']}" != 'ovs' ]; then - echo "${red}${deploy_options_array['dataplane']} not supported${reset}" - exit 1 - fi - - if [ "$debug" == 'TRUE' ]; then - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" "LIBGUESTFS_BACKEND=direct virt-customize -a overcloud-full.qcow2 --root-password password:opnfvapex" - fi - - # upgrade ovs into ovs ovs 2.6.1 with NSH function if SFC is enabled - if [[ "${deploy_options_array['sfc']}" == 'True' && "${deploy_options_array['dataplane']}" == 'ovs' ]]; then - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" < /dev/null; then - sudo systemctl restart openstack-swift-proxy -fi -echo "Uploading overcloud glance images" -openstack overcloud image upload - -echo "Configuring undercloud and discovering nodes" - - -if [[ -z "$virtual" ]]; then - openstack overcloud node import instackenv.json - openstack overcloud node introspect --all-manageable --provide - #if [[ -n "$root_disk_list" ]]; then - # TODO: replace node configure boot with ironic node-update - # TODO: configure boot is not used in ocata here anymore - #openstack overcloud node configure boot --root-device=${root_disk_list} - #https://github.com/openstack/tripleo-quickstart-extras/blob/master/roles/overcloud-prep-images/templates/overcloud-prep-images.sh.j2#L73-L130 - #ironic node-update $ironic_node add properties/root_device='{"{{ node['key'] }}": "{{ node['value'] }}"}' - #fi -else - openstack overcloud node import --provide instackenv.json -fi - -openstack flavor set --property "cpu_arch"="x86_64" baremetal -openstack flavor set --property "cpu_arch"="x86_64" control -openstack flavor set --property "cpu_arch"="x86_64" compute -echo "Configuring nameserver on ctlplane network" -dns_server_ext='' -for dns_server in ${dns_servers}; do - dns_server_ext="\${dns_server_ext} --dns-nameserver \${dns_server}" -done -openstack subnet set ctlplane-subnet \${dns_server_ext} -sed -i '/CloudDomain:/c\ CloudDomain: '${domain_name} ${ENV_FILE} -echo "Executing overcloud deployment, this could run for an extended period without output." -sleep 60 #wait for Hypervisor stats to check-in to nova -# save deploy command so it can be used for debugging -cat > deploy_command << EOF -openstack overcloud deploy --templates $DEPLOY_OPTIONS --timeout 90 -EOF -EOI - - if [ "$interactive" == "TRUE" ]; then - if ! prompt_user "Overcloud Deployment"; then - echo -e "${blue}INFO: User requests exit${reset}" - exit 0 - fi - fi - - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" </dev/null; then - $(typeset -f debug_stack) - debug_stack - exit 1 -fi -EOI - - # Configure DPDK and restart ovs agent after bringing up br-phy - if [ "${deploy_options_array['dataplane']}" == 'ovs_dpdk' ]; then - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" < instackenv.json << EOF -$instackenv_output -EOF -EOI - if output=$(python3 -B $LIB/python/apex_python_utils.py parse-inventory -f $INVENTORY_FILE $inv_virt $inv_ha --export-bash); then - echo -e "${blue}${output}${reset}" - eval "$output" - else - echo -e "${red}ERROR: Failed to parse inventory bash settings file ${INVENTORY_FILE}${reset}" - exit 1 - fi - -} diff --git a/lib/post-install-functions.sh b/lib/post-install-functions.sh deleted file mode 100755 index 7678b0d3..00000000 --- a/lib/post-install-functions.sh +++ /dev/null @@ -1,281 +0,0 @@ -#!/usr/bin/env bash -############################################################################## -# Copyright (c) 2015 Tim Rozet (Red Hat), Dan Radez (Red Hat) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -##Post configuration after install -##params: none -function configure_post_install { - local opnfv_attach_networks ovs_ip ip_range net_cidr tmp_ip af external_network_ipv6 - external_network_ipv6=False - opnfv_attach_networks="admin" - if [[ $enabled_network_list =~ "external" ]]; then - opnfv_attach_networks+=' external' - fi - - echo -e "${blue}INFO: Post Install Configuration Running...${reset}" - - echo -e "${blue}INFO: Configuring ssh for root to overcloud nodes...${reset}" - # copy host key to instack - scp ${SSH_OPTIONS[@]} /root/.ssh/id_rsa.pub "stack@$UNDERCLOUD":jumphost_id_rsa.pub - - # add host key to overcloud nodes authorized keys - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" << EOI -source stackrc -nodes=\$(nova list | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") -for node in \$nodes; do -cat ~/jumphost_id_rsa.pub | ssh -T ${SSH_OPTIONS[@]} "heat-admin@\$node" 'cat >> ~/.ssh/authorized_keys' -done -EOI - - echo -e "${blue}INFO: Checking if OVS bridges have IP addresses...${reset}" - for network in ${opnfv_attach_networks}; do - ovs_ip=$(find_ip ${NET_MAP[$network]}) - tmp_ip='' - if [ -n "$ovs_ip" ]; then - echo -e "${blue}INFO: OVS Bridge ${NET_MAP[$network]} has IP address ${ovs_ip}${reset}" - else - echo -e "${blue}INFO: OVS Bridge ${NET_MAP[$network]} missing IP, will configure${reset}" - # use last IP of allocation pool - eval "ip_range=\${${network}_overcloud_ip_range}" - ovs_ip=${ip_range##*,} - eval "net_cidr=\${${network}_cidr}" - if [[ $ovs_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - af=4 - else - af=6 - if [ "$network" == "external" ]; then - ublic_network_ipv6=True - fi - #enable ipv6 on bridge interface - echo 0 > /proc/sys/net/ipv6/conf/${NET_MAP[$network]}/disable_ipv6 - fi - sudo ip addr add ${ovs_ip}/${net_cidr##*/} dev ${NET_MAP[$network]} - sudo ip link set up ${NET_MAP[$network]} - tmp_ip=$(find_ip ${NET_MAP[$network]} $af) - if [ -n "$tmp_ip" ]; then - echo -e "${blue}INFO: OVS Bridge ${NET_MAP[$network]} IP set: ${tmp_ip}${reset}" - continue - else - echo -e "${red}ERROR: Unable to set OVS Bridge ${NET_MAP[$network]} with IP: ${ovs_ip}${reset}" - return 1 - fi - fi - done - - if [ "${deploy_options_array['dataplane']}" == 'ovs_dpdk' ]; then - echo -e "${blue}INFO: Bringing up br-phy and ovs-agent for dpdk compute nodes...${reset}" - compute_nodes=$(undercloud_connect stack "source stackrc; nova list | grep compute | wc -l") - i=0 - while [ "$i" -lt "$compute_nodes" ]; do - overcloud_connect compute${i} "sudo ifup br-phy; sudo systemctl restart neutron-openvswitch-agent" - i=$((i + 1)) - done - fi - - # TODO fix this when HA SDN controllers are supported - if [ "${deploy_options_array['sdn_controller']}" != 'False' ]; then - echo -e "${blue}INFO: Finding SDN Controller IP for overcloudrc...${reset}" - sdn_controller_ip=$(undercloud_connect stack "source stackrc;nova list | grep controller-0 | cut -d '|' -f 7 | grep -Eo [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") - echo -e "${blue}INFO: SDN Controller IP is ${sdn_controller_ip} ${reset}" - undercloud_connect stack "echo 'export SDN_CONTROLLER_IP=${sdn_controller_ip}' >> /home/stack/overcloudrc" - fi - - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" <> ./overcloudrc -fi -if ! grep -q "OS_TENANT_NAME" ./overcloudrc;then - echo "export OS_TENANT_NAME=admin" >> ./overcloudrc -fi - -if [ "${deploy_options_array['dataplane']}" == 'fdio' ] || [ "${deploy_options_array['dataplane']}" == 'ovs_dpdk' ]; then - for flavor in \$(openstack flavor list -c Name -f value); do - echo "INFO: Configuring \$flavor to use hugepage" - nova flavor-key \$flavor set hw:mem_page_size=large - done -fi - -if [ "${deploy_options_array['congress']}" == 'True' ]; then - ds_configs="--config username=\$OS_USERNAME - --config tenant_name=\$OS_PROJECT_NAME - --config password=\$OS_PASSWORD - --config auth_url=\$OS_AUTH_URL" - for s in nova neutronv2 cinder glancev2 keystone; do - ds_extra_configs="" - if [ "\$s" == "nova" ]; then - # nova's latest version is 2.38 but congress relies on nova to do - # floating ip operation instead of neutron. fip support in nova - # was depricated as of 2.35. Hard coding 2.34 for danube. - # Carlos.Goncalves working on fixes for upstream congress that - # should be ready for ocata. - nova_micro_version="2.34" - #nova_micro_version=\$(nova version-list | grep CURRENT | awk '{print \$10}') - ds_extra_configs+="--config api_version=\$nova_micro_version" - fi - if openstack congress datasource create \$s "\$s" \$ds_configs \$ds_extra_configs; then - echo "INFO: Datasource: \$s created" - else - echo "WARN: Datasource: \$s could NOT be created" - fi - done - if openstack congress datasource create doctor "doctor"; then - echo "INFO: Datasource: doctor created" - else - echo "WARN: Datsource: doctor could NOT be created" - fi -fi - - -EOI - - # we need to restart neutron-server in Gluon deployments to allow the Gluon core - # plugin to correctly register itself with Neutron - if [ "${deploy_options_array['gluon']}" == 'True' ]; then - echo "Restarting neutron-server to finalize Gluon installation" - overcloud_connect "controller0" "sudo systemctl restart neutron-server" - fi - - # for virtual, we NAT external network through Undercloud - # same goes for baremetal if only jumphost has external connectivity - if [ "$virtual" == "TRUE" ] || ! test_overcloud_connectivity && [ "$external_network_ipv6" != "True" ]; then - if [[ "$enabled_network_list" =~ "external" ]]; then - nat_cidr=${external_cidr} - else - nat_cidr=${admin_cidr} - fi - if ! configure_undercloud_nat ${nat_cidr}; then - echo -e "${red}ERROR: Unable to NAT undercloud with external net: ${nat_cidr}${reset}" - exit 1 - else - echo -e "${blue}INFO: Undercloud VM has been setup to NAT Overcloud external network${reset}" - fi - fi - - # for sfc deployments we need the vxlan workaround - if [ "${deploy_options_array['sfc']}" == 'True' ]; then - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" <&1 > /var/log/vsperf.log'" - fi - - # install docker - if [ "${deploy_options_array['yardstick']}" == 'True' ] || [ "${deploy_options_array['dovetail']}" == 'True' ]; then - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" < /dev/null || echo 'WARNING: zrpcd is not running on controller0'" -fi -} diff --git a/lib/python/apex/__init__.py b/lib/python/apex/__init__.py deleted file mode 100644 index b2a45f7d..00000000 --- a/lib/python/apex/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from .network_settings import NetworkSettings -from .deploy_settings import DeploySettings -from .network_environment import NetworkEnvironment -from .clean import clean_nodes -from .inventory import Inventory diff --git a/lib/python/apex/clean.py b/lib/python/apex/clean.py deleted file mode 100644 index 184b5ec9..00000000 --- a/lib/python/apex/clean.py +++ /dev/null @@ -1,39 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -# Clean will eventually be migrated to this file - -import logging -import pyipmi -import pyipmi.interfaces -import sys - -from .common import utils - - -def clean_nodes(inventory): - inv_dict = utils.parse_yaml(inventory) - if inv_dict is None or 'nodes' not in inv_dict: - logging.error("Inventory file is empty or missing nodes definition") - sys.exit(1) - for node, node_info in inv_dict['nodes'].items(): - logging.info("Cleaning node: {}".format(node)) - try: - interface = pyipmi.interfaces.create_interface( - 'ipmitool', interface_type='lanplus') - connection = pyipmi.create_connection(interface) - connection.session.set_session_type_rmcp(node_info['ipmi_ip']) - connection.target = pyipmi.Target(0x20) - connection.session.set_auth_type_user(node_info['ipmi_user'], - node_info['ipmi_pass']) - connection.session.establish() - connection.chassis_control_power_down() - except Exception as e: - logging.error("Failure while shutting down node {}".format(e)) - sys.exit(1) diff --git a/lib/python/apex/common/__init__.py b/lib/python/apex/common/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/python/apex/common/constants.py b/lib/python/apex/common/constants.py deleted file mode 100644 index 3aa28eab..00000000 --- a/lib/python/apex/common/constants.py +++ /dev/null @@ -1,30 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -ADMIN_NETWORK = 'admin' -TENANT_NETWORK = 'tenant' -EXTERNAL_NETWORK = 'external' -STORAGE_NETWORK = 'storage' -API_NETWORK = 'api' -CONTROLLER = 'controller' -COMPUTE = 'compute' - -OPNFV_NETWORK_TYPES = [ADMIN_NETWORK, TENANT_NETWORK, EXTERNAL_NETWORK, - STORAGE_NETWORK, API_NETWORK] -DNS_SERVERS = ["8.8.8.8", "8.8.4.4"] -NTP_SERVER = ["pool.ntp.org"] -COMPUTE = 'compute' -CONTROLLER = 'controller' -ROLES = [COMPUTE, CONTROLLER] -DOMAIN_NAME = 'localdomain.com' -COMPUTE_PRE = "OS::TripleO::ComputeExtraConfigPre" -CONTROLLER_PRE = "OS::TripleO::ControllerExtraConfigPre" -PRE_CONFIG_DIR = "/usr/share/openstack-tripleo-heat-templates/puppet/" \ - "extraconfig/pre_deploy/" -DEFAULT_ROOT_DEV = 'sda' diff --git a/lib/python/apex/common/utils.py b/lib/python/apex/common/utils.py deleted file mode 100644 index 8e6896fa..00000000 --- a/lib/python/apex/common/utils.py +++ /dev/null @@ -1,31 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import yaml - - -def str2bool(var): - if isinstance(var, bool): - return var - else: - return var.lower() in ("true", "yes") - - -def parse_yaml(yaml_file): - with open(yaml_file) as f: - parsed_dict = yaml.safe_load(f) - return parsed_dict - - -def write_str(bash_str, path=None): - if path: - with open(path, 'w') as file: - file.write(bash_str) - else: - print(bash_str) diff --git a/lib/python/apex/deploy_settings.py b/lib/python/apex/deploy_settings.py deleted file mode 100644 index 06185941..00000000 --- a/lib/python/apex/deploy_settings.py +++ /dev/null @@ -1,195 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Michael Chapman (michapma@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import yaml -import logging - -from .common import utils - -REQ_DEPLOY_SETTINGS = ['sdn_controller', - 'odl_version', - 'tacker', - 'congress', - 'dataplane', - 'sfc', - 'vpn', - 'vpp', - 'ceph', - 'gluon', - 'rt_kvm'] - -OPT_DEPLOY_SETTINGS = ['performance', - 'vsperf', - 'ceph_device', - 'yardstick', - 'dovetail', - 'odl_vpp_routing_node', - 'odl_vpp_netvirt', - 'barometer'] - -VALID_ROLES = ['Controller', 'Compute', 'ObjectStorage'] -VALID_PERF_OPTS = ['kernel', 'nova', 'vpp', 'ovs'] -VALID_DATAPLANES = ['ovs', 'ovs_dpdk', 'fdio'] - - -class DeploySettings(dict): - """ - This class parses a APEX deploy settings yaml file into an object - - Currently the parsed object is dumped into a bash global definition file - for deploy.sh consumption. This object will later be used directly as - deployment script move to python. - """ - def __init__(self, filename): - init_dict = {} - if isinstance(filename, str): - with open(filename, 'r') as deploy_settings_file: - init_dict = yaml.safe_load(deploy_settings_file) - else: - # assume input is a dict to build from - init_dict = filename - - super().__init__(init_dict) - self._validate_settings() - - def _validate_settings(self): - """ - Validates the deploy settings file provided - - DeploySettingsException will be raised if validation fails. - """ - - if 'deploy_options' not in self: - raise DeploySettingsException("No deploy options provided in" - " deploy settings file") - if 'global_params' not in self: - raise DeploySettingsException("No global options provided in" - " deploy settings file") - - deploy_options = self['deploy_options'] - if not isinstance(deploy_options, dict): - raise DeploySettingsException("deploy_options should be a list") - - if ('gluon' in self['deploy_options'] and - 'vpn' in self['deploy_options']): - if (self['deploy_options']['gluon'] is True and - self['deploy_options']['vpn'] is False): - raise DeploySettingsException( - "Invalid deployment configuration: " - "If gluon is enabled, " - "vpn also needs to be enabled") - - for setting, value in deploy_options.items(): - if setting not in REQ_DEPLOY_SETTINGS + OPT_DEPLOY_SETTINGS: - raise DeploySettingsException("Invalid deploy_option {} " - "specified".format(setting)) - if setting == 'dataplane': - if value not in VALID_DATAPLANES: - planes = ' '.join(VALID_DATAPLANES) - raise DeploySettingsException( - "Invalid dataplane {} specified. Valid dataplanes:" - " {}".format(value, planes)) - - for req_set in REQ_DEPLOY_SETTINGS: - if req_set not in deploy_options: - if req_set == 'dataplane': - self['deploy_options'][req_set] = 'ovs' - elif req_set == 'ceph': - self['deploy_options'][req_set] = True - else: - self['deploy_options'][req_set] = False - - if 'performance' in deploy_options: - if not isinstance(deploy_options['performance'], dict): - raise DeploySettingsException("Performance deploy_option" - "must be a dictionary.") - for role, role_perf_sets in deploy_options['performance'].items(): - if role not in VALID_ROLES: - raise DeploySettingsException("Performance role {}" - "is not valid, choose" - "from {}".format( - role, - " ".join(VALID_ROLES) - )) - - for key in role_perf_sets: - if key not in VALID_PERF_OPTS: - raise DeploySettingsException("Performance option {} " - "is not valid, choose" - "from {}".format( - key, - " ".join( - VALID_PERF_OPTS) - )) - - def _dump_performance(self): - """ - Creates performance settings string for bash consumption. - - Output will be in the form of a list that can be iterated over in - bash, with each string being the direct input to the performance - setting script in the form to - facilitate modification of the correct image. - """ - bash_str = 'performance_options=(\n' - deploy_options = self['deploy_options'] - for role, settings in deploy_options['performance'].items(): - for category, options in settings.items(): - for key, value in options.items(): - bash_str += "\"{} {} {} {}\"\n".format(role, - category, - key, - value) - bash_str += ')\n' - bash_str += '\n' - bash_str += 'performance_roles=(\n' - for role in self['deploy_options']['performance']: - bash_str += role + '\n' - bash_str += ')\n' - bash_str += '\n' - - return bash_str - - def _dump_deploy_options_array(self): - """ - Creates deploy settings array in bash syntax. - """ - bash_str = '' - for key, value in self['deploy_options'].items(): - if not isinstance(value, bool): - bash_str += "deploy_options_array[{}]=\"{}\"\n".format(key, - value) - else: - bash_str += "deploy_options_array[{}]={}\n".format(key, - value) - return bash_str - - def dump_bash(self, path=None): - """ - Prints settings for bash consumption. - - If optional path is provided, bash string will be written to the file - instead of stdout. - """ - bash_str = '' - for key, value in self['global_params'].items(): - bash_str += "{}={}\n".format(key, value) - if 'performance' in self['deploy_options']: - bash_str += self._dump_performance() - bash_str += self._dump_deploy_options_array() - utils.write_str(bash_str, path) - - -class DeploySettingsException(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value diff --git a/lib/python/apex/inventory.py b/lib/python/apex/inventory.py deleted file mode 100644 index 64f47b49..00000000 --- a/lib/python/apex/inventory.py +++ /dev/null @@ -1,98 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Dan Radez (dradez@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import yaml -import json -import platform - -from .common import constants -from .common import utils - - -class Inventory(dict): - """ - This class parses an APEX inventory yaml file into an object. It - generates or detects all missing fields for deployment. - - It then collapses one level of identification from the object to - convert it to a structure that can be dumped into a json file formatted - such that Triple-O can read the resulting json as an instackenv.json file. - """ - def __init__(self, source, ha=True, virtual=False): - init_dict = {} - self.root_device = constants.DEFAULT_ROOT_DEV - if isinstance(source, str): - with open(source, 'r') as inventory_file: - yaml_dict = yaml.safe_load(inventory_file) - # collapse node identifiers from the structure - init_dict['nodes'] = list(map(lambda n: n[1], - yaml_dict['nodes'].items())) - else: - # assume input is a dict to build from - init_dict = source - - # move ipmi_* to pm_* - # make mac a list - def munge_nodes(node): - node['pm_addr'] = node['ipmi_ip'] - node['pm_password'] = node['ipmi_pass'] - node['pm_user'] = node['ipmi_user'] - node['mac'] = [node['mac_address']] - if 'cpus' in node: - node['cpu'] = node['cpus'] - - for i in ('ipmi_ip', 'ipmi_pass', 'ipmi_user', 'mac_address', - 'disk_device'): - if i == 'disk_device' and 'disk_device' in node.keys(): - self.root_device = node[i] - else: - continue - del node[i] - - return node - - super().__init__({'nodes': list(map(munge_nodes, init_dict['nodes']))}) - - # verify number of nodes - if ha and len(self['nodes']) < 5: - raise InventoryException('You must provide at least 5 ' - 'nodes for HA baremetal deployment') - elif len(self['nodes']) < 2: - raise InventoryException('You must provide at least 2 nodes ' - 'for non-HA baremetal deployment') - - if virtual: - self['arch'] = platform.machine() - self['host-ip'] = '192.168.122.1' - self['power_manager'] = \ - 'nova.virt.baremetal.virtual_power_driver.VirtualPowerManager' - self['seed-ip'] = '' - self['ssh-key'] = 'INSERT_STACK_USER_PRIV_KEY' - self['ssh-user'] = 'root' - - def dump_instackenv_json(self): - print(json.dumps(dict(self), sort_keys=True, indent=4)) - - def dump_bash(self, path=None): - """ - Prints settings for bash consumption. - - If optional path is provided, bash string will be written to the file - instead of stdout. - """ - bash_str = "{}={}\n".format('root_disk_list', str(self.root_device)) - utils.write_str(bash_str, path) - - -class InventoryException(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value diff --git a/lib/python/apex/ip_utils.py b/lib/python/apex/ip_utils.py deleted file mode 100644 index ae60b705..00000000 --- a/lib/python/apex/ip_utils.py +++ /dev/null @@ -1,230 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -import ipaddress -import subprocess -import re -import logging - - -def get_ip_range(start_offset=None, count=None, end_offset=None, - cidr=None, interface=None): - """ - Generate IP range for a network (cidr) or an interface. - - If CIDR is provided, it will take precedence over interface. In this case, - The entire CIDR IP address space is considered usable. start_offset will be - calculated from the network address, and end_offset will be calculated from - the last address in subnet. - - If interface is provided, the interface IP will be used to calculate - offsets: - - If the interface IP is in the first half of the address space, - start_offset will be calculated from the interface IP, and end_offset - will be calculated from end of address space. - - If the interface IP is in the second half of the address space, - start_offset will be calculated from the network address in the address - space, and end_offset will be calculated from the interface IP. - - 2 of start_offset, end_offset and count options must be provided: - - If start_offset and end_offset are provided, a range from - start_offset to end_offset will be returned. - - If count is provided, a range from either start_offset to - (start_offset+count) or (end_offset-count) to end_offset will be - returned. The IP range returned will be of size . - Both start_offset and end_offset must be greater than 0. - - Returns IP range in the format of "first_addr,second_addr" or exception - is raised. - """ - if cidr: - if count and start_offset and not end_offset: - start_index = start_offset - end_index = start_offset + count - 1 - elif count and end_offset and not start_offset: - end_index = -1 - end_offset - start_index = -1 - end_index - count + 1 - elif start_offset and end_offset and not count: - start_index = start_offset - end_index = -1 - end_offset - else: - raise IPUtilsException("Argument error: must pass in exactly 2 of" - " start_offset, end_offset and count") - - start_ip = cidr[start_index] - end_ip = cidr[end_index] - network = cidr - elif interface: - network = interface.network - number_of_addr = network.num_addresses - if interface.ip < network[int(number_of_addr / 2)]: - if count and start_offset and not end_offset: - start_ip = interface.ip + start_offset - end_ip = start_ip + count - 1 - elif count and end_offset and not start_offset: - end_ip = network[-1 - end_offset] - start_ip = end_ip - count + 1 - elif start_offset and end_offset and not count: - start_ip = interface.ip + start_offset - end_ip = network[-1 - end_offset] - else: - raise IPUtilsException( - "Argument error: must pass in exactly 2 of" - " start_offset, end_offset and count") - else: - if count and start_offset and not end_offset: - start_ip = network[start_offset] - end_ip = start_ip + count - 1 - elif count and end_offset and not start_offset: - end_ip = interface.ip - end_offset - start_ip = end_ip - count + 1 - elif start_offset and end_offset and not count: - start_ip = network[start_offset] - end_ip = interface.ip - end_offset - else: - raise IPUtilsException( - "Argument error: must pass in exactly 2 of" - " start_offset, end_offset and count") - - else: - raise IPUtilsException("Must pass in cidr or interface to generate" - "ip range") - - range_result = _validate_ip_range(start_ip, end_ip, network) - if range_result: - ip_range = "{},{}".format(start_ip, end_ip) - return ip_range - else: - raise IPUtilsException("Invalid IP range: {},{} for network {}" - .format(start_ip, end_ip, network)) - - -def get_ip(offset, cidr=None, interface=None): - """ - Returns an IP in a network given an offset. - - Either cidr or interface must be provided, cidr takes precedence. - - If cidr is provided, offset is calculated from network address. - If interface is provided, offset is calculated from interface IP. - - offset can be positive or negative, but the resulting IP address must also - be contained in the same subnet, otherwise an exception will be raised. - - returns a IP address object. - """ - if cidr: - ip = cidr[0 + offset] - network = cidr - elif interface: - ip = interface.ip + offset - network = interface.network - else: - raise IPUtilsException("Must pass in cidr or interface to generate IP") - - if ip not in network: - raise IPUtilsException("IP {} not in network {}".format(ip, network)) - else: - return str(ip) - - -def get_interface(nic, address_family=4): - """ - Returns interface object for a given NIC name in the system - - Only global address will be returned at the moment. - - Returns interface object if an address is found for the given nic, - otherwise returns None. - """ - if not nic.strip(): - logging.error("empty nic name specified") - return None - output = subprocess.getoutput("/usr/sbin/ip -{} addr show {} scope global" - .format(address_family, nic)) - if address_family == 4: - pattern = re.compile("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}") - elif address_family == 6: - pattern = re.compile("([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}/\d{1,3}") - else: - raise IPUtilsException("Invalid address family: {}" - .format(address_family)) - match = re.search(pattern, output) - if match: - logging.info("found interface {} ip: {}".format(nic, match.group())) - return ipaddress.ip_interface(match.group()) - else: - logging.info("interface ip not found! ip address output:\n{}" - .format(output)) - return None - - -def find_gateway(interface): - """ - Validate gateway on the system - - Ensures that the provided interface object is in fact configured as default - route on the system. - - Returns gateway IP (reachable from interface) if default route is found, - otherwise returns None. - """ - - address_family = interface.version - output = subprocess.getoutput("/usr/sbin/ip -{} route".format( - address_family)) - - pattern = re.compile("default\s+via\s+(\S+)\s+") - match = re.search(pattern, output) - - if match: - gateway_ip = match.group(1) - reverse_route_output = subprocess.getoutput("/usr/sbin/ip route get {}" - .format(gateway_ip)) - pattern = re.compile("{}.+src\s+{}".format(gateway_ip, interface.ip)) - if not re.search(pattern, reverse_route_output): - logging.warning("Default route doesn't match interface specified: " - "{}".format(reverse_route_output)) - return None - else: - return gateway_ip - else: - logging.warning("Can't find gateway address on system") - return None - - -def _validate_ip_range(start_ip, end_ip, cidr): - """ - Validates an IP range is in good order and the range is part of cidr. - - Returns True if validation succeeds, False otherwise. - """ - ip_range = "{},{}".format(start_ip, end_ip) - if end_ip <= start_ip: - logging.warning("IP range {} is invalid: end_ip should be greater " - "than starting ip".format(ip_range)) - return False - if start_ip not in ipaddress.ip_network(cidr): - logging.warning('start_ip {} is not in network {}' - .format(start_ip, cidr)) - return False - if end_ip not in ipaddress.ip_network(cidr): - logging.warning('end_ip {} is not in network {}'.format(end_ip, cidr)) - return False - - return True - - -class IPUtilsException(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value diff --git a/lib/python/apex/network_environment.py b/lib/python/apex/network_environment.py deleted file mode 100644 index dd9530b8..00000000 --- a/lib/python/apex/network_environment.py +++ /dev/null @@ -1,219 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import yaml -import re -from .common.constants import ( - CONTROLLER, - COMPUTE, - ADMIN_NETWORK, - TENANT_NETWORK, - STORAGE_NETWORK, - EXTERNAL_NETWORK, - API_NETWORK, - CONTROLLER_PRE, - COMPUTE_PRE, - PRE_CONFIG_DIR -) -from .network_settings import NetworkSettings - -HEAT_NONE = 'OS::Heat::None' -PORTS = '/ports' -# Resources defined by : -EXTERNAL_RESOURCES = {'OS::TripleO::Network::External': None, - 'OS::TripleO::Network::Ports::ExternalVipPort': PORTS, - 'OS::TripleO::Controller::Ports::ExternalPort': PORTS, - 'OS::TripleO::Compute::Ports::ExternalPort': PORTS} -TENANT_RESOURCES = {'OS::TripleO::Network::Tenant': None, - 'OS::TripleO::Controller::Ports::TenantPort': PORTS, - 'OS::TripleO::Compute::Ports::TenantPort': PORTS} -STORAGE_RESOURCES = {'OS::TripleO::Network::Storage': None, - 'OS::TripleO::Network::Ports::StorageVipPort': PORTS, - 'OS::TripleO::Controller::Ports::StoragePort': PORTS, - 'OS::TripleO::Compute::Ports::StoragePort': PORTS} -API_RESOURCES = {'OS::TripleO::Network::InternalApi': None, - 'OS::TripleO::Network::Ports::InternalApiVipPort': PORTS, - 'OS::TripleO::Controller::Ports::InternalApiPort': PORTS, - 'OS::TripleO::Compute::Ports::InternalApiPort': PORTS} - -# A list of flags that will be set to true when IPv6 is enabled -IPV6_FLAGS = ["NovaIPv6", "MongoDbIPv6", "CorosyncIPv6", "CephIPv6", - "RabbitIPv6", "MemcachedIPv6"] - -reg = 'resource_registry' -param_def = 'parameter_defaults' - - -class NetworkEnvironment(dict): - """ - This class creates a Network Environment to be used in TripleO Heat - Templates. - - The class builds upon an existing network-environment file and modifies - based on a NetworkSettings object. - """ - def __init__(self, net_settings, filename, compute_pre_config=False, - controller_pre_config=False): - """ - Create Network Environment according to Network Settings - """ - init_dict = {} - if isinstance(filename, str): - with open(filename, 'r') as net_env_fh: - init_dict = yaml.safe_load(net_env_fh) - - super().__init__(init_dict) - if not isinstance(net_settings, NetworkSettings): - raise NetworkEnvException('Invalid Network Settings object') - - self._set_tht_dir() - - nets = net_settings['networks'] - - admin_cidr = nets[ADMIN_NETWORK]['cidr'] - admin_prefix = str(admin_cidr.prefixlen) - self[param_def]['ControlPlaneSubnetCidr'] = admin_prefix - self[param_def]['ControlPlaneDefaultRoute'] = \ - nets[ADMIN_NETWORK]['installer_vm']['ip'] - self[param_def]['EC2MetadataIp'] = \ - nets[ADMIN_NETWORK]['installer_vm']['ip'] - self[param_def]['DnsServers'] = net_settings['dns_servers'] - - if EXTERNAL_NETWORK in net_settings.enabled_network_list: - external_cidr = net_settings.get_network(EXTERNAL_NETWORK)['cidr'] - self[param_def]['ExternalNetCidr'] = str(external_cidr) - external_vlan = self._get_vlan(net_settings.get_network( - EXTERNAL_NETWORK)) - if isinstance(external_vlan, int): - self[param_def]['NeutronExternalNetworkBridge'] = '""' - self[param_def]['ExternalNetworkVlanID'] = external_vlan - external_range = net_settings.get_network(EXTERNAL_NETWORK)[ - 'overcloud_ip_range'] - self[param_def]['ExternalAllocationPools'] = \ - [{'start': str(external_range[0]), - 'end': str(external_range[1])}] - self[param_def]['ExternalInterfaceDefaultRoute'] = \ - net_settings.get_network(EXTERNAL_NETWORK)['gateway'] - - if external_cidr.version == 6: - postfix = '/external_v6.yaml' - else: - postfix = '/external.yaml' - else: - postfix = '/noop.yaml' - - # apply resource registry update for EXTERNAL_RESOURCES - self._config_resource_reg(EXTERNAL_RESOURCES, postfix) - - if TENANT_NETWORK in net_settings.enabled_network_list: - tenant_range = nets[TENANT_NETWORK]['overcloud_ip_range'] - self[param_def]['TenantAllocationPools'] = \ - [{'start': str(tenant_range[0]), - 'end': str(tenant_range[1])}] - tenant_cidr = nets[TENANT_NETWORK]['cidr'] - self[param_def]['TenantNetCidr'] = str(tenant_cidr) - if tenant_cidr.version == 6: - postfix = '/tenant_v6.yaml' - # set overlay_ip_version option in Neutron ML2 config - self[param_def]['NeutronOverlayIPVersion'] = "6" - else: - postfix = '/tenant.yaml' - - tenant_vlan = self._get_vlan(nets[TENANT_NETWORK]) - if isinstance(tenant_vlan, int): - self[param_def]['TenantNetworkVlanID'] = tenant_vlan - else: - postfix = '/noop.yaml' - - # apply resource registry update for TENANT_RESOURCES - self._config_resource_reg(TENANT_RESOURCES, postfix) - - if STORAGE_NETWORK in net_settings.enabled_network_list: - storage_range = nets[STORAGE_NETWORK]['overcloud_ip_range'] - self[param_def]['StorageAllocationPools'] = \ - [{'start': str(storage_range[0]), - 'end': str(storage_range[1])}] - storage_cidr = nets[STORAGE_NETWORK]['cidr'] - self[param_def]['StorageNetCidr'] = str(storage_cidr) - if storage_cidr.version == 6: - postfix = '/storage_v6.yaml' - else: - postfix = '/storage.yaml' - storage_vlan = self._get_vlan(nets[STORAGE_NETWORK]) - if isinstance(storage_vlan, int): - self[param_def]['StorageNetworkVlanID'] = storage_vlan - else: - postfix = '/noop.yaml' - - # apply resource registry update for STORAGE_RESOURCES - self._config_resource_reg(STORAGE_RESOURCES, postfix) - - if API_NETWORK in net_settings.enabled_network_list: - api_range = nets[API_NETWORK]['overcloud_ip_range'] - self[param_def]['InternalApiAllocationPools'] = \ - [{'start': str(api_range[0]), - 'end': str(api_range[1])}] - api_cidr = nets[API_NETWORK]['cidr'] - self[param_def]['InternalApiNetCidr'] = str(api_cidr) - if api_cidr.version == 6: - postfix = '/internal_api_v6.yaml' - else: - postfix = '/internal_api.yaml' - api_vlan = self._get_vlan(nets[API_NETWORK]) - if isinstance(api_vlan, int): - self[param_def]['InternalApiNetworkVlanID'] = api_vlan - else: - postfix = '/noop.yaml' - - # apply resource registry update for API_RESOURCES - self._config_resource_reg(API_RESOURCES, postfix) - - # Set IPv6 related flags to True. Not that we do not set those to False - # when IPv4 is configured, we'll use the default or whatever the user - # may have set. - if net_settings.get_ip_addr_family() == 6: - for flag in IPV6_FLAGS: - self[param_def][flag] = True - - def _get_vlan(self, network): - if isinstance(network['nic_mapping'][CONTROLLER]['vlan'], int): - return network['nic_mapping'][CONTROLLER]['vlan'] - elif isinstance(network['nic_mapping'][COMPUTE]['vlan'], int): - return network['nic_mapping'][COMPUTE]['vlan'] - else: - return 'native' - - def _set_tht_dir(self): - self.tht_dir = None - for key, prefix in TENANT_RESOURCES.items(): - if prefix is None: - prefix = '' - m = re.split('%s/\w+\.yaml' % prefix, self[reg][key]) - if m is not None and len(m) > 1: - self.tht_dir = m[0] - break - if not self.tht_dir: - raise NetworkEnvException('Unable to parse THT Directory') - - def _config_resource_reg(self, resources, postfix): - for key, prefix in resources.items(): - if prefix is None: - if postfix == '/noop.yaml': - self[reg][key] = HEAT_NONE - continue - prefix = '' - self[reg][key] = self.tht_dir + prefix + postfix - - -class NetworkEnvException(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value diff --git a/lib/python/apex/network_settings.py b/lib/python/apex/network_settings.py deleted file mode 100644 index 79b0a9d1..00000000 --- a/lib/python/apex/network_settings.py +++ /dev/null @@ -1,360 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import yaml -import logging -import ipaddress - -from copy import copy -from .common import utils -from . import ip_utils -from .common.constants import ( - CONTROLLER, - COMPUTE, - ROLES, - DOMAIN_NAME, - DNS_SERVERS, - NTP_SERVER, - ADMIN_NETWORK, - EXTERNAL_NETWORK, - OPNFV_NETWORK_TYPES, -) - - -class NetworkSettings(dict): - """ - This class parses APEX network settings yaml file into an object. It - generates or detects all missing fields for deployment. - - The resulting object will be used later to generate network environment - file as well as configuring post deployment networks. - - Currently the parsed object is dumped into a bash global definition file - for deploy.sh consumption. This object will later be used directly as - deployment script move to python. - """ - def __init__(self, filename): - init_dict = {} - if isinstance(filename, str): - with open(filename, 'r') as network_settings_file: - init_dict = yaml.safe_load(network_settings_file) - else: - # assume input is a dict to build from - init_dict = filename - super().__init__(init_dict) - - if 'apex' in self: - # merge two dics Nondestructively - def merge(pri, sec): - for key, val in sec.items(): - if key in pri: - if isinstance(val, dict): - merge(pri[key], val) - # else - # do not overwrite what's already there - else: - pri[key] = val - # merge the apex specific config into the first class settings - merge(self, copy(self['apex'])) - - self.enabled_network_list = [] - self.nics = {COMPUTE: {}, CONTROLLER: {}} - self.nics_specified = {COMPUTE: False, CONTROLLER: False} - self._validate_input() - - def get_network(self, network): - if network == EXTERNAL_NETWORK and self['networks'][network]: - for net in self['networks'][network]: - if 'public' in net: - return net - - raise NetworkSettingsException("The external network, " - "'public', should be defined " - "when external networks are " - "enabled") - else: - return self['networks'][network] - - def _validate_input(self): - """ - Validates the network settings file and populates all fields. - - NetworkSettingsException will be raised if validation fails. - """ - if not self['networks'].get(ADMIN_NETWORK, {}).get('enabled', False): - raise NetworkSettingsException("You must enable admin network " - "and configure it explicitly or " - "use auto-detection") - - for network in OPNFV_NETWORK_TYPES: - if network in self['networks']: - _network = self.get_network(network) - if _network.get('enabled', True): - logging.info("{} enabled".format(network)) - self._config_required_settings(network) - nicmap = _network['nic_mapping'] - self._validate_overcloud_nic_order(network) - iface = nicmap[CONTROLLER]['members'][0] - self._config_ip_range(network=network, - interface=iface, - ip_range='overcloud_ip_range', - start_offset=21, end_offset=21) - self.enabled_network_list.append(network) - # TODO self._config_optional_settings(network) - else: - logging.info("{} disabled, will collapse with " - "admin network".format(network)) - else: - logging.info("{} is not in specified, will collapse with " - "admin network".format(network)) - - if 'dns-domain' not in self: - self['domain_name'] = DOMAIN_NAME - self['dns_servers'] = self.get('dns_nameservers', DNS_SERVERS) - self['ntp_servers'] = self.get('ntp', NTP_SERVER) - - def _validate_overcloud_nic_order(self, network): - """ - Detects if nic order is specified per profile (compute/controller) - for network - - If nic order is specified in a network for a profile, it should be - specified for every network with that profile other than admin network - - Duplicate nic names are also not allowed across different networks - - :param network: network to detect if nic order present - :return: None - """ - for role in ROLES: - _network = self.get_network(network) - _nicmap = _network.get('nic_mapping', {}) - _role = _nicmap.get(role, {}) - interfaces = _role.get('members', []) - - if interfaces: - interface = interfaces[0] - if not isinstance(_role.get('vlan', 'native'), int) and \ - any(y == interface for x, y in self.nics[role].items()): - raise NetworkSettingsException( - "Duplicate {} already specified for " - "another network".format(interface)) - self.nics[role][network] = interface - self.nics_specified[role] = True - logging.info("{} nic order specified for network {" - "}".format(role, network)) - else: - raise NetworkSettingsException( - "Interface members are not supplied for {} network " - "for the {} role. Please add nic assignments" - "".format(network, role)) - - def _config_required_settings(self, network): - """ - Configures either CIDR or bridged_interface setting - - cidr takes precedence if both cidr and bridged_interface are specified - for a given network. - - When using bridged_interface, we will detect network setting on the - given NIC in the system. The resulting config in settings object will - be an ipaddress.network object, replacing the NIC name. - """ - _network = self.get_network(network) - # if vlan not defined then default it to native - if network is not ADMIN_NETWORK: - for role in ROLES: - if 'vlan' not in _network['nic_mapping'][role]: - _network['nic_mapping'][role]['vlan'] = 'native' - - cidr = _network.get('cidr') - - if cidr: - cidr = ipaddress.ip_network(_network['cidr']) - _network['cidr'] = cidr - logging.info("{}_cidr: {}".format(network, cidr)) - elif 'installer_vm' in _network: - ucloud_if_list = _network['installer_vm']['members'] - # If cidr is not specified, we need to know if we should find - # IPv6 or IPv4 address on the interface - ip = ipaddress.ip_address(_network['installer_vm']['ip']) - nic_if = ip_utils.get_interface(ucloud_if_list[0], ip.version) - if nic_if: - logging.info("{}_bridged_interface: {}". - format(network, nic_if)) - else: - raise NetworkSettingsException( - "Auto detection failed for {}: Unable to find valid " - "ip for interface {}".format(network, ucloud_if_list[0])) - - else: - raise NetworkSettingsException( - "Auto detection failed for {}: either installer_vm " - "members or cidr must be specified".format(network)) - - # undercloud settings - if network == ADMIN_NETWORK: - provisioner_ip = _network['installer_vm']['ip'] - iface = _network['installer_vm']['members'][0] - if not provisioner_ip: - _network['installer_vm']['ip'] = self._gen_ip(network, 1) - self._config_ip_range(network=network, interface=iface, - ip_range='dhcp_range', - start_offset=2, count=9) - self._config_ip_range(network=network, interface=iface, - ip_range='introspection_range', - start_offset=11, count=9) - elif network == EXTERNAL_NETWORK: - provisioner_ip = _network['installer_vm']['ip'] - iface = _network['installer_vm']['members'][0] - if not provisioner_ip: - _network['installer_vm']['ip'] = self._gen_ip(network, 1) - self._config_ip_range(network=network, interface=iface, - ip_range='floating_ip_range', - end_offset=2, count=20) - - gateway = _network['gateway'] - interface = _network['installer_vm']['ip'] - self._config_gateway(network, gateway, interface) - - def _config_ip_range(self, network, ip_range, interface=None, - start_offset=None, end_offset=None, count=None): - """ - Configures IP range for a given setting. - If the setting is already specified, no change will be made. - The spec for start_offset, end_offset and count are identical to - ip_utils.get_ip_range. - """ - _network = self.get_network(network) - if ip_range not in _network: - cidr = _network.get('cidr') - _ip_range = ip_utils.get_ip_range(start_offset=start_offset, - end_offset=end_offset, - count=count, - cidr=cidr, - interface=interface) - _network[ip_range] = _ip_range.split(',') - - logging.info("Config IP Range: {} {}".format(network, ip_range)) - - def _gen_ip(self, network, offset): - """ - Generate and ip offset within the given network - """ - _network = self.get_network(network) - cidr = _network.get('cidr') - ip = ip_utils.get_ip(offset, cidr) - logging.info("Config IP: {} {}".format(network, ip)) - return ip - - def _config_optional_settings(self, network): - """ - Configures optional settings: - - admin_network: - - provisioner_ip - - dhcp_range - - introspection_range - - public_network: - - provisioner_ip - - floating_ip_range - - gateway - """ - if network == ADMIN_NETWORK: - self._config_ip(network, None, 'provisioner_ip', 1) - self._config_ip_range(network=network, - ip_range='dhcp_range', - start_offset=2, count=9) - self._config_ip_range(network=network, - ip_range='introspection_range', - start_offset=11, count=9) - elif network == EXTERNAL_NETWORK: - self._config_ip(network, None, 'provisioner_ip', 1) - self._config_ip_range(network=network, - ip_range='floating_ip_range', - end_offset=2, count=20) - self._config_gateway(network) - - def _config_gateway(self, network, gateway, interface): - """ - Configures gateway setting for a given network. - - If cidr is specified, we always use the first address in the address - space for gateway. Otherwise, we detect the system gateway. - """ - _network = self.get_network(network) - if not gateway: - cidr = _network.get('cidr') - if cidr: - _gateway = ip_utils.get_ip(1, cidr) - else: - _gateway = ip_utils.find_gateway(interface) - - if _gateway: - _network['gateway'] = _gateway - else: - raise NetworkSettingsException("Failed to set gateway") - - logging.info("Config Gateway: {} {}".format(network, gateway)) - - def dump_bash(self, path=None): - """ - Prints settings for bash consumption. - - If optional path is provided, bash string will be written to the file - instead of stdout. - """ - def flatten(name, obj, delim=','): - """ - flatten lists to delim separated strings - flatten dics to underscored key names and string values - """ - if isinstance(obj, list): - return "{}=\'{}\'\n".format(name, - delim.join(map(lambda x: str(x), - obj))) - elif isinstance(obj, dict): - flat_str = '' - for k in obj: - flat_str += flatten("{}_{}".format(name, k), obj[k]) - return flat_str - elif isinstance(obj, str): - return "{}='{}'\n".format(name, obj) - else: - return "{}={}\n".format(name, str(obj)) - - bash_str = '' - for network in self.enabled_network_list: - _network = self.get_network(network) - bash_str += flatten(network, _network) - bash_str += flatten('enabled_network_list', - self.enabled_network_list, ' ') - bash_str += flatten('ip_addr_family', self.get_ip_addr_family()) - bash_str += flatten('dns_servers', self['dns_servers'], ' ') - bash_str += flatten('domain_name', self['dns-domain'], ' ') - bash_str += flatten('ntp_server', self['ntp_servers'][0], ' ') - utils.write_str(bash_str, path) - - def get_ip_addr_family(self,): - """ - Returns IP address family for current deployment. - - If any enabled network has IPv6 CIDR, the deployment is classified as - IPv6. - """ - return max([ - ipaddress.ip_network(self.get_network(n)['cidr']).version - for n in self.enabled_network_list]) - - -class NetworkSettingsException(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value diff --git a/lib/python/apex_python_utils.py b/lib/python/apex_python_utils.py deleted file mode 100755 index 70fc592d..00000000 --- a/lib/python/apex_python_utils.py +++ /dev/null @@ -1,265 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Feng Pan (fpan@redhat.com), Dan Radez (dradez@redhat.com) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import apex -import argparse -import sys -import logging -import os -import yaml - -from jinja2 import Environment -from jinja2 import FileSystemLoader - -from apex import NetworkSettings -from apex import NetworkEnvironment -from apex import DeploySettings -from apex import Inventory -from apex import ip_utils - - -def parse_net_settings(args): - """ - Parse OPNFV Apex network_settings.yaml config file - and dump bash syntax to set environment variables - - Args: - - file: string - file to network_settings.yaml file - """ - settings = NetworkSettings(args.net_settings_file) - net_env = NetworkEnvironment(settings, args.net_env_file, - args.compute_pre_config, - args.controller_pre_config) - target = args.target_dir.split('/') - target.append('network-environment.yaml') - dump_yaml(dict(net_env), '/'.join(target)) - settings.dump_bash() - - -def dump_yaml(data, file): - """ - Dumps data to a file as yaml - :param data: yaml to be written to file - :param file: filename to write to - :return: - """ - with open(file, "w") as fh: - yaml.dump(data, fh, default_flow_style=False) - - -def parse_deploy_settings(args): - settings = DeploySettings(args.file) - settings.dump_bash() - - -def run_clean(args): - apex.clean_nodes(args.file) - - -def parse_inventory(args): - inventory = Inventory(args.file, ha=args.ha, virtual=args.virtual) - if args.export_bash is True: - inventory.dump_bash() - else: - inventory.dump_instackenv_json() - - -def find_ip(args): - """ - Get and print the IP from a specific interface - - Args: - - interface: string - network interface name - - address_family: int - 4 or 6, respective to ipv4 or ipv6 - """ - interface = ip_utils.get_interface(args.interface, - args.address_family) - if interface: - print(interface.ip) - - -def build_nic_template(args): - """ - Build and print a Triple-O nic template from jinja template - - Args: - - template: string - path to jinja template to load - - enabled_networks: comma delimited list - list of networks defined in net_env.py - - ext_net_type: string - interface or br-ex, defines the external network configuration - - address_family: string - 4 or 6, respective to ipv4 or ipv6 - - ovs_dpdk_bridge: string - bridge name to use as ovs_dpdk - """ - template_dir, template = args.template.rsplit('/', 1) - - netsets = NetworkSettings(args.net_settings_file) - nets = netsets.get('networks') - ds = DeploySettings(args.deploy_settings_file).get('deploy_options') - env = Environment(loader=FileSystemLoader(template_dir), autoescape=True) - template = env.get_template(template) - - if ds['dataplane'] == 'fdio': - nets['tenant']['nic_mapping'][args.role]['phys_type'] = 'vpp_interface' - if ds['sdn_controller'] == 'opendaylight': - nets['external'][0]['nic_mapping'][args.role]['phys_type'] =\ - 'vpp_interface' - if ds.get('odl_vpp_routing_node') == 'dvr': - nets['admin']['nic_mapping'][args.role]['phys_type'] =\ - 'linux_bridge' - if ds.get('performance', {}).get(args.role.title(), {}).get('vpp', {})\ - .get('uio-driver'): - nets['tenant']['nic_mapping'][args.role]['uio-driver'] =\ - ds['performance'][args.role.title()]['vpp']['uio-driver'] - if ds['sdn_controller'] == 'opendaylight': - nets['external'][0]['nic_mapping'][args.role]['uio-driver'] =\ - ds['performance'][args.role.title()]['vpp']['uio-driver'] - if ds.get('performance', {}).get(args.role.title(), {}).get('vpp', {})\ - .get('interface-options'): - nets['tenant']['nic_mapping'][args.role]['interface-options'] =\ - ds['performance'][args.role.title()]['vpp']['interface-options'] - - print(template.render(nets=nets, - role=args.role, - external_net_af=netsets.get_ip_addr_family(), - external_net_type=args.ext_net_type, - ovs_dpdk_bridge=args.ovs_dpdk_bridge)) - - -def get_parser(): - parser = argparse.ArgumentParser() - parser.add_argument('--debug', action='store_true', default=False, - help="Turn on debug messages") - parser.add_argument('-l', '--log-file', default='/var/log/apex/apex.log', - dest='log_file', help="Log file to log to") - subparsers = parser.add_subparsers() - # parse-net-settings - net_settings = subparsers.add_parser('parse-net-settings', - help='Parse network settings file') - net_settings.add_argument('-s', '--net-settings-file', - default='network-settings.yaml', - dest='net_settings_file', - help='path to network settings file') - net_settings.add_argument('-e', '--net-env-file', - default="network-environment.yaml", - dest='net_env_file', - help='path to network environment file') - net_settings.add_argument('-td', '--target-dir', - default="/tmp", - dest='target_dir', - help='directory to write the' - 'network-environment.yaml file') - net_settings.add_argument('--compute-pre-config', - default=False, - action='store_true', - dest='compute_pre_config', - help='Boolean to enable Compute Pre Config') - net_settings.add_argument('--controller-pre-config', - action='store_true', - default=False, - dest='controller_pre_config', - help='Boolean to enable Controller Pre Config') - - net_settings.set_defaults(func=parse_net_settings) - # find-ip - get_int_ip = subparsers.add_parser('find-ip', - help='Find interface ip') - get_int_ip.add_argument('-i', '--interface', required=True, - help='Interface name') - get_int_ip.add_argument('-af', '--address-family', default=4, type=int, - choices=[4, 6], dest='address_family', - help='IP Address family') - get_int_ip.set_defaults(func=find_ip) - # nic-template - nic_template = subparsers.add_parser('nic-template', - help='Build NIC templates') - nic_template.add_argument('-r', '--role', required=True, - choices=['controller', 'compute'], - help='Role template generated for') - nic_template.add_argument('-t', '--template', required=True, - dest='template', - help='Template file to process') - nic_template.add_argument('-s', '--net-settings-file', - default='network-settings.yaml', - dest='net_settings_file', - help='path to network settings file') - nic_template.add_argument('-e', '--ext-net-type', default='interface', - dest='ext_net_type', - choices=['interface', 'vpp_interface', 'br-ex'], - help='External network type') - nic_template.add_argument('-d', '--ovs-dpdk-bridge', - default=None, dest='ovs_dpdk_bridge', - help='OVS DPDK Bridge Name') - nic_template.add_argument('--deploy-settings-file', - help='path to deploy settings file') - - nic_template.set_defaults(func=build_nic_template) - # parse-deploy-settings - deploy_settings = subparsers.add_parser('parse-deploy-settings', - help='Parse deploy settings file') - deploy_settings.add_argument('-f', '--file', - default='deploy_settings.yaml', - help='path to deploy settings file') - deploy_settings.set_defaults(func=parse_deploy_settings) - # parse-inventory - inventory = subparsers.add_parser('parse-inventory', - help='Parse inventory file') - inventory.add_argument('-f', '--file', - default='deploy_settings.yaml', - help='path to deploy settings file') - inventory.add_argument('--ha', - default=False, - action='store_true', - help='Indicate if deployment is HA or not') - inventory.add_argument('--virtual', - default=False, - action='store_true', - help='Indicate if deployment inventory is virtual') - inventory.add_argument('--export-bash', - default=False, - dest='export_bash', - action='store_true', - help='Export bash variables from inventory') - inventory.set_defaults(func=parse_inventory) - - clean = subparsers.add_parser('clean', - help='Parse deploy settings file') - clean.add_argument('-f', '--file', - help='path to inventory file') - clean.set_defaults(func=run_clean) - - return parser - - -def main(): - parser = get_parser() - args = parser.parse_args(sys.argv[1:]) - if args.debug: - logging.basicConfig(level=logging.DEBUG) - else: - apex_log_filename = args.log_file - os.makedirs(os.path.dirname(apex_log_filename), exist_ok=True) - logging.basicConfig(filename=apex_log_filename, - format='%(asctime)s %(levelname)s: %(message)s', - datefmt='%m/%d/%Y %I:%M:%S %p', - level=logging.DEBUG) - if hasattr(args, 'func'): - args.func(args) - else: - parser.print_help() - exit(1) - -if __name__ == "__main__": - main() diff --git a/lib/python/build_utils.py b/lib/python/build_utils.py deleted file mode 100644 index 14327a90..00000000 --- a/lib/python/build_utils.py +++ /dev/null @@ -1,108 +0,0 @@ -############################################################################## -# Copyright (c) 2017 Feng Pan (fpan@redhat.com) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import argparse -import git -import logging -import os -from pygerrit2.rest import GerritRestAPI -import re -import shutil -import sys - - -def clone_fork(args): - ref = None - logging.info("Cloning {}".format(args.repo)) - - try: - cm = git.Repo(search_parent_directories=True).commit().message - except git.exc.InvalidGitRepositoryError: - logging.debug('Current Apex directory is not a git repo: {}' - .format(os.getcwd())) - cm = '' - - logging.info("Current commit message: {}".format(cm)) - m = re.search('{}:\s*(\S+)'.format(args.repo), cm) - - if m: - change_id = m.group(1) - logging.info("Using change ID {} from {}".format(change_id, args.repo)) - rest = GerritRestAPI(url=args.url) - change_str = "changes/{}?o=CURRENT_REVISION".format(change_id) - change = rest.get(change_str) - try: - assert change['status'] not in 'ABANDONED' 'CLOSED',\ - 'Change {} is in {} state'.format(change_id, change['status']) - if change['status'] == 'MERGED': - logging.info('Change {} is merged, ignoring...' - .format(change_id)) - else: - current_revision = change['current_revision'] - ref = change['revisions'][current_revision]['ref'] - logging.info('setting ref to {}'.format(ref)) - except KeyError: - logging.error('Failed to get valid change data structure from url ' - '{}/{}, data returned: \n{}' - .format(change_id, change_str, change)) - raise - - # remove existing file or directory named repo - if os.path.exists(args.repo): - if os.path.isdir(args.repo): - shutil.rmtree(args.repo) - else: - os.remove(args.repo) - - ws = git.Repo.clone_from("{}/{}".format(args.url, args.repo), - args.repo, b=args.branch) - if ref: - git_cmd = ws.git - git_cmd.fetch("{}/{}".format(args.url, args.repo), ref) - git_cmd.checkout('FETCH_HEAD') - logging.info('Checked out commit:\n{}'.format(ws.head.commit.message)) - - -def get_parser(): - parser = argparse.ArgumentParser() - parser.add_argument('--debug', action='store_true', default=False, - help="Turn on debug messages") - subparsers = parser.add_subparsers() - fork = subparsers.add_parser('clone-fork', - help='Clone fork of dependent repo') - fork.add_argument('-r', '--repo', required=True, help='Name of repository') - fork.add_argument('-u', '--url', - default='https://gerrit.opnfv.org/gerrit', - help='Gerrit URL of repository') - fork.add_argument('-b', '--branch', - default='master', - help='Branch to checkout') - fork.set_defaults(func=clone_fork) - return parser - - -def main(): - parser = get_parser() - args = parser.parse_args(sys.argv[1:]) - if args.debug: - logging_level = logging.DEBUG - else: - logging_level = logging.INFO - - logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', - datefmt='%m/%d/%Y %I:%M:%S %p', - level=logging_level) - if hasattr(args, 'func'): - args.func(args) - else: - parser.print_help() - exit(1) - -if __name__ == "__main__": - main() diff --git a/lib/undercloud-functions.sh b/lib/undercloud-functions.sh deleted file mode 100755 index 08e1b7cf..00000000 --- a/lib/undercloud-functions.sh +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env bash -############################################################################## -# Copyright (c) 2015 Tim Rozet (Red Hat), Dan Radez (Red Hat) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -##verify vm exists, an has a dhcp lease assigned to it -##params: none -function setup_undercloud_vm { - local libvirt_imgs=/var/lib/libvirt/images - if ! virsh list --all | grep undercloud > /dev/null; then - undercloud_nets="default admin" - if [[ $enabled_network_list =~ "external" ]]; then - undercloud_nets+=" external" - fi - define_vm undercloud hd 30 "$undercloud_nets" 4 12288 - - ### this doesn't work for some reason I was getting hangup events so using cp instead - #virsh vol-upload --pool default --vol undercloud.qcow2 --file $BASE/stack/undercloud.qcow2 - #2015-12-05 12:57:20.569+0000: 8755: info : libvirt version: 1.2.8, package: 16.el7_1.5 (CentOS BuildSystem , 2015-11-03-13:56:46, worker1.bsys.centos.org) - #2015-12-05 12:57:20.569+0000: 8755: warning : virKeepAliveTimerInternal:143 : No response from client 0x7ff1e231e630 after 6 keepalive messages in 35 seconds - #2015-12-05 12:57:20.569+0000: 8756: warning : virKeepAliveTimerInternal:143 : No response from client 0x7ff1e231e630 after 6 keepalive messages in 35 seconds - #error: cannot close volume undercloud.qcow2 - #error: internal error: received hangup / error event on socket - #error: Reconnected to the hypervisor - - cp -f $IMAGES/undercloud.qcow2 $libvirt_imgs/undercloud.qcow2 - cp -f $IMAGES/overcloud-full.vmlinuz $libvirt_imgs/overcloud-full.vmlinuz - cp -f $IMAGES/overcloud-full.initrd $libvirt_imgs/overcloud-full.initrd - - # resize Undercloud machine - echo "Checking if Undercloud needs to be resized..." - undercloud_size=$(LIBGUESTFS_BACKEND=direct virt-filesystems --long -h --all -a $libvirt_imgs/undercloud.qcow2 |grep device | grep -Eo "[0-9\.]+G" | sed -n 's/\([0-9][0-9]*\).*/\1/p') - if [ "$undercloud_size" -lt 30 ]; then - qemu-img resize /var/lib/libvirt/images/undercloud.qcow2 +25G - LIBGUESTFS_BACKEND=direct virt-resize --expand /dev/sda1 $IMAGES/undercloud.qcow2 $libvirt_imgs/undercloud.qcow2 - LIBGUESTFS_BACKEND=direct virt-customize -a $libvirt_imgs/undercloud.qcow2 --run-command 'xfs_growfs -d /dev/sda1 || true' - new_size=$(LIBGUESTFS_BACKEND=direct virt-filesystems --long -h --all -a $libvirt_imgs/undercloud.qcow2 |grep filesystem | grep -Eo "[0-9\.]+G" | sed -n 's/\([0-9][0-9]*\).*/\1/p') - if [ "$new_size" -lt 30 ]; then - echo "Error resizing Undercloud machine, disk size is ${new_size}" - exit 1 - else - echo "Undercloud successfully resized" - fi - else - echo "Skipped Undercloud resize, upstream is large enough" - fi - - else - echo "Found existing Undercloud VM, exiting." - exit 1 - fi - - # if the VM is not running update the authkeys and start it - if ! virsh list | grep undercloud > /dev/null; then - if [ "$debug" == 'TRUE' ]; then - LIBGUESTFS_BACKEND=direct virt-customize -a $libvirt_imgs/undercloud.qcow2 --root-password password:opnfvapex - fi - - echo "Injecting ssh key to Undercloud VM" - LIBGUESTFS_BACKEND=direct virt-customize -a $libvirt_imgs/undercloud.qcow2 --run-command "mkdir -p /root/.ssh/" \ - --upload ~/.ssh/id_rsa.pub:/root/.ssh/authorized_keys \ - --run-command "chmod 600 /root/.ssh/authorized_keys && restorecon /root/.ssh/authorized_keys" \ - --run-command "cp /root/.ssh/authorized_keys /home/stack/.ssh/" \ - --run-command "chown stack:stack /home/stack/.ssh/authorized_keys && chmod 600 /home/stack/.ssh/authorized_keys" - virsh start undercloud - virsh autostart undercloud - fi - - sleep 10 # let undercloud get started up - - # get the undercloud VM IP - CNT=10 - echo -n "${blue}Waiting for Undercloud's dhcp address${reset}" - undercloud_mac=$(virsh domiflist undercloud | grep default | awk '{ print $5 }') - while ! $(arp -en | grep ${undercloud_mac} > /dev/null) && [ $CNT -gt 0 ]; do - echo -n "." - sleep 10 - CNT=$((CNT-1)) - done - UNDERCLOUD=$(arp -en | grep ${undercloud_mac} | awk {'print $1'}) - - if [ -z "$UNDERCLOUD" ]; then - echo "\n\nCan't get IP for Undercloud. Can Not Continue." - exit 1 - else - echo -e "${blue}\rUndercloud VM has IP $UNDERCLOUD${reset}" - fi - - CNT=10 - echo -en "${blue}\rValidating Undercloud VM connectivity${reset}" - while ! ping -c 1 $UNDERCLOUD > /dev/null && [ $CNT -gt 0 ]; do - echo -n "." - sleep 3 - CNT=$((CNT-1)) - done - if [ "$CNT" -eq 0 ]; then - echo "Failed to contact Undercloud. Can Not Continue" - exit 1 - fi - CNT=10 - while ! ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "echo ''" 2>&1> /dev/null && [ $CNT -gt 0 ]; do - echo -n "." - sleep 3 - CNT=$((CNT-1)) - done - if [ "$CNT" -eq 0 ]; then - echo "Failed to connect to Undercloud. Can Not Continue" - exit 1 - fi - - # extra space to overwrite the previous connectivity output - echo -e "${blue}\r ${reset}" - sleep 1 - - # ensure stack user on Undercloud machine has an ssh key - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" "if [ ! -e ~/.ssh/id_rsa.pub ]; then ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa; fi" - - # ssh key fix for stack user - ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "restorecon -r /home/stack" -} - -##Copy over the glance images and instackenv json file -##params: none -function configure_undercloud { - local controller_nic_template compute_nic_template - echo - echo "Copying configuration files to Undercloud" - echo -e "${blue}Network Environment set for Deployment: ${reset}" - cat $APEX_TMP_DIR/network-environment.yaml - scp ${SSH_OPTIONS[@]} $APEX_TMP_DIR/network-environment.yaml "stack@$UNDERCLOUD": - - # check for ODL L3/ONOS - if [ "${deploy_options_array['dataplane']}" == 'fdio' ]; then - ext_net_type=vpp_interface - else - ext_net_type=br-ex - fi - - if [ "${deploy_options_array['dataplane']}" == 'ovs_dpdk' ]; then - ovs_dpdk_bridge='br-phy' - else - ovs_dpdk_bridge='' - fi - - # for some reason putting IP on the bridge fails with pinging validation in OOO - if [ "${deploy_options_array['sfc']}" == 'True' ]; then - controller_external='interface' - else - controller_external='br-ex' - fi - - if ! controller_nic_template=$(python3 -B $LIB/python/apex_python_utils.py nic-template -r controller -s $NETSETS -t $BASE/nics-template.yaml.jinja2 -e $controller_external --deploy-settings-file $DEPLOY_SETTINGS_FILE); then - echo -e "${red}ERROR: Failed to generate controller NIC heat template ${reset}" - exit 1 - fi - - if ! compute_nic_template=$(python3 -B $LIB/python/apex_python_utils.py nic-template -r compute -s $NETSETS -t $BASE/nics-template.yaml.jinja2 -e $ext_net_type -d "$ovs_dpdk_bridge" --deploy-settings-file $DEPLOY_SETTINGS_FILE); then - echo -e "${red}ERROR: Failed to generate compute NIC heat template ${reset}" - exit 1 - fi - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" << EOI -mkdir nics/ -cat > nics/controller.yaml << EOF -$controller_nic_template -EOF -cat > nics/compute.yaml << EOF -$compute_nic_template -EOF -EOI - - # disable requiretty for sudo - ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" "sed -i 's/Defaults\s*requiretty//'" /etc/sudoers - - # configure undercloud on Undercloud VM - echo "Running undercloud installation and configuration." - echo "Logging undercloud installation to stack@undercloud:/home/stack/apex-undercloud-install.log" - ssh -T ${SSH_OPTIONS[@]} "stack@$UNDERCLOUD" << EOI -set -e -openstack-config --set undercloud.conf DEFAULT local_ip ${admin_installer_vm_ip}/${admin_cidr##*/} -openstack-config --set undercloud.conf DEFAULT network_gateway ${admin_installer_vm_ip} -openstack-config --set undercloud.conf DEFAULT network_cidr ${admin_cidr} -openstack-config --set undercloud.conf DEFAULT dhcp_start ${admin_dhcp_range%%,*} -openstack-config --set undercloud.conf DEFAULT dhcp_end ${admin_dhcp_range##*,} -openstack-config --set undercloud.conf DEFAULT inspection_iprange ${admin_introspection_range} -openstack-config --set undercloud.conf DEFAULT undercloud_debug false -openstack-config --set undercloud.conf DEFAULT undercloud_hostname "undercloud.${domain_name}" -openstack-config --set undercloud.conf DEFAULT enable_ui false -openstack-config --set undercloud.conf DEFAULT undercloud_update_packages false -sudo openstack-config --set /etc/ironic/ironic.conf disk_utils iscsi_verify_attempts 30 -sudo openstack-config --set /etc/ironic/ironic.conf disk_partitioner check_device_max_retries 40 - -if [[ -n "${deploy_options_array['ceph_device']}" ]]; then - sed -i '/ExtraConfig/a\\ ceph::profile::params::osds: {\\x27${deploy_options_array['ceph_device']}\\x27: {}}' ${ENV_FILE} -fi - -sudo sed -i '/CephClusterFSID:/c\\ CephClusterFSID: \\x27$(cat /proc/sys/kernel/random/uuid)\\x27' /usr/share/openstack-tripleo-heat-templates/environments/storage-environment.yaml -sudo sed -i '/CephMonKey:/c\\ CephMonKey: \\x27'"\$(ceph-authtool --gen-print-key)"'\\x27' /usr/share/openstack-tripleo-heat-templates/environments/storage-environment.yaml -sudo sed -i '/CephAdminKey:/c\\ CephAdminKey: \\x27'"\$(ceph-authtool --gen-print-key)"'\\x27' /usr/share/openstack-tripleo-heat-templates/environments/storage-environment.yaml - -if [ "\$(uname -i)" == 'aarch64' ]; then - -# These two fixes are done in the base OOO image build right now -# keeping them here to know that they are done and in case we need -# to take care of them in the future. -# # remove syslinux references for aarch64 -# sudo sh -xc 'cd /etc/puppet/modules/ironic/manifests && patch -p0 < puppet-ironic-manifests-pxe-pp-aarch64.patch' -# sudo sed -i '/syslinux-extlinux/d' /usr/share/instack-undercloud/puppet-stack-config/puppet-stack-config.pp -# -# # disable use_linkat in swift -# sudo sed -i 's/o_tmpfile_supported()/False/' /usr/lib/python2.7/site-packages/swift/obj/diskfile.py - - openstack-config --set undercloud.conf DEFAULT ipxe_enabled false - sudo sed -i '/ _link_ip_address_pxe_configs/a\\ _link_mac_pxe_configs(task)' /usr/lib/python2.7/site-packages/ironic/common/pxe_utils.py -fi - -openstack undercloud install &> apex-undercloud-install.log || { - # cat the undercloud install log incase it fails - echo "ERROR: openstack undercloud install has failed. Dumping Log:" - cat apex-undercloud-install.log - exit 1 -} - -if [ "\$(uname -i)" == 'aarch64' ]; then -sudo yum -y reinstall grub2-efi shim -sudo cp /boot/efi/EFI/centos/grubaa64.efi /tftpboot/grubaa64.efi -sudo mkdir -p /tftpboot/EFI/centos -sudo tee /tftpboot/EFI/centos/grub.cfg > /dev/null << EOF -set default=master -set timeout=5 -set hidden_timeout_quiet=false - -menuentry "master" { -configfile /tftpboot/\\\$net_default_ip.conf -} -EOF -sudo chmod 644 /tftpboot/EFI/centos/grub.cfg -sudo openstack-config --set /etc/ironic/ironic.conf pxe uefi_pxe_config_template \\\$pybasedir/drivers/modules/pxe_grub_config.template -sudo openstack-config --set /etc/ironic/ironic.conf pxe uefi_pxe_bootfile_name grubaa64.efi -sudo service openstack-ironic-conductor restart -sudo sed -i 's/linuxefi/linux/g' /usr/lib/python2.7/site-packages/ironic/drivers/modules/pxe_grub_config.template -sudo sed -i 's/initrdefi/initrd/g' /usr/lib/python2.7/site-packages/ironic/drivers/modules/pxe_grub_config.template -echo '' | sudo tee --append /tftpboot/map-file > /dev/null -echo 'r ^/EFI/centos/grub.cfg-(.*) /tftpboot/pxelinux.cfg/\\1' | sudo tee --append /tftpboot/map-file > /dev/null -sudo service xinetd restart -fi - -# Set nova domain name -sudo openstack-config --set /etc/nova/nova.conf DEFAULT dns_domain ${domain_name} -sudo openstack-config --set /etc/nova/nova.conf DEFAULT dhcp_domain ${domain_name} -sudo systemctl restart openstack-nova-conductor -sudo systemctl restart openstack-nova-compute -sudo systemctl restart openstack-nova-api -sudo systemctl restart openstack-nova-scheduler - -# Set neutron domain name -sudo openstack-config --set /etc/neutron/neutron.conf DEFAULT dns_domain ${domain_name} -sudo systemctl restart neutron-server -sudo systemctl restart neutron-dhcp-agent -EOI - -# configure external network -if [[ "$enabled_network_list" =~ "external" ]]; then - ssh -T ${SSH_OPTIONS[@]} "root@$UNDERCLOUD" << EOI -if [[ "$external_installer_vm_vlan" != "native" ]]; then - cat < /etc/sysconfig/network-scripts/ifcfg-vlan${external_installer_vm_vlan} -DEVICE=vlan${external_installer_vm_vlan} -ONBOOT=yes -DEVICETYPE=ovs -TYPE=OVSIntPort -BOOTPROTO=static -IPADDR=${external_installer_vm_ip} -PREFIX=${external_cidr##*/} -OVS_BRIDGE=br-ctlplane -OVS_OPTIONS="tag=${external_installer_vm_vlan}" -EOF - ifup vlan${external_installer_vm_vlan} -else - if ! ip a s eth2 | grep ${external_installer_vm_ip} > /dev/null; then - ip a a ${external_installer_vm_ip}/${external_cidr##*/} dev eth2 - ip link set up dev eth2 - fi -fi -EOI -fi - -} diff --git a/lib/utility-functions.sh b/lib/utility-functions.sh deleted file mode 100644 index c12619ae..00000000 --- a/lib/utility-functions.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env bash -# Utility Functions used by OPNFV Apex -# author: Tim Rozet (trozet@redhat.com) - -SSH_OPTIONS=(-o StrictHostKeyChecking=no -o GlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null -o LogLevel=error) - -##connects to undercloud -##params: user to login with, command to execute on undercloud (optional) -function undercloud_connect { - local user=$1 - - if [ -z "$1" ]; then - echo "Missing required argument: user to login as to undercloud" - return 1 - fi - - if [ -z "$2" ]; then - ssh ${SSH_OPTIONS[@]} ${user}@$(get_undercloud_ip) - else - ssh ${SSH_OPTIONS[@]} -T ${user}@$(get_undercloud_ip) "$2" - fi -} - -##outputs the Undercloud's IP address -##params: none -function get_undercloud_ip { - echo $(arp -an | grep $(virsh domiflist undercloud | grep default |\ - awk '{print $5}') | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") -} - -##connects to overcloud nodes -##params: node to login to, command to execute on overcloud (optional) -function overcloud_connect { - local node - local node_output - local node_ip - - if [ -z "$1" ]; then - echo "Missing required argument: overcloud node to login to" - return 1 - elif ! echo "$1" | grep -E "(controller|compute)[0-9]+" > /dev/null; then - echo "Invalid argument: overcloud node to login to must be in the format: \ -controller or compute" - return 1 - fi - - node_output=$(undercloud_connect "stack" "source stackrc; nova list") - node=$(echo "$1" | sed -E 's/([a-zA-Z]+)([0-9]+)/\1-\2/') - - node_ip=$(echo "$node_output" | grep "$node" | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") - - if [ "$node_ip" == "" ]; then - echo -e "Unable to find IP for ${node} in \n${node_output}" - return 1 - fi - - if [ -z "$2" ]; then - ssh ${SSH_OPTIONS[@]} heat-admin@${node_ip} - else - ssh ${SSH_OPTIONS[@]} -T heat-admin@${node_ip} "$2" - fi -} - -##connects to opendaylight karaf console -##params: None -function opendaylight_connect { - local opendaylight_ip - opendaylight_ip=$(undercloud_connect "stack" "cat overcloudrc | grep SDN_CONTROLLER_IP | grep -Eo [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") - - if [ "$opendaylight_ip" == "" ]; then - echo -e "Unable to find IP for OpenDaylight in overcloudrc" - return 1 - else - echo -e "Connecting to ODL Karaf console. Default password is 'karaf'" - fi - - ssh -p 8101 ${SSH_OPTIONS[@]} karaf@${opendaylight_ip} -} - -##outputs heat stack deployment failures -##params: none -function debug_stack { - source ~/stackrc - openstack stack failures list overcloud --long -} diff --git a/lib/virtual-setup-functions.sh b/lib/virtual-setup-functions.sh deleted file mode 100755 index 5f9e6ba5..00000000 --- a/lib/virtual-setup-functions.sh +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env bash -############################################################################## -# Copyright (c) 2015 Tim Rozet (Red Hat), Dan Radez (Red Hat) and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -##Create virtual nodes in virsh -##params: vcpus, ramsize -function setup_virtual_baremetal { - local vcpus ramsize held_ramsize - if [ -z "$1" ]; then - vcpus=4 - ramsize=8192 - elif [ -z "$2" ]; then - vcpus=$1 - ramsize=8192 - else - vcpus=$1 - ramsize=$(($2*1024)) - fi - #start by generating the opening yaml for the inventory-virt.yaml file - cat > $APEX_TMP_DIR/inventory-virt.yaml << EOF -nodes: -EOF - - # next create the virtual machines and add their definitions to the file - if [ "$ha_enabled" == "False" ]; then - controller_index=0 - else - controller_index=2 - # 3 controller + computes - # zero based so add 2 to compute count - if [ $VM_COMPUTES -lt 2 ]; then - VM_COMPUTES=2 - fi - fi - - # tmp var to hold ramsize in case modified during detection - held_ramsize=${ramsize} - for i in $(seq 0 $(($controller_index+$VM_COMPUTES))); do - ramsize=${held_ramsize} - if [ $i -gt $controller_index ]; then - capability="profile:compute" - if [ -n "$VM_COMPUTE_RAM" ]; then - ramsize=$((${VM_COMPUTE_RAM}*1024)) - fi - else - capability="profile:control" - if [[ "${deploy_options_array['sdn_controller']}" == 'opendaylight' && "$ramsize" -lt 12288 ]]; then - echo "WARN: RAM per controller too low. OpenDaylight specified in deployment requires at least 12GB" - echo "INFO: Increasing RAM per controller to 12GB" - ramsize=12288 - elif [[ "$ramsize" -lt 10240 ]]; then - echo "WARN: RAM per controller too low. Deployment requires at least 10GB" - echo "INFO: Increasing RAM per controller to 10GB" - ramsize=10240 - fi - fi - if ! virsh list --all | grep baremetal${i} > /dev/null; then - define_vm baremetal${i} network 41 'admin' $vcpus $ramsize - for n in tenant external storage api; do - if [[ $enabled_network_list =~ $n ]]; then - echo -n "$n " - virsh attach-interface --domain baremetal${i} --type network --source $n --model virtio --config - fi - done - else - echo "Found baremetal${i} VM, using existing VM" - fi - #virsh vol-list default | grep baremetal${i} 2>&1> /dev/null || virsh vol-create-as default baremetal${i}.qcow2 41G --format qcow2 - mac=$(virsh domiflist baremetal${i} | grep admin | awk '{ print $5 }') - - cat >> $APEX_TMP_DIR/inventory-virt.yaml << EOF - node${i}: - mac_address: "$mac" - ipmi_ip: 192.168.122.1 - ipmi_user: admin - ipmi_pass: "password" - pm_type: "pxe_ipmitool" - pm_port: "623$i" - cpu: $vcpus - memory: $ramsize - disk: 41 - arch: "$(uname -i)" - capabilities: "$capability" -EOF - vbmc add baremetal$i --port 623$i - if service firewalld status > /dev/null; then - firewall-cmd --permanent --zone=public --add-port=623$i/udp - fi - # TODO: add iptables check and commands too - vbmc start baremetal$i - done - if service firewalld status > /dev/null; then - firewall-cmd --reload - fi -} - -##Create virtual nodes in virsh -##params: name - String: libvirt name for VM -## bootdev - String: boot device for the VM -## disksize - Number: size of the disk in GB -## ovs_bridges: - List: list of ovs bridges -## vcpus - Number of VCPUs to use (defaults to 4) -## ramsize - Size of RAM for VM in MB (defaults to 8192) -function define_vm () { - local vcpus ramsize volume_path direct_boot kernel_args - - if [ -z "$5" ]; then - vcpus=4 - ramsize=8388608 - elif [ -z "$6" ]; then - vcpus=$5 - ramsize=8388608 - else - vcpus=$5 - ramsize=$(($6*1024)) - fi - - # Create the libvirt storage volume - if virsh vol-list default | grep ${1}.qcow2 2>&1> /dev/null; then - volume_path=$(virsh vol-path --pool default ${1}.qcow2 || echo "/var/lib/libvirt/images/${1}.qcow2") - echo "Volume ${1} exists. Deleting Existing Volume $volume_path" - virsh vol-dumpxml ${1}.qcow2 --pool default > /dev/null || echo '' #ok for this to fail - touch $volume_path - virsh vol-delete ${1}.qcow2 --pool default - fi - virsh vol-create-as default ${1}.qcow2 ${3}G --format qcow2 - volume_path=$(virsh vol-path --pool default ${1}.qcow2) - if [ ! -f $volume_path ]; then - echo "$volume_path Not created successfully... Aborting" - exit 1 - fi - - # undercloud need to be direct booted. - # the upstream image no longer includes the kernel and initrd - if [ "$1" == 'undercloud' ]; then - direct_boot='--direct-boot overcloud-full' - kernel_args='--kernel-arg console=ttyS0 --kernel-arg root=/dev/sda' - fi - - if [ "$(uname -i)" == 'aarch64' ]; then - diskbus='scsi' - else - diskbus='sata' - fi - - # create the VM - $LIB/configure-vm --name $1 \ - --bootdev $2 \ - --image "$volume_path" \ - --diskbus $diskbus \ - --arch $(uname -i) \ - --cpus $vcpus \ - --memory $ramsize \ - --libvirt-nic-driver virtio \ - $direct_boot \ - $kernel_args \ - --baremetal-interface $4 -} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..af2a106e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +pbr!=2.1.0,>=2.0.0 # Apache-2.0 + +libvirt-python +python-iptables +virtualbmc +PyYAML +cryptography +python-ipmi +PyYAML +Jinja2>=2.8 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..ee3105af --- /dev/null +++ b/setup.cfg @@ -0,0 +1,48 @@ +[metadata] +name = apex +summary = A ansible roles and tools for deploying OPNFV +description-file = + INFO +author = Apex Team +author-email = michapma@redhat.com trozet@redhat.com dradez@redhat.com +home-page = https://github.com/opnfv/apex +classifier = + License :: OSI Approved :: Apache Software License + Development Status :: 4 - Beta + Intended Audience :: Developers + Intended Audience :: System Administrators + Intended Audience :: Information Technology + Topic :: Utilities + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[entry_points] +console_scripts = + opnfv-deploy = apex.deploy:main + +[files] +packages = + apex +data_files = + share/opnfv-apex/ = + build/network-environment.yaml + build/opnfv-environment.yaml + build/nics-template.yaml.jinja2 + build/csit-environment.yaml + build/virtual-environment.yaml + build/baremetal-environment.yaml + build/domain.xml + share/opnfv-apex/ansible = lib/ansible/* + share/opnfv-apex/config = config/* + share/opnfv-apex/docs = docs/* + +[wheel] +universal = 1 + +[pbr] +skip_authors = True +skip_changelog = True diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..6a931a61 --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +# Copyright Red Hat, Inc. 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 setuptools + +setuptools.setup( + setup_requires=['pbr'], + pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..f22863c7 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,6 @@ +coverage>=4.0 # Apache-2.0 +mock>=2.0 # BSD +nose # LGPL +flake8<2.6.0,>=2.5.4 # MIT +pylint==1.4.5 # GPLv2 +sphinx!=1.3b1,<1.4,>=1.2.1 # BSD diff --git a/tests/config/inventory.yaml b/tests/config/inventory.yaml deleted file mode 100644 index 2abe0fc9..00000000 --- a/tests/config/inventory.yaml +++ /dev/null @@ -1,57 +0,0 @@ ---- -nodes: - node1: - mac_address: "00:25:B5:cc:00:1e" - ipmi_ip: 72.30.8.69 - ipmi_user: admin - ipmi_pass: octopus - pm_type: "pxe_ipmitool" - cpus: 2 - memory: 8192 - disk: 40 - arch: "x86_64" - capabilities: "profile:control" - node2: - mac_address: "00:25:B5:cc:00:5d" - ipmi_ip: 72.30.8.78 - ipmi_user: admin - ipmi_pass: octopus - pm_type: "pxe_ipmitool" - cpus: 2 - memory: 8192 - disk: 40 - arch: "x86_64" - capabilities: "profile:control" - node3: - mac_address: "00:25:B5:cc:00:1d" - ipmi_ip: 72.30.8.67 - ipmi_user: admin - ipmi_pass: octopus - pm_type: "pxe_ipmitool" - cpus: 2 - memory: 8192 - disk: 40 - arch: "x86_64" - capabilities: "profile:control" - node4: - mac_address: "00:25:B5:cc:00:3c" - ipmi_ip: 72.30.8.76 - ipmi_user: admin - ipmi_pass: octopus - pm_type: "pxe_ipmitool" - cpus: 2 - memory: 8192 - disk: 40 - arch: "x86_64" - capabilities: "profile:compute" - node5: - mac_address: "00:25:B5:cc:00:5b" - ipmi_ip: 72.30.8.71 - ipmi_user: admin - ipmi_pass: octopus - pm_type: "pxe_ipmitool" - cpus: 2 - memory: 8192 - disk: 40 - arch: "x86_64" - capabilities: "profile:compute" diff --git a/tests/smoke_tests/execute_smoke_tests.sh b/tests/smoke_tests/execute_smoke_tests.sh deleted file mode 100755 index 27f95251..00000000 --- a/tests/smoke_tests/execute_smoke_tests.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -python ~/snaps/snaps/test_runner.py -e ~stack/overcloudrc -n external -c -a -i -f -k -l INFO &> ~stack/smoke-tests.out \ No newline at end of file diff --git a/tests/smoke_tests/execute_tests.yml b/tests/smoke_tests/execute_tests.yml deleted file mode 100644 index 5042d230..00000000 --- a/tests/smoke_tests/execute_tests.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -- hosts: all - become: yes - become_method: sudo - become_user: root - - tasks: - - name: Copy execute_smoke_tests.sh - copy: src=execute_smoke_tests.sh dest=~/execute_smoke_tests.sh mode=0755 - - name: Execute Tests - command: sh ~/execute_smoke_tests.sh | tee ~/unit_tests.out \ No newline at end of file diff --git a/tests/smoke_tests/prepare_undercloud.yml b/tests/smoke_tests/prepare_undercloud.yml deleted file mode 100644 index 7ad769c0..00000000 --- a/tests/smoke_tests/prepare_undercloud.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -- hosts: all - become: yes - become_method: sudo - become_user: root - - tasks: - - git: repo=https://gerrit.opnfv.org/gerrit/snaps dest=~/snaps - - command: pip install -e ~/snaps/ diff --git a/tests/smoke_tests/smoke_tests.yml b/tests/smoke_tests/smoke_tests.yml deleted file mode 100644 index b67c194f..00000000 --- a/tests/smoke_tests/smoke_tests.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -- include: prepare_undercloud.yml -- include: execute_tests.yml \ No newline at end of file diff --git a/tests/test_apex_clean.py b/tests/test_apex_clean.py deleted file mode 100644 index 2a436a7c..00000000 --- a/tests/test_apex_clean.py +++ /dev/null @@ -1,41 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Tim Rozet (Red Hat) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import mock -import pyipmi -import pyipmi.chassis - -from apex import clean_nodes -from mock import patch -from nose import tools - - -class TestClean(object): - @classmethod - def setup_class(klass): - """This method is run once for each class before any tests are run""" - - @classmethod - def teardown_class(klass): - """This method is run once for each class _after_ all tests are run""" - - def setUp(self): - """This method is run once before _each_ test method is executed""" - - def teardown(self): - """This method is run once after _each_ test method is executed""" - - def test_clean(self): - with mock.patch.object(pyipmi.Session, 'establish') as mock_method: - with patch.object(pyipmi.chassis.Chassis, - 'chassis_control_power_down') as mock_method2: - clean_nodes('config/inventory.yaml') - - tools.assert_equal(mock_method.call_count, 5) - tools.assert_equal(mock_method2.call_count, 5) diff --git a/tests/test_apex_common_utils.py b/tests/test_apex_common_utils.py deleted file mode 100644 index 94598657..00000000 --- a/tests/test_apex_common_utils.py +++ /dev/null @@ -1,39 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Dan Radez (Red Hat) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import nose.tools - -from apex.common import utils - - -class TestCommonUtils(object): - @classmethod - def setup_class(klass): - """This method is run once for each class before any tests are run""" - - @classmethod - def teardown_class(klass): - """This method is run once for each class _after_ all tests are run""" - - def setUp(self): - """This method is run once before _each_ test method is executed""" - - def teardown(self): - """This method is run once after _each_ test method is executed""" - - def test_str2bool(self): - nose.tools.assert_equal(utils.str2bool(True), True) - nose.tools.assert_equal(utils.str2bool(False), False) - nose.tools.assert_equal(utils.str2bool("True"), True) - nose.tools.assert_equal(utils.str2bool("YES"), True) - - def test_parse_yaml(self): - nose.tools.assert_is_instance( - utils.parse_yaml('../config/network/network_settings.yaml'), - dict) diff --git a/tests/test_apex_deploy_settings.py b/tests/test_apex_deploy_settings.py deleted file mode 100644 index 00eb2744..00000000 --- a/tests/test_apex_deploy_settings.py +++ /dev/null @@ -1,107 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Dan Radez (Red Hat) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -# https://docs.python.org/3/library/io.html -import io -import tempfile - -from apex.deploy_settings import DeploySettings -from apex.deploy_settings import DeploySettingsException - -from nose.tools import assert_equal -from nose.tools import assert_raises -from nose.tools import assert_is_instance - -deploy_files = ('deploy_settings.yaml', - 'os-nosdn-nofeature-noha.yaml', - 'os-nosdn-ovs_dpdk-noha.yaml', - 'os-ocl-nofeature-ha.yaml', - 'os-odl-bgpvpn-ha.yaml', - 'os-odl-bgpvpn-noha.yaml', - 'os-odl-nofeature-ha.yaml', - 'os-nosdn-nofeature-ha.yaml', - 'os-nosdn-ovs_dpdk-ha.yaml', - 'os-nosdn-performance-ha.yaml', - 'os-odl-nofeature-ha.yaml', - 'os-onos-nofeature-ha.yaml', - 'os-onos-sfc-ha.yaml') - -test_deploy_content = ( - 'global_params:', - 'deploy_options: string', - """deploy_options: string -global_params:""", - """global_params: -deploy_options: - error: error -""", - """global_params: -deploy_options: - performance: string -""", - """global_params: -deploy_options: - dataplane: invalid -""", - """global_params: -deploy_options: - performance: - Controller: - error: error -""", - """global_params: -deploy_options: - performance: - InvalidRole: - error: error -""",) - - -class TestIpUtils(object): - @classmethod - def setup_class(klass): - """This method is run once for each class before any tests are run""" - - @classmethod - def teardown_class(klass): - """This method is run once for each class _after_ all tests are run""" - - def setUp(self): - """This method is run once before _each_ test method is executed""" - - def teardown(self): - """This method is run once after _each_ test method is executed""" - - def test_init(self): - for f in deploy_files: - ds = DeploySettings('../config/deploy/{}'.format(f)) - ds = DeploySettings(ds) - - def test__validate_settings(self): - for c in test_deploy_content: - try: - f = tempfile.NamedTemporaryFile(mode='w') - f.write(c) - f.flush() - assert_raises(DeploySettingsException, - DeploySettings, f.name) - finally: - f.close() - - def test_dump_bash(self): - # the performance file has the most use of the function - # so using that as the test case - ds = DeploySettings('../config/deploy/os-nosdn-performance-ha.yaml') - assert_equal(ds.dump_bash(), None) - assert_equal(ds.dump_bash(path='/dev/null'), None) - - def test_exception(sefl): - e = DeploySettingsException("test") - print(e) - assert_is_instance(e, DeploySettingsException) diff --git a/tests/test_apex_inventory.py b/tests/test_apex_inventory.py deleted file mode 100644 index ec75856b..00000000 --- a/tests/test_apex_inventory.py +++ /dev/null @@ -1,81 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Dan Radez (Red Hat) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import sys - -from apex.inventory import Inventory -from apex.inventory import InventoryException - -from nose.tools import assert_is_instance -from nose.tools import assert_raises -from nose.tools import assert_equal -from nose.tools import assert_regexp_matches -from io import StringIO - -inventory_files = ('intel_pod2_settings.yaml', - 'nokia_pod1_settings.yaml', - 'pod_example_settings.yaml') - - -class TestInventory(object): - @classmethod - def setup_class(klass): - """This method is run once for each class before any tests are run""" - - @classmethod - def teardown_class(klass): - """This method is run once for each class _after_ all tests are run""" - - def setUp(self): - """This method is run once before _each_ test method is executed""" - - def teardown(self): - """This method is run once after _each_ test method is executed""" - - def test_init(self): - for f in inventory_files: - i = Inventory('../config/inventory/{}'.format(f)) - assert_equal(i.dump_instackenv_json(), None) - - # test virtual - i = Inventory(i, virtual=True) - assert_equal(i.dump_instackenv_json(), None) - - # Remove nodes to violate HA node count - while len(i['nodes']) >= 5: - i['nodes'].pop() - assert_raises(InventoryException, - Inventory, i) - - # Remove nodes to violate non-HA node count - while len(i['nodes']) >= 2: - i['nodes'].pop() - assert_raises(InventoryException, - Inventory, i, ha=False) - - def test_exception(sefl): - e = InventoryException("test") - print(e) - assert_is_instance(e, InventoryException) - - def test_dump_bash_default(self): - i = Inventory('../config/inventory/intel_pod2_settings.yaml') - out = StringIO() - sys.stdout = out - i.dump_bash() - output = out.getvalue().strip() - assert_regexp_matches(output, 'root_disk_list=sda') - - def test_dump_bash_set_root_device(self): - i = Inventory('../config/inventory/pod_example_settings.yaml') - out = StringIO() - sys.stdout = out - i.dump_bash() - output = out.getvalue().strip() - assert_regexp_matches(output, 'root_disk_list=sdb') diff --git a/tests/test_apex_ip_utils.py b/tests/test_apex_ip_utils.py deleted file mode 100644 index e5e84b63..00000000 --- a/tests/test_apex_ip_utils.py +++ /dev/null @@ -1,135 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Dan Radez (Red Hat) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import re -import ipaddress - -from apex.ip_utils import IPUtilsException -from apex.ip_utils import get_interface -from apex.ip_utils import find_gateway -from apex.ip_utils import get_ip -from apex.ip_utils import get_ip_range -from apex.ip_utils import _validate_ip_range - -from nose.tools import assert_true -from nose.tools import assert_false -from nose.tools import assert_equal -from nose.tools import assert_raises -from nose.tools import assert_is_instance -from nose.tools import assert_regexp_matches - -from ipaddress import IPv4Address -from ipaddress import IPv6Address -from ipaddress import ip_network - - -ip4_pattern = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') -ip4_range_pattern = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3},\d{1,' - '3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') - - -def get_default_gateway_linux(): - """Read the default gateway directly from /proc.""" - with open("/proc/net/route") as fh: - for line in fh: - fields = line.strip().split() - if fields[2] not in ('00000000', 'Gateway'): - return fields[0] - - -class TestIpUtils(object): - @classmethod - def setup_class(klass): - """This method is run once for each class before any tests are run""" - klass.iface_name = get_default_gateway_linux() - iface = get_interface(klass.iface_name) - klass.iface = iface - - @classmethod - def teardown_class(klass): - """This method is run once for each class _after_ all tests are run""" - - def setUp(self): - """This method is run once before _each_ test method is executed""" - - def teardown(self): - """This method is run once after _each_ test method is executed""" - - def test_get_interface(self): - assert_equal(get_interface(''), None) - assert_equal(get_interface('notreal'), None) - assert_is_instance(get_interface(self.iface_name, - address_family=4), - IPv4Address) - # can't enable this until there's a v6 address on the ci hosts - # assert_is_instance(get_interface( - # self.iface_name, - # address_family=6), IPv6Address) - assert_raises(IPUtilsException, - get_interface, self.iface_name, 0) - - def test_find_gateway(self): - assert_is_instance(find_gateway(self.iface), str) - iface_virbr0 = get_interface('virbr0') - assert_equal(find_gateway(iface_virbr0), None) - - def test_get_ip(self): - cidr = ipaddress.ip_network("10.10.10.0/24") - assert_equal(get_ip(1, cidr=cidr), "10.10.10.1") - assert_raises(IPUtilsException, get_ip, 1000, interface=self.iface) - assert_regexp_matches(get_ip(1, interface=self.iface), ip4_pattern) - assert_raises(IPUtilsException, get_ip, 1) - - def test_get_ip_range_raises(self): - assert_raises(IPUtilsException, get_ip_range) - assert_raises(IPUtilsException, get_ip_range, interface=self.iface) - - def test_get_ip_range_with_interface(self): - assert_regexp_matches(get_ip_range(interface=self.iface, - start_offset=1, end_offset=20), - ip4_range_pattern) - assert_regexp_matches(get_ip_range(interface=self.iface, - start_offset=1, count=10), - ip4_range_pattern) - assert_regexp_matches(get_ip_range(interface=self.iface, end_offset=20, - count=10), ip4_range_pattern) - - def test_get_ip_range_with_cidr(self): - cidr = ip_network('10.10.10.0/24') - assert_raises(IPUtilsException, get_ip_range, cidr=cidr) - assert_regexp_matches(get_ip_range(cidr=cidr, start_offset=1, - end_offset=20), ip4_pattern) - assert_regexp_matches(get_ip_range(cidr=cidr, start_offset=1, - count=10), ip4_pattern) - assert_regexp_matches(get_ip_range(cidr=cidr, end_offset=20, - count=10), ip4_pattern) - - def test__validate_ip_range(self): - cidr = ip_network('10.10.10.0/24') - assert_true(_validate_ip_range( - start_ip=ipaddress.IPv4Address('10.10.10.1'), - end_ip=ipaddress.IPv4Address('10.10.10.10'), - cidr=cidr)) - assert_false(_validate_ip_range( - start_ip=ipaddress.IPv4Address('10.10.10.10'), - end_ip=ipaddress.IPv4Address('10.10.10.1'), - cidr=cidr)) - assert_false(_validate_ip_range( - start_ip=ipaddress.IPv4Address('10.10.0.1'), - end_ip=ipaddress.IPv4Address('10.10.10.10'), - cidr=cidr)) - assert_false(_validate_ip_range( - start_ip=ipaddress.IPv4Address('10.10.10.1'), - end_ip=ipaddress.IPv4Address('10.10.11.10'), - cidr=cidr)) - - def test_exception(self): - e = IPUtilsException("test") - print(e) - assert_is_instance(e, IPUtilsException) diff --git a/tests/test_apex_network_environment.py b/tests/test_apex_network_environment.py deleted file mode 100644 index b4d7e717..00000000 --- a/tests/test_apex_network_environment.py +++ /dev/null @@ -1,170 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Dan Radez (Red Hat) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import ipaddress - -from copy import copy - -from apex.common.constants import ( - EXTERNAL_NETWORK, - TENANT_NETWORK, - STORAGE_NETWORK, - API_NETWORK, - CONTROLLER) -from apex.network_settings import NetworkSettings -from apex.network_environment import ( - NetworkEnvironment, - NetworkEnvException, - EXTERNAL_RESOURCES, - TENANT_RESOURCES, - STORAGE_RESOURCES, - API_RESOURCES) - -from nose.tools import assert_equal -from nose.tools import assert_raises -from nose.tools import assert_is_instance -from nose.tools import assert_not_equal - - -class TestNetworkEnvironment(object): - @classmethod - def setup_class(klass): - """This method is run once for each class before any tests are run""" - klass.ns = NetworkSettings( - '../config/network/network_settings.yaml') - klass.ns_vlans = NetworkSettings( - '../config/network/network_settings_vlans.yaml') - klass.ns_ipv6 = NetworkSettings( - '../config/network/network_settings_v6.yaml') - - @classmethod - def teardown_class(klass): - """This method is run once for each class _after_ all tests are run""" - - def setUp(self): - """This method is run once before _each_ test method is executed""" - - def teardown(self): - """This method is run once after _each_ test method is executed""" - - def test_init(self): - assert_raises(NetworkEnvException, NetworkEnvironment, - None, '../build/network-environment.yaml') - - def test_netenv_settings_external_network_vlans(self): - # test vlans - ne = NetworkEnvironment(self.ns_vlans, - '../build/network-environment.yaml') - assert_equal(ne['parameter_defaults']['NeutronExternalNetworkBridge'], - '""') - assert_equal(ne['parameter_defaults']['ExternalNetworkVlanID'], 501) - - def test_netenv_settings_external_network_ipv6(self): - # Test IPv6 - ne = NetworkEnvironment(self.ns_ipv6, - '../build/network-environment.yaml') - regstr = ne['resource_registry']['OS::TripleO::Network::External'] - assert_equal(regstr.split('/')[-1], 'external_v6.yaml') - - def test_netenv_settings_external_network_removed(self): - ns = copy(self.ns) - # Test removing EXTERNAL_NETWORK - ns.enabled_network_list.remove(EXTERNAL_NETWORK) - ne = NetworkEnvironment(ns, '../build/network-environment.yaml') - regstr = ne['resource_registry']['OS::TripleO::Network::External'] - assert_equal(regstr.split('/')[-1], 'OS::Heat::None') - - def test_netenv_settings_tenant_network_vlans(self): - # test vlans - ne = NetworkEnvironment(self.ns_vlans, - '../build/network-environment.yaml') - assert_equal(ne['parameter_defaults']['TenantNetworkVlanID'], 401) - -# Apex is does not support v6 tenant networks -# Though there is code that would fire if a -# v6 cidr was passed in, just uncomment this to -# cover that code -# def test_netenv_settings_tenant_network_v6(self): -# # Test IPv6 -# ne = NetworkEnvironment(self.ns_ipv6, -# '../build/network-environment.yaml') -# regstr = ne['resource_registry'][next(iter(TENANT_RESOURCES.keys()))] -# assert_equal(regstr.split('/')[-1], 'tenant_v6.yaml') - - def test_netenv_settings_tenant_network_removed(self): - ns = copy(self.ns) - # Test removing TENANT_NETWORK - ns.enabled_network_list.remove(TENANT_NETWORK) - ne = NetworkEnvironment(ns, '../build/network-environment.yaml') - regstr = ne['resource_registry']['OS::TripleO::Network::Tenant'] - assert_equal(regstr.split('/')[-1], 'OS::Heat::None') - - def test_netenv_settings_storage_network_vlans(self): - # test vlans - ne = NetworkEnvironment(self.ns_vlans, - '../build/network-environment.yaml') - assert_equal(ne['parameter_defaults']['StorageNetworkVlanID'], 201) - - def test_netenv_settings_storage_network_v6(self): - # Test IPv6 - ne = NetworkEnvironment(self.ns_ipv6, - '../build/network-environment.yaml') - regstr = ne['resource_registry']['OS::TripleO::Network::Storage'] - assert_equal(regstr.split('/')[-1], 'storage_v6.yaml') - - def test_netenv_settings_storage_network_removed(self): - ns = copy(self.ns) - # Test removing STORAGE_NETWORK - ns.enabled_network_list.remove(STORAGE_NETWORK) - ne = NetworkEnvironment(ns, '../build/network-environment.yaml') - regstr = ne['resource_registry']['OS::TripleO::Network::Storage'] - assert_equal(regstr.split('/')[-1], 'OS::Heat::None') - - def test_netenv_settings_api_network_v4(self): - ns = copy(self.ns_vlans) - ns['networks'][API_NETWORK]['enabled'] = True - ns['networks'][API_NETWORK]['cidr'] = '10.11.12.0/24' - ns = NetworkSettings(ns) - # test vlans - ne = NetworkEnvironment(ns, '../build/network-environment.yaml') - assert_equal(ne['parameter_defaults']['InternalApiNetworkVlanID'], 101) - - def test_netenv_settings_api_network_vlans(self): - ns = copy(self.ns_vlans) - ns['networks'][API_NETWORK]['enabled'] = True - ns = NetworkSettings(ns) - # test vlans - ne = NetworkEnvironment(ns, '../build/network-environment.yaml') - assert_equal(ne['parameter_defaults']['InternalApiNetworkVlanID'], 101) - - def test_netenv_settings_api_network_v6(self): - # Test IPv6 - ne = NetworkEnvironment(self.ns_ipv6, - '../build/network-environment.yaml') - regstr = ne['resource_registry']['OS::TripleO::Network::InternalApi'] - assert_equal(regstr.split('/')[-1], 'internal_api_v6.yaml') - - def test_netenv_settings_api_network_removed(self): - ns = copy(self.ns) - # API_NETWORK is not in the default network settings file - ne = NetworkEnvironment(ns, '../build/network-environment.yaml') - regstr = ne['resource_registry']['OS::TripleO::Network::InternalApi'] - assert_equal(regstr.split('/')[-1], 'OS::Heat::None') - - def test_numa_configs(self): - ne = NetworkEnvironment(self.ns, '../build/network-environment.yaml', - compute_pre_config=True, - controller_pre_config=True) - assert_is_instance(ne, dict) - assert_not_equal(ne, {}) - - def test_exception(self): - e = NetworkEnvException("test") - print(e) - assert_is_instance(e, NetworkEnvException) diff --git a/tests/test_apex_network_settings.py b/tests/test_apex_network_settings.py deleted file mode 100644 index a1dbaf1c..00000000 --- a/tests/test_apex_network_settings.py +++ /dev/null @@ -1,160 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Dan Radez (Red Hat) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -from apex.common.constants import ( - EXTERNAL_NETWORK, - STORAGE_NETWORK, - ADMIN_NETWORK, -) - -from apex.network_settings import ( - NetworkSettings, - NetworkSettingsException, -) - -from nose.tools import ( - assert_equal, - assert_is_instance, - assert_raises -) - -files_dir = '../config/network/' - - -class TestNetworkSettings(object): - @classmethod - def setup_class(klass): - """This method is run once for each class before any tests are run""" - - @classmethod - def teardown_class(klass): - """This method is run once for each class _after_ all tests are run""" - - def setUp(self): - """This method is run once before _each_ test method is executed""" - - def teardown(self): - """This method is run once after _each_ test method is executed""" - - def test_init(self): - assert_is_instance( - NetworkSettings(files_dir+'network_settings.yaml'), - NetworkSettings) - - def test_init_vlans(self): - assert_is_instance( - NetworkSettings(files_dir+'network_settings_vlans.yaml'), - NetworkSettings) - -# TODO, v6 test is stuck - # def test_init_v6(self): - # assert_is_instance( - # NetworkSettings(files_dir+'network_settings_v6.yaml', True), - # NetworkSettings) - - def test_init_admin_disabled_or_missing(self): - ns = NetworkSettings(files_dir+'network_settings.yaml') - # remove admin, apex section will re-add it - ns['networks'].pop('admin', None) - assert_raises(NetworkSettingsException, NetworkSettings, ns) - # remove admin and apex - ns.pop('apex', None) - ns['networks'].pop('admin', None) - assert_raises(NetworkSettingsException, NetworkSettings, ns) - - def test_init_collapse_storage(self): - ns = NetworkSettings(files_dir+'network_settings.yaml') - # remove storage - ns['networks'].pop('storage', None) - assert_is_instance(NetworkSettings(ns), NetworkSettings) - - def test_init_missing_dns_domain(self): - ns = NetworkSettings(files_dir+'network_settings.yaml') - # remove storage - ns.pop('dns-domain', None) - assert_is_instance(NetworkSettings(ns), NetworkSettings) - - def test_dump_bash(self): - ns = NetworkSettings('../config/network/network_settings.yaml') - assert_equal(ns.dump_bash(), None) - assert_equal(ns.dump_bash(path='/dev/null'), None) - - def test_get_network_settings(self): - ns = NetworkSettings('../config/network/network_settings.yaml') - assert_is_instance(ns, NetworkSettings) - for role in ['controller', 'compute']: - nic_index = 0 - print(ns.nics) - for network in ns.enabled_network_list: - nic = 'eth' + str(nic_index) - assert_equal(ns.nics[role][network], nic) - nic_index += 1 - - def test_get_enabled_networks(self): - ns = NetworkSettings('../config/network/network_settings.yaml') - assert_is_instance(ns.enabled_network_list, list) - - def test_invalid_nic_members(self): - ns = NetworkSettings(files_dir+'network_settings.yaml') - storage_net_nicmap = ns['networks'][STORAGE_NETWORK]['nic_mapping'] - # set duplicate nic - storage_net_nicmap['controller']['members'][0] = 'eth0' - assert_raises(NetworkSettingsException, NetworkSettings, ns) - # remove nic members - storage_net_nicmap['controller']['members'] = [] - assert_raises(NetworkSettingsException, NetworkSettings, ns) - - def test_missing_vlan(self): - ns = NetworkSettings(files_dir+'network_settings.yaml') - storage_net_nicmap = ns['networks'][STORAGE_NETWORK]['nic_mapping'] - # remove vlan from storage net - storage_net_nicmap['compute'].pop('vlan', None) - assert_is_instance(NetworkSettings(ns), NetworkSettings) - -# TODO -# need to manipulate interfaces some how -# maybe for ip_utils to return something to pass this -# def test_admin_auto_detect(self): -# ns = NetworkSettings(files_dir+'network_settings.yaml') -# # remove cidr to force autodetection -# ns['networks'][ADMIN_NETWORK].pop('cidr', None) -# assert_is_instance(NetworkSettings(ns), NetworkSettings) - - def test_admin_fail_auto_detect(self): - ns = NetworkSettings(files_dir+'network_settings.yaml') - # remove cidr and installer_vm to fail autodetect - ns['networks'][ADMIN_NETWORK].pop('cidr', None) - ns['networks'][ADMIN_NETWORK].pop('installer_vm', None) - assert_raises(NetworkSettingsException, NetworkSettings, ns) - - def test_exception(self): - e = NetworkSettingsException("test") - print(e) - assert_is_instance(e, NetworkSettingsException) - - def test_config_ip(self): - ns = NetworkSettings(files_dir+'network_settings.yaml') - # set the provisioner ip to None to force _gen_ip to generate one - ns['networks'][ADMIN_NETWORK]['installer_vm']['ip'] = None - ns['networks'][EXTERNAL_NETWORK][0]['installer_vm']['ip'] = None - # Now rebuild network settings object and check for repopulated values - ns = NetworkSettings(ns) - assert_equal(ns['networks'][ADMIN_NETWORK]['installer_vm']['ip'], - '192.0.2.1') - assert_equal(ns['networks'][EXTERNAL_NETWORK][0]['installer_vm']['ip'], - '192.168.37.1') - - def test_config_gateway(self): - ns = NetworkSettings(files_dir+'network_settings.yaml') - # set the gateway ip to None to force _config_gateway to generate one - ns['networks'][EXTERNAL_NETWORK][0]['gateway'] = None - # Now rebuild network settings object and check for a repopulated value - ns = NetworkSettings(ns) - assert_equal(ns['networks'][EXTERNAL_NETWORK][0]['gateway'], - '192.168.37.1') diff --git a/tests/test_apex_python_utils_py.py b/tests/test_apex_python_utils_py.py deleted file mode 100644 index 550042f5..00000000 --- a/tests/test_apex_python_utils_py.py +++ /dev/null @@ -1,91 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Dan Radez (Red Hat) -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import shutil -import sys -import tempfile - -from test_apex_ip_utils import get_default_gateway_linux -from apex_python_utils import main -from apex_python_utils import get_parser -from apex_python_utils import parse_net_settings -from apex_python_utils import parse_deploy_settings -from apex_python_utils import find_ip -from apex_python_utils import build_nic_template -from apex_python_utils import parse_inventory - -from nose.tools import assert_equal -from nose.tools import assert_raises - - -net_sets = '../config/network/network_settings.yaml' -net_env = '../build/network-environment.yaml' -deploy_sets = '../config/deploy/deploy_settings.yaml' -nic_template = '../build/nics-template.yaml.jinja2' -inventory = '../config/inventory/pod_example_settings.yaml' - - -class TestCommonUtils(object): - @classmethod - def setup_class(klass): - """This method is run once for each class before any tests are run""" - klass.parser = get_parser() - klass.iface_name = get_default_gateway_linux() - - @classmethod - def teardown_class(klass): - """This method is run once for each class _after_ all tests are run""" - - def setUp(self): - """This method is run once before _each_ test method is executed""" - - def teardown(self): - """This method is run once after _each_ test method is executed""" - - def test_main(self): - sys.argv = ['apex_python_utils', '-l', '/dev/null'] - assert_raises(SystemExit, main) - sys.argv = ['apex_python_utils', '--debug', '-l', '/dev/null'] - assert_raises(SystemExit, main) - sys.argv = ['apex_python_utils', '-l', '/dev/null', - 'parse-deploy-settings', - '-f', deploy_sets] - assert_equal(main(), None) - - def test_parse_net_settings(self): - tmp_dir = tempfile.mkdtemp() - args = self.parser.parse_args(['parse-net-settings', - '-s', net_sets, - '-td', tmp_dir, - '-e', net_env]) - assert_equal(parse_net_settings(args), None) - shutil.rmtree(tmp_dir, ignore_errors=True) - - def test_parse_deploy_settings(self): - args = self.parser.parse_args(['parse-deploy-settings', - '-f', deploy_sets]) - assert_equal(parse_deploy_settings(args), None) - - def test_find_ip(self): - args = self.parser.parse_args(['find-ip', - '-i', self.iface_name]) - assert_equal(find_ip(args), None) - - def test_build_nic_template(self): - args = self.parser.parse_args(['nic-template', - '-s', net_sets, - '-r', 'compute', - '-t', nic_template, - '--deploy-settings-file', deploy_sets]) - assert_equal(build_nic_template(args), None) - - def test_parse_inventory(self): - args = self.parser.parse_args(['parse-inventory', - '-f', inventory]) - assert_equal(parse_inventory(args), None) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..87b6c035 --- /dev/null +++ b/tox.ini @@ -0,0 +1,26 @@ +[tox] +envlist = docs,pep8,pylint,py35 + +[testenv] +usedevelop = True +deps = + -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +commands = + coverage erase + nosetests-3.4 --with-xunit \ + --with-coverage \ + --cover-tests \ + --cover-package=apex \ + --cover-xml \ + --cover-min-percentage 90 \ + apex/tests + coverage report + +[testenv:pep8] +basepython = python3 +commands = flake8 --exclude .build,build --ignore=F401 + +[testenv:py35] +basepython = python3 + -- cgit 1.2.3-korg