summaryrefslogtreecommitdiffstats
path: root/apex
diff options
context:
space:
mode:
Diffstat (limited to 'apex')
-rw-r--r--apex/build_utils.py27
-rw-r--r--apex/builders/common_builder.py90
-rw-r--r--apex/builders/exceptions.py12
-rw-r--r--apex/builders/overcloud_builder.py73
-rw-r--r--apex/builders/undercloud_builder.py7
-rw-r--r--apex/common/constants.py16
-rw-r--r--apex/common/exceptions.py4
-rw-r--r--apex/common/utils.py63
-rw-r--r--apex/deploy.py97
-rw-r--r--apex/overcloud/deploy.py172
-rw-r--r--apex/settings/deploy_settings.py3
-rw-r--r--apex/tests/config/98faaca.diff331
-rw-r--r--apex/tests/test_apex_build_utils.py21
-rw-r--r--apex/tests/test_apex_common_builder.py69
-rw-r--r--apex/tests/test_apex_common_utils.py48
-rw-r--r--apex/tests/test_apex_deploy.py70
-rw-r--r--apex/tests/test_apex_overcloud_builder.py65
-rw-r--r--apex/tests/test_apex_overcloud_deploy.py159
-rw-r--r--apex/tests/test_apex_undercloud.py29
-rw-r--r--apex/undercloud/undercloud.py15
20 files changed, 1264 insertions, 107 deletions
diff --git a/apex/build_utils.py b/apex/build_utils.py
index c9d8472e..1c413dfd 100644
--- a/apex/build_utils.py
+++ b/apex/build_utils.py
@@ -90,6 +90,31 @@ def clone_fork(args):
logging.info('Checked out commit:\n{}'.format(ws.head.commit.message))
+def strip_patch_sections(patch, sections=['releasenotes']):
+ """
+ Removes patch sections from a diff which contain a file path
+ :param patch: patch to strip
+ :param sections: list of keywords to use to strip out of the patch file
+ :return: stripped patch
+ """
+
+ append_line = True
+ tmp_patch = []
+ for line in patch.split("\n"):
+ if re.match('diff\s', line):
+ for section in sections:
+ if re.search(section, line):
+ logging.debug("Stripping {} from patch: {}".format(
+ section, line))
+ append_line = False
+ break
+ else:
+ append_line = True
+ if append_line:
+ tmp_patch.append(line)
+ return '\n'.join(tmp_patch)
+
+
def get_patch(change_id, repo, branch, url=con.OPENSTACK_GERRIT):
logging.info("Fetching patch for change id {}".format(change_id))
change = get_change(url, repo, branch, change_id)
@@ -100,7 +125,7 @@ def get_patch(change_id, repo, branch, url=con.OPENSTACK_GERRIT):
change_id)
patch_url = "changes/{}/revisions/{}/patch".format(change_path,
current_revision)
- return rest.get(patch_url)
+ return strip_patch_sections(rest.get(patch_url))
def get_parser():
diff --git a/apex/builders/common_builder.py b/apex/builders/common_builder.py
index fd3bcc3d..05a81efe 100644
--- a/apex/builders/common_builder.py
+++ b/apex/builders/common_builder.py
@@ -10,11 +10,16 @@
# Common building utilities for undercloud and overcloud
import git
+import json
import logging
import os
+import re
+import apex.builders.overcloud_builder as oc_builder
from apex import build_utils
+from apex.builders import exceptions as exc
from apex.common import constants as con
+from apex.common import utils
from apex.virtual import utils as virt_utils
@@ -35,9 +40,38 @@ def project_to_path(project):
return "/usr/lib/python2.7/site-packages/{}".format(project)
+def project_to_docker_image(project):
+ """
+ Translates OpenStack project to OOO services that are containerized
+ :param project: name of OpenStack project
+ :return: List of OOO docker service names
+ """
+ # Fetch all docker containers in docker hub with tripleo and filter
+ # based on project
+ hub_output = utils.open_webpage(con.DOCKERHUB_OOO, timeout=10)
+ try:
+ results = json.loads(hub_output.decode())['results']
+ except Exception as e:
+ logging.error("Unable to parse docker hub output for"
+ "tripleoupstream repository")
+ logging.debug("HTTP response from dockerhub:\n{}".format(hub_output))
+ raise exc.ApexCommonBuilderException(
+ "Failed to parse docker image info from Docker Hub: {}".format(e))
+ logging.debug("Docker Hub tripleoupstream entities found: {}".format(
+ results))
+ docker_images = list()
+ for result in results:
+ if result['name'].startswith("centos-binary-{}".format(project)):
+ # add as docker image shortname (just service name)
+ docker_images.append(result['name'].replace('centos-binary-', ''))
+
+ return docker_images
+
+
def add_upstream_patches(patches, image, tmp_dir,
default_branch=os.path.join('stable',
- con.DEFAULT_OS_VERSION)):
+ con.DEFAULT_OS_VERSION),
+ uc_ip=None, docker_tag=None):
"""
Adds patches from upstream OpenStack gerrit to Undercloud for deployment
:param patches: list of patches
@@ -45,10 +79,13 @@ def add_upstream_patches(patches, image, tmp_dir,
:param tmp_dir: to store temporary patch files
:param default_branch: default branch to fetch commit (if not specified
in patch)
- :return: None
+ :param uc_ip: undercloud IP (required only for docker patches)
+ :param docker_tag: Docker Tag (required only for docker patches)
+ :return: Set of docker services patched (if applicable)
"""
virt_ops = [{con.VIRT_INSTALL: 'patch'}]
logging.debug("Evaluating upstream patches:\n{}".format(patches))
+ docker_services = set()
for patch in patches:
assert isinstance(patch, dict)
assert all(i in patch.keys() for i in ['project', 'change-id'])
@@ -60,21 +97,52 @@ def add_upstream_patches(patches, image, tmp_dir,
patch['project'], branch)
if patch_diff:
patch_file = "{}.patch".format(patch['change-id'])
- patch_file_path = os.path.join(tmp_dir, patch_file)
+ project_path = project_to_path(patch['project'])
+ # If docker tag and python we know this patch belongs on docker
+ # container for a docker service. Therefore we build the dockerfile
+ # and move the patch into the containers directory. We also assume
+ # this builder call is for overcloud, because we do not support
+ # undercloud containers
+ if docker_tag and 'python' in project_path:
+ # Projects map to multiple THT services, need to check which
+ # are supported
+ ooo_docker_services = project_to_docker_image(patch['project'])
+ else:
+ ooo_docker_services = []
+ # If we found services, then we treat the patch like it applies to
+ # docker only
+ if ooo_docker_services:
+ os_version = default_branch.replace('stable/', '')
+ for service in ooo_docker_services:
+ docker_services = docker_services.union({service})
+ docker_cmds = [
+ "WORKDIR {}".format(project_path),
+ "ADD {} {}".format(patch_file, project_path),
+ "RUN patch -p1 < {}".format(patch_file)
+ ]
+ src_img_uri = "{}:8787/{}/centos-binary-{}:" \
+ "{}".format(uc_ip, os_version, service,
+ docker_tag)
+ oc_builder.build_dockerfile(service, tmp_dir, docker_cmds,
+ src_img_uri)
+ patch_file_path = os.path.join(tmp_dir, 'containers',
+ patch_file)
+ else:
+ patch_file_path = os.path.join(tmp_dir, patch_file)
+ virt_ops.extend([
+ {con.VIRT_UPLOAD: "{}:{}".format(patch_file_path,
+ project_path)},
+ {con.VIRT_RUN_CMD: "cd {} && patch -p1 < {}".format(
+ project_path, patch_file)}])
+ logging.info("Adding patch {} to {}".format(patch_file,
+ image))
with open(patch_file_path, 'w') as fh:
fh.write(patch_diff)
- project_path = project_to_path(patch['project'])
- virt_ops.extend([
- {con.VIRT_UPLOAD: "{}:{}".format(patch_file_path,
- project_path)},
- {con.VIRT_RUN_CMD: "cd {} && patch -p1 < {}".format(
- project_path, patch_file)}])
- logging.info("Adding patch {} to {}".format(patch_file,
- image))
else:
logging.info("Ignoring patch:\n{}".format(patch))
if len(virt_ops) > 1:
virt_utils.virt_customize(virt_ops, image)
+ return docker_services
def add_repo(repo_url, repo_name, image, tmp_dir):
diff --git a/apex/builders/exceptions.py b/apex/builders/exceptions.py
new file mode 100644
index 00000000..b88f02bf
--- /dev/null
+++ b/apex/builders/exceptions.py
@@ -0,0 +1,12 @@
+##############################################################################
+# Copyright (c) 2018 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 ApexCommonBuilderException(Exception):
+ pass
diff --git a/apex/builders/overcloud_builder.py b/apex/builders/overcloud_builder.py
index e7b07963..a84d100b 100644
--- a/apex/builders/overcloud_builder.py
+++ b/apex/builders/overcloud_builder.py
@@ -10,13 +10,17 @@
# Used to modify overcloud qcow2 image
import logging
+import os
+import tarfile
-from apex.builders import common_builder as c_builder
+import apex.builders.common_builder
from apex.common import constants as con
+from apex.common.exceptions import ApexBuildException
from apex.virtual import utils as virt_utils
-def inject_opendaylight(odl_version, image, tmp_dir):
+def inject_opendaylight(odl_version, image, tmp_dir, uc_ip,
+ os_version, docker_tag=None):
assert odl_version in con.VALID_ODL_VERSIONS
# add repo
if odl_version == 'master':
@@ -28,18 +32,77 @@ def inject_opendaylight(odl_version, image, tmp_dir):
odl_url = "https://nexus.opendaylight.org/content/repositories" \
"/opendaylight-{}-epel-7-x86_64-devel/".format(odl_pkg_version)
repo_name = "opendaylight-{}".format(odl_pkg_version)
- c_builder.add_repo(odl_url, repo_name, image, tmp_dir)
+ apex.builders.common_builder.add_repo(odl_url, repo_name, image, tmp_dir)
# download puppet-opendaylight
- archive = c_builder.create_git_archive(
+ archive = apex.builders.common_builder.create_git_archive(
repo_url=con.PUPPET_ODL_URL, repo_name='puppet-opendaylight',
tmp_dir=tmp_dir, branch=branch, prefix='opendaylight/')
# install ODL, puppet-odl
virt_ops = [
- {con.VIRT_INSTALL: 'opendaylight'},
{con.VIRT_UPLOAD: "{}:/etc/puppet/modules/".format(archive)},
{con.VIRT_RUN_CMD: 'rm -rf /etc/puppet/modules/opendaylight'},
{con.VIRT_RUN_CMD: "cd /etc/puppet/modules/ && tar xvf "
"puppet-opendaylight.tar"}
]
+ if docker_tag:
+ docker_cmds = [
+ "RUN yum remove opendaylight -y",
+ "RUN echo $'[opendaylight]\\n\\",
+ "baseurl={}\\n\\".format(odl_url),
+ "gpgcheck=0\\n\\",
+ "enabled=1' > /etc/yum.repos.d/opendaylight.repo",
+ "RUN yum -y install opendaylight"
+ ]
+ src_img_uri = "{}:8787/{}/centos-binary-{}:" \
+ "{}".format(uc_ip, os_version, 'opendaylight',
+ docker_tag)
+ build_dockerfile('opendaylight', tmp_dir, docker_cmds, src_img_uri)
+ else:
+ virt_ops.append({con.VIRT_INSTALL: 'opendaylight'})
virt_utils.virt_customize(virt_ops, image)
logging.info("OpenDaylight injected into {}".format(image))
+
+
+def build_dockerfile(service, tmp_dir, docker_cmds, src_image_uri):
+ """
+ Builds docker file per service and stores it in a
+ tmp_dir/containers/<service> directory. If the Dockerfile already exists,
+ simply append the docker cmds to it.
+ :param service: name of sub-directory to store Dockerfile in
+ :param tmp_dir: Temporary directory to store the container's dockerfile in
+ :param docker_cmds: List of commands to insert into the dockerfile
+ :param src_image_uri: Docker URI format for where the source image exists
+ :return: None
+ """
+ logging.debug("Building Dockerfile for {} with docker_cmds: {}".format(
+ service, docker_cmds))
+ c_dir = os.path.join(tmp_dir, 'containers')
+ service_dir = os.path.join(c_dir, service)
+ if not os.path.isdir(service_dir):
+ os.makedirs(service_dir, exist_ok=True)
+ from_cmd = "FROM {}\n".format(src_image_uri)
+ service_file = os.path.join(service_dir, 'Dockerfile')
+ assert isinstance(docker_cmds, list)
+ if os.path.isfile(service_file):
+ append_cmds = True
+ else:
+ append_cmds = False
+ with open(service_file, "a+") as fh:
+ if not append_cmds:
+ fh.write(from_cmd)
+ fh.write('\n'.join(docker_cmds))
+
+
+def archive_docker_patches(tmp_dir):
+ """
+ Archives Overcloud docker patches into a tar file for upload to Undercloud
+ :param tmp_dir: temporary directory where containers folder is stored
+ :return: None
+ """
+ container_path = os.path.join(tmp_dir, 'containers')
+ if not os.path.isdir(container_path):
+ raise ApexBuildException("Docker directory for patches not found: "
+ "{}".format(container_path))
+ archive_file = os.path.join(tmp_dir, 'docker_patches.tar.gz')
+ with tarfile.open(archive_file, "w:gz") as tar:
+ tar.add(container_path, arcname=os.path.basename(container_path))
diff --git a/apex/builders/undercloud_builder.py b/apex/builders/undercloud_builder.py
index baba8a55..268bad7f 100644
--- a/apex/builders/undercloud_builder.py
+++ b/apex/builders/undercloud_builder.py
@@ -20,6 +20,11 @@ def add_upstream_packages(image):
:return: None
"""
virt_ops = list()
+ # FIXME(trozet): we have to lock to this beta ceph ansible package because
+ # the current RPM versioning is wrong and an older package has a higher
+ # version than this package. We should change to just 'ceph-ansible'
+ # once the package/repo has been fixed. Note: luminous is fine here
+ # because Apex will only support container deployment for Queens and later
pkgs = [
'openstack-utils',
'ceph-common',
@@ -29,6 +34,8 @@ def add_upstream_packages(image):
'docker-distribution',
'openstack-tripleo-validations',
'libguestfs-tools',
+ 'http://mirror.centos.org/centos/7/storage/x86_64/ceph-luminous' +
+ '/ceph-ansible-3.1.0-0.beta3.1.el7.noarch.rpm'
]
for pkg in pkgs:
diff --git a/apex/common/constants.py b/apex/common/constants.py
index a2b9a634..0aa6a6ca 100644
--- a/apex/common/constants.py
+++ b/apex/common/constants.py
@@ -39,10 +39,14 @@ VIRT_PW = '--root-password'
THT_DIR = '/usr/share/openstack-tripleo-heat-templates'
THT_ENV_DIR = os.path.join(THT_DIR, 'environments')
+THT_DOCKER_ENV_DIR = os.path.join(THT_ENV_DIR, 'services-docker')
DEFAULT_OS_VERSION = 'pike'
DEFAULT_ODL_VERSION = 'nitrogen'
VALID_ODL_VERSIONS = ['carbon', 'nitrogen', 'oxygen', 'master']
+CEPH_VERSION_MAP = {'pike': 'jewel',
+ 'queens': 'luminous',
+ 'master': 'luminous'}
PUPPET_ODL_URL = 'https://git.opendaylight.org/gerrit/integration/packaging' \
'/puppet-opendaylight'
DEBUG_OVERCLOUD_PW = 'opnfvapex'
@@ -50,3 +54,15 @@ NET_ENV_FILE = 'network-environment.yaml'
DEPLOY_TIMEOUT = 90
UPSTREAM_RDO = 'https://images.rdoproject.org/pike/delorean/current-tripleo/'
OPENSTACK_GERRIT = 'https://review.openstack.org'
+
+DOCKER_TAG = 'current-tripleo-rdo'
+# Maps regular service files to docker versions
+# None value means mapping is same as key
+VALID_DOCKER_SERVICES = {
+ 'neutron-opendaylight.yaml': None,
+ 'neutron-opendaylight-dpdk.yaml': None,
+ 'neutron-opendaylight-sriov.yaml': None,
+ 'neutron-ml2-ovn.yaml': 'neutron-ovn.yaml'
+}
+DOCKERHUB_OOO = ('https://registry.hub.docker.com/v2/repositories'
+ '/tripleoupstream/?page_size=1024')
diff --git a/apex/common/exceptions.py b/apex/common/exceptions.py
index 54d99834..a4d390a4 100644
--- a/apex/common/exceptions.py
+++ b/apex/common/exceptions.py
@@ -18,3 +18,7 @@ class JumpHostNetworkException(Exception):
class ApexCleanException(Exception):
pass
+
+
+class ApexBuildException(Exception):
+ pass
diff --git a/apex/common/utils.py b/apex/common/utils.py
index b727b11a..cb7cbe13 100644
--- a/apex/common/utils.py
+++ b/apex/common/utils.py
@@ -22,6 +22,8 @@ import urllib.request
import urllib.parse
import yaml
+from apex.common import exceptions as exc
+
def str2bool(var):
if isinstance(var, bool):
@@ -139,30 +141,45 @@ def run_ansible(ansible_vars, playbook, host='localhost', user='root',
raise Exception(e)
-def fetch_upstream_and_unpack(dest, url, targets):
+def fetch_upstream_and_unpack(dest, url, targets, fetch=True):
"""
Fetches targets from a url destination and downloads them if they are
newer. Also unpacks tar files in dest dir.
:param dest: Directory to download and unpack files to
:param url: URL where target files are located
:param targets: List of target files to download
+ :param fetch: Whether or not to fetch latest from internet (boolean)
:return: None
"""
os.makedirs(dest, exist_ok=True)
assert isinstance(targets, list)
for target in targets:
- download_target = True
target_url = urllib.parse.urljoin(url, target)
target_dest = os.path.join(dest, target)
- logging.debug("Fetching and comparing upstream target: \n{}".format(
- target_url))
- try:
- u = urllib.request.urlopen(target_url)
- except urllib.error.URLError as e:
- logging.error("Failed to fetch target url. Error: {}".format(
- e.reason))
- raise
- if os.path.isfile(target_dest):
+ target_exists = os.path.isfile(target_dest)
+ if fetch:
+ download_target = True
+ elif not target_exists:
+ logging.warning("no-fetch requested but target: {} is not "
+ "cached, will download".format(target_dest))
+ download_target = True
+ else:
+ logging.info("no-fetch requested and previous cache exists for "
+ "target: {}. Will skip download".format(target_dest))
+ download_target = False
+
+ if download_target:
+ logging.debug("Fetching and comparing upstream"
+ " target: \n{}".format(target_url))
+ try:
+ u = urllib.request.urlopen(target_url)
+ except urllib.error.URLError as e:
+ logging.error("Failed to fetch target url. Error: {}".format(
+ e.reason))
+ raise
+ # Check if previous file and fetch we need to compare files to
+ # determine if download is necessary
+ if target_exists and download_target:
logging.debug("Previous file found: {}".format(target_dest))
metadata = u.info()
headers = metadata.items()
@@ -186,6 +203,7 @@ def fetch_upstream_and_unpack(dest, url, targets):
download_target = False
else:
logging.debug('Unable to find last modified url date')
+
if download_target:
urllib.request.urlretrieve(target_url, filename=target_dest)
logging.info("Target downloaded: {}".format(target))
@@ -220,3 +238,26 @@ def internet_connectivity():
except (urllib.request.URLError, socket.timeout):
logging.debug('No internet connectivity detected')
return False
+
+
+def open_webpage(url, timeout=5):
+ try:
+ response = urllib.request.urlopen(url, timeout=timeout)
+ return response.read()
+ except (urllib.request.URLError, socket.timeout):
+ logging.error("Unable to open URL: {}".format(url))
+ raise
+
+
+def edit_tht_env(env_file, section, settings):
+ assert isinstance(settings, dict)
+ with open(env_file) as fh:
+ data = yaml.safe_load(fh)
+
+ if section not in data.keys():
+ data[section] = {}
+ for setting, value in settings.items():
+ data[section][setting] = value
+ with open(env_file, 'w') as fh:
+ yaml.safe_dump(data, fh, default_flow_style=False)
+ logging.debug("Data written to env file {}:\n{}".format(env_file, data))
diff --git a/apex/deploy.py b/apex/deploy.py
index b9267a3c..7bc5568f 100644
--- a/apex/deploy.py
+++ b/apex/deploy.py
@@ -21,13 +21,13 @@ import tempfile
import apex.virtual.configure_vm as vm_lib
import apex.virtual.utils as virt_utils
+import apex.builders.common_builder as c_builder
+import apex.builders.overcloud_builder as oc_builder
+import apex.builders.undercloud_builder as uc_builder
from apex import DeploySettings
from apex import Inventory
from apex import NetworkEnvironment
from apex import NetworkSettings
-from apex.builders import common_builder as c_builder
-from apex.builders import overcloud_builder as oc_builder
-from apex.builders import undercloud_builder as uc_builder
from apex.common import utils
from apex.common import constants
from apex.common import parsers
@@ -181,6 +181,10 @@ def create_deploy_parser():
default=False,
help='Force deployment to use upstream '
'artifacts')
+ deploy_parser.add_argument('--no-fetch', action='store_true',
+ default=False,
+ help='Ignore fetching latest upstream and '
+ 'use what is in cache')
return deploy_parser
@@ -352,15 +356,9 @@ def main():
constants.DEFAULT_OS_VERSION, os_version)
upstream_targets = ['overcloud-full.tar', 'undercloud.qcow2']
utils.fetch_upstream_and_unpack(args.image_dir, upstream_url,
- upstream_targets)
+ upstream_targets,
+ fetch=not args.no_fetch)
sdn_image = os.path.join(args.image_dir, 'overcloud-full.qcow2')
- if ds_opts['sdn_controller'] == 'opendaylight':
- logging.info("Preparing upstream image with OpenDaylight")
- oc_builder.inject_opendaylight(
- odl_version=ds_opts['odl_version'],
- image=sdn_image,
- tmp_dir=APEX_TEMP_DIR
- )
# copy undercloud so we don't taint upstream fetch
uc_image = os.path.join(args.image_dir, 'undercloud_mod.qcow2')
uc_fetch_img = os.path.join(args.image_dir, 'undercloud.qcow2')
@@ -372,12 +370,12 @@ def main():
patches = deploy_settings['global_params']['patches']
c_builder.add_upstream_patches(patches['undercloud'], uc_image,
APEX_TEMP_DIR, branch)
- logging.info('Adding patches to overcloud')
- c_builder.add_upstream_patches(patches['overcloud'], sdn_image,
- APEX_TEMP_DIR, branch)
else:
sdn_image = os.path.join(args.image_dir, SDN_IMAGE)
uc_image = 'undercloud.qcow2'
+ # patches are ignored in non-upstream deployments
+ patches = {'overcloud': [], 'undercloud': []}
+ # Create/Start Undercloud VM
undercloud = uc_lib.Undercloud(args.image_dir,
args.deploy_dir,
root_pw=root_pw,
@@ -385,6 +383,13 @@ def main():
image_name=os.path.basename(uc_image),
os_version=os_version)
undercloud.start()
+ undercloud_admin_ip = net_settings['networks'][
+ constants.ADMIN_NETWORK]['installer_vm']['ip']
+
+ if upstream and ds_opts['containers']:
+ tag = constants.DOCKER_TAG
+ else:
+ tag = None
# Generate nic templates
for role in 'compute', 'controller':
@@ -394,7 +399,7 @@ def main():
undercloud.configure(net_settings, deploy_settings,
os.path.join(args.lib_dir, ANSIBLE_PATH,
'configure_undercloud.yml'),
- APEX_TEMP_DIR)
+ APEX_TEMP_DIR, virtual_oc=args.virtual)
# Prepare overcloud-full.qcow2
logging.info("Preparing Overcloud for deployment...")
@@ -410,22 +415,57 @@ def main():
args.env_file = 'upstream-environment.yaml'
opnfv_env = os.path.join(args.deploy_dir, args.env_file)
if not upstream:
+ # TODO(trozet): Invoke with containers after Fraser migration
oc_deploy.prep_env(deploy_settings, net_settings, inventory,
opnfv_env, net_env_target, APEX_TEMP_DIR)
- oc_deploy.prep_image(deploy_settings, net_settings, sdn_image,
- APEX_TEMP_DIR, root_pw=root_pw)
else:
- shutil.copyfile(sdn_image, os.path.join(APEX_TEMP_DIR,
- 'overcloud-full.qcow2'))
shutil.copyfile(
opnfv_env,
os.path.join(APEX_TEMP_DIR, os.path.basename(opnfv_env))
)
+ patched_containers = oc_deploy.prep_image(
+ deploy_settings, net_settings, sdn_image, APEX_TEMP_DIR,
+ root_pw=root_pw, docker_tag=tag, patches=patches['overcloud'],
+ upstream=upstream)
oc_deploy.create_deploy_cmd(deploy_settings, net_settings, inventory,
APEX_TEMP_DIR, args.virtual,
os.path.basename(opnfv_env),
net_data=net_data)
+ # Prepare undercloud with containers
+ docker_playbook = os.path.join(args.lib_dir, ANSIBLE_PATH,
+ 'prepare_overcloud_containers.yml')
+ if ds_opts['containers']:
+ ceph_version = constants.CEPH_VERSION_MAP[ds_opts['os_version']]
+ ceph_docker_image = "ceph/daemon:tag-build-master-" \
+ "{}-centos-7".format(ceph_version)
+ logging.info("Preparing Undercloud with Docker containers")
+ if patched_containers:
+ oc_builder.archive_docker_patches(APEX_TEMP_DIR)
+ container_vars = dict()
+ container_vars['apex_temp_dir'] = APEX_TEMP_DIR
+ container_vars['patched_docker_services'] = list(
+ patched_containers)
+ container_vars['container_tag'] = constants.DOCKER_TAG
+ container_vars['stackrc'] = 'source /home/stack/stackrc'
+ container_vars['upstream'] = upstream
+ container_vars['sdn'] = ds_opts['sdn_controller']
+ container_vars['undercloud_ip'] = undercloud_admin_ip
+ container_vars['os_version'] = os_version
+ container_vars['ceph_docker_image'] = ceph_docker_image
+ container_vars['sdn_env_file'] = \
+ oc_deploy.get_docker_sdn_file(ds_opts)
+ try:
+ utils.run_ansible(container_vars, docker_playbook,
+ host=undercloud.ip, user='stack',
+ tmp_dir=APEX_TEMP_DIR)
+ logging.info("Container preparation complete")
+ except Exception:
+ logging.error("Unable to complete container prep on "
+ "Undercloud")
+ os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
+ raise
+
deploy_playbook = os.path.join(args.lib_dir, ANSIBLE_PATH,
'deploy_overcloud.yml')
virt_env = 'virtual-environment.yaml'
@@ -494,19 +534,14 @@ def main():
else:
deploy_vars['congress'] = False
deploy_vars['calipso'] = ds_opts.get('calipso', False)
- deploy_vars['calipso_ip'] = net_settings['networks']['admin'][
- 'installer_vm']['ip']
- # 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']
+ deploy_vars['calipso_ip'] = undercloud_admin_ip
+ # overcloudrc.v3 removed and set as default in queens and later
+ if os_version == 'pike':
+ deploy_vars['overcloudrc_files'] = ['overcloudrc',
+ 'overcloudrc.v3']
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
+ deploy_vars['overcloudrc_files'] = ['overcloudrc']
+
post_undercloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
'post_deploy_undercloud.yml')
logging.info("Executing post deploy configuration undercloud playbook")
diff --git a/apex/overcloud/deploy.py b/apex/overcloud/deploy.py
index 33641ed5..0701c188 100644
--- a/apex/overcloud/deploy.py
+++ b/apex/overcloud/deploy.py
@@ -16,10 +16,13 @@ import shutil
import uuid
import struct
import time
+import apex.builders.overcloud_builder as oc_builder
+import apex.builders.common_builder as c_builder
from apex.common import constants as con
from apex.common.exceptions import ApexDeployException
from apex.common import parsers
+from apex.common import utils
from apex.virtual import utils as virt_utils
from cryptography.hazmat.primitives import serialization as \
crypto_serialization
@@ -72,6 +75,21 @@ 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"
+LOSETUP_SERVICE = """[Unit]
+Description=Setup loop devices
+Before=network.target
+
+[Service]
+Type=oneshot
+ExecStart=/sbin/losetup /dev/loop3 /srv/data.img
+ExecStop=/sbin/losetup -d /dev/loop3
+TimeoutSec=60
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
+"""
+
def build_sdn_env_list(ds, sdn_map, env_list=None):
"""
@@ -118,6 +136,25 @@ def build_sdn_env_list(ds, sdn_map, env_list=None):
return env_list
+def get_docker_sdn_file(ds_opts):
+ """
+ Returns docker env file for detected SDN
+ :param ds_opts: deploy options
+ :return: docker THT env file for an SDN
+ """
+ # FIXME(trozet): We assume right now there is only one docker SDN file
+ docker_services = con.VALID_DOCKER_SERVICES
+ sdn_env_list = build_sdn_env_list(ds_opts, SDN_FILE_MAP)
+ for sdn_file in sdn_env_list:
+ sdn_base = os.path.basename(sdn_file)
+ if sdn_base in docker_services:
+ if docker_services[sdn_base] is not None:
+ return os.path.join(con.THT_DOCKER_ENV_DIR,
+ docker_services[sdn_base])
+ else:
+ return os.path.join(con.THT_DOCKER_ENV_DIR, sdn_base)
+
+
def create_deploy_cmd(ds, ns, inv, tmp_dir,
virtual, env_file='opnfv-environment.yaml',
net_data=False):
@@ -125,26 +162,47 @@ def create_deploy_cmd(ds, ns, inv, tmp_dir,
logging.info("Creating deployment command")
deploy_options = ['network-environment.yaml']
+ ds_opts = ds['deploy_options']
+
+ if ds_opts['containers']:
+ deploy_options.append(os.path.join(con.THT_ENV_DIR,
+ 'docker.yaml'))
+
+ if ds['global_params']['ha_enabled']:
+ if ds_opts['containers']:
+ deploy_options.append(os.path.join(con.THT_ENV_DIR,
+ 'docker-ha.yaml'))
+ else:
+ deploy_options.append(os.path.join(con.THT_ENV_DIR,
+ 'puppet-pacemaker.yaml'))
+
if env_file:
deploy_options.append(env_file)
- ds_opts = ds['deploy_options']
- deploy_options += build_sdn_env_list(ds_opts, SDN_FILE_MAP)
+
+ if ds_opts['containers']:
+ deploy_options.append('docker-images.yaml')
+ sdn_docker_file = get_docker_sdn_file(ds_opts)
+ if sdn_docker_file:
+ deploy_options.append(sdn_docker_file)
+ deploy_options.append('sdn-images.yaml')
+ else:
+ deploy_options += build_sdn_env_list(ds_opts, SDN_FILE_MAP)
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['containers']:
+ deploy_options.append(os.path.join(con.THT_DOCKER_ENV_DIR,
+ "{}.yaml".format(k)))
+ else:
+ deploy_options.append(os.path.join(con.THT_ENV_DIR, v))
if ds_opts['ceph']:
- prep_storage_env(ds, tmp_dir)
+ prep_storage_env(ds, ns, virtual, tmp_dir)
deploy_options.append(os.path.join(con.THT_ENV_DIR,
'storage-environment.yaml'))
if ds_opts['sriov']:
prep_sriov_env(ds, tmp_dir)
- 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:
@@ -190,7 +248,8 @@ def create_deploy_cmd(ds, ns, inv, tmp_dir,
return cmd
-def prep_image(ds, ns, img, tmp_dir, root_pw=None):
+def prep_image(ds, ns, img, tmp_dir, root_pw=None, docker_tag=None,
+ patches=None, upstream=False):
"""
Locates sdn image and preps for deployment.
:param ds: deploy settings
@@ -198,6 +257,9 @@ def prep_image(ds, ns, img, tmp_dir, root_pw=None):
:param img: sdn image
:param tmp_dir: dir to store modified sdn image
:param root_pw: password to configure for overcloud image
+ :param docker_tag: Docker image tag for RDO version (default None)
+ :param patches: List of patches to apply to overcloud image
+ :param upstream: (boolean) Indicates if upstream deployment or not
:return: None
"""
# TODO(trozet): Come up with a better way to organize this logic in this
@@ -210,6 +272,7 @@ def prep_image(ds, ns, img, tmp_dir, root_pw=None):
ds_opts = ds['deploy_options']
virt_cmds = list()
sdn = ds_opts['sdn_controller']
+ patched_containers = set()
# we need this due to rhbz #1436021
# fixed in systemd-219-37.el7
if sdn is not False:
@@ -298,7 +361,13 @@ def prep_image(ds, ns, img, tmp_dir, root_pw=None):
"/root/nosdn_vpp_rpms/*.rpm"}
])
- if sdn == 'opendaylight':
+ 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))
+
+ # TODO (trozet): remove this if block after Fraser
+ if sdn == 'opendaylight' and not upstream:
if ds_opts['odl_version'] != con.DEFAULT_ODL_VERSION:
virt_cmds.extend([
{con.VIRT_RUN_CMD: "yum -y remove opendaylight"},
@@ -325,6 +394,19 @@ def prep_image(ds, ns, img, tmp_dir, root_pw=None):
{con.VIRT_RUN_CMD: "yum -y install /root/{}/*".format(
ODL_NETVIRT_VPP_RPM)}
])
+ elif sdn == 'opendaylight':
+ undercloud_admin_ip = ns['networks'][con.ADMIN_NETWORK][
+ 'installer_vm']['ip']
+ oc_builder.inject_opendaylight(
+ odl_version=ds_opts['odl_version'],
+ image=tmp_oc_image,
+ tmp_dir=tmp_dir,
+ uc_ip=undercloud_admin_ip,
+ os_version=ds_opts['os_version'],
+ docker_tag=docker_tag,
+ )
+ if docker_tag:
+ patched_containers = patched_containers.union({'opendaylight'})
if sdn == 'ovn':
virt_cmds.extend([
@@ -334,12 +416,35 @@ def prep_image(ds, ns, img, tmp_dir, root_pw=None):
"*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))
+ if patches:
+ if ds_opts['os_version'] == 'master':
+ branch = ds_opts['os_version']
+ else:
+ branch = "stable/{}".format(ds_opts['os_version'])
+ logging.info('Adding patches to overcloud')
+ patched_containers = patched_containers.union(
+ c_builder.add_upstream_patches(patches,
+ tmp_oc_image, tmp_dir,
+ branch,
+ uc_ip=undercloud_admin_ip,
+ docker_tag=docker_tag))
+ # if containers with ceph, and no ceph device we need to use a
+ # persistent loop device for Ceph OSDs
+ if docker_tag and not ds_opts.get('ceph_device', None):
+ tmp_losetup = os.path.join(tmp_dir, 'losetup.service')
+ with open(tmp_losetup, 'w') as fh:
+ fh.write(LOSETUP_SERVICE)
+ virt_cmds.extend([
+ {con.VIRT_UPLOAD: "{}:/usr/lib/systemd/system/".format(tmp_losetup)
+ },
+ {con.VIRT_RUN_CMD: 'truncate /srv/data.img --size 10G'},
+ {con.VIRT_RUN_CMD: 'mkfs.ext4 -F /srv/data.img'},
+ {con.VIRT_RUN_CMD: 'systemctl daemon-reload'},
+ {con.VIRT_RUN_CMD: 'systemctl enable losetup.service'},
+ ])
virt_utils.virt_customize(virt_cmds, tmp_oc_image)
logging.info("Overcloud image customization complete")
+ return patched_containers
def make_ssh_key():
@@ -541,11 +646,13 @@ def generate_ceph_key():
return base64.b64encode(header + key)
-def prep_storage_env(ds, tmp_dir):
+def prep_storage_env(ds, ns, virtual, tmp_dir):
"""
Creates storage environment file for deployment. Source file is copied by
undercloud playbook to host.
:param ds:
+ :param ns:
+ :param virtual:
:param tmp_dir:
:return:
"""
@@ -572,7 +679,40 @@ def prep_storage_env(ds, tmp_dir):
'utf-8')))
else:
print(line)
- if 'ceph_device' in ds_opts and ds_opts['ceph_device']:
+
+ if ds_opts['containers']:
+ undercloud_admin_ip = ns['networks'][con.ADMIN_NETWORK][
+ 'installer_vm']['ip']
+ ceph_version = con.CEPH_VERSION_MAP[ds_opts['os_version']]
+ docker_image = "{}:8787/ceph/daemon:tag-build-master-" \
+ "{}-centos-7".format(undercloud_admin_ip,
+ ceph_version)
+ ceph_params = {
+ 'DockerCephDaemonImage': docker_image,
+ }
+ if not ds['global_params']['ha_enabled']:
+ ceph_params['CephPoolDefaultSize'] = 1
+
+ if virtual:
+ ceph_params['CephAnsibleExtraConfig'] = {
+ 'centos_package_dependencies': [],
+ 'ceph_osd_docker_memory_limit': '1g',
+ 'ceph_mds_docker_memory_limit': '1g',
+ }
+ ceph_params['CephPoolDefaultPgNum'] = 32
+ if 'ceph_device' in ds_opts and ds_opts['ceph_device']:
+ ceph_device = ds_opts['ceph_device']
+ else:
+ # TODO(trozet): make this DS default after Fraser
+ ceph_device = '/dev/loop3'
+
+ ceph_params['CephAnsibleDisksConfig'] = {
+ 'devices': [ceph_device],
+ 'journal_size': 512,
+ 'osd_scenario': 'collocated'
+ }
+ utils.edit_tht_env(storage_file, 'parameter_defaults', ceph_params)
+ elif 'ceph_device' in ds_opts and ds_opts['ceph_device']:
with open(storage_file, 'a') as fh:
fh.write(' ExtraConfig:\n')
fh.write(" ceph::profile::params::osds:{{{}:{{}}}}\n".format(
diff --git a/apex/settings/deploy_settings.py b/apex/settings/deploy_settings.py
index c05922bf..4f887ed0 100644
--- a/apex/settings/deploy_settings.py
+++ b/apex/settings/deploy_settings.py
@@ -25,7 +25,8 @@ REQ_DEPLOY_SETTINGS = ['sdn_controller',
'rt_kvm',
'os_version',
'l2gw',
- 'sriov']
+ 'sriov',
+ 'containers']
OPT_DEPLOY_SETTINGS = ['performance',
'vsperf',
diff --git a/apex/tests/config/98faaca.diff b/apex/tests/config/98faaca.diff
new file mode 100644
index 00000000..68a66fbc
--- /dev/null
+++ b/apex/tests/config/98faaca.diff
@@ -0,0 +1,331 @@
+From 98faacad44e39a456d9fe1a1d21f5a65e8de4fc1 Mon Sep 17 00:00:00 2001
+From: Janki Chhatbar <jchhatba@redhat.com>
+Date: Tue, 23 Jan 2018 22:43:49 +0530
+Subject: [PATCH] Minor update steps for ODL
+
+Updating OpenStack (within release) means updating ODL from v1 to v1.1.
+This is done by "openstack overcloud update" which collects
+update_tasks. ODL needs 2 different steps to achieve this
+minor update. These are called Level1 and Level2. L1 is
+simple - stop ODL, update, start. This is taken care by paunch
+and no separate implementation is needed. L2 has extra steps
+which are implemented in update_tasks and post_update_tasks.
+
+Updating ODL within the same major release (1->1.1) consists of either
+L1 or L2 steps. These steps are decided from ODLUpdateLevel parameter
+specified in environments/services-docker/update-odl.yaml.
+
+Upgrading ODL to the next major release (1.1->2) requires
+only the L2 steps. These are implemented as upgrade_tasks and
+post_upgrade_tasks in https://review.openstack.org/489201.
+
+Steps involved in level 2 update are
+ 1. Block OVS instances to connect to ODL
+ 2. Set ODL upgrade flag to True
+ 3. Start ODL
+ 4. Start Neutron re-sync and wait for it to finish
+ 5. Delete OVS groups and ports
+ 6. Stop OVS
+ 7. Unblock OVS ports
+ 8. Start OVS
+ 9. Unset ODL upgrade flag
+
+These steps are exactly same as upgrade_tasks.
+The logic implemented is:
+follow upgrade_tasks; when update_level == 2
+
+Change-Id: Ie532800663dd24313a7350b5583a5080ddb796e7
+---
+
+diff --git a/common/deploy-steps.j2 b/common/deploy-steps.j2
+index 595e16c..c4fb05f 100644
+--- a/common/deploy-steps.j2
++++ b/common/deploy-steps.j2
+@@ -23,6 +23,7 @@
+ {% set post_upgrade_steps_max = 4 -%}
+ {% set fast_forward_upgrade_steps_max = 9 -%}
+ {% set fast_forward_upgrade_prep_steps_max = 3 -%}
++{% set post_update_steps_max = 4 -%}
+
+ heat_template_version: queens
+
+@@ -590,3 +591,15 @@
+ - include_tasks: {{role.name}}/fast_forward_upgrade_tasks.yaml
+ when: role_name == '{{role.name}}' and ansible_hostname == {{role.name}}[0]
+ {%- endfor %}
++ post_update_steps_tasks: |
++{%- for role in roles %}
++ - include: {{role.name}}/post_update_tasks.yaml
++ when: role_name == '{{role.name}}'
++{%- endfor %}
++ post_update_steps_playbook: |
++ - hosts: overcloud
++ tasks:
++ - include: post_update_steps_tasks.yaml
++ with_sequence: start=0 end={{post_update_steps_max-1}}
++ loop_control:
++ loop_var: step
+diff --git a/common/services.yaml b/common/services.yaml
+index 2a62c1b..c197b05 100644
+--- a/common/services.yaml
++++ b/common/services.yaml
+@@ -283,6 +283,16 @@
+ expression: coalesce($.data, []).where($ != null).select($.get('update_tasks')).where($ != null).flatten().distinct()
+ data: {get_attr: [ServiceChain, role_data]}
+
++ PostUpdateTasks:
++ type: OS::Heat::Value
++ properties:
++ type: comma_delimited_list
++ value:
++ yaql:
++ # Note we use distinct() here to filter any identical tasks, e.g yum update for all services
++ expression: coalesce($.data, []).where($ != null).select($.get('post_update_tasks')).where($ != null).flatten().distinct()
++ data: {get_attr: [ServiceChain, role_data]}
++
+ UpgradeBatchTasks:
+ type: OS::Heat::Value
+ properties:
+@@ -349,6 +359,7 @@
+ upgrade_tasks: {get_attr: [UpgradeTasks, value]}
+ post_upgrade_tasks: {get_attr: [PostUpgradeTasks, value]}
+ update_tasks: {get_attr: [UpdateTasks, value]}
++ post_update_tasks: {get_attr: [PostUpdateTasks, value]}
+ upgrade_batch_tasks: {get_attr: [UpgradeBatchTasks, value]}
+ service_metadata_settings: {get_attr: [ServiceServerMetadataHook, metadata]}
+
+diff --git a/docker/services/opendaylight-api.yaml b/docker/services/opendaylight-api.yaml
+index 6175db9..3cafe53 100644
+--- a/docker/services/opendaylight-api.yaml
++++ b/docker/services/opendaylight-api.yaml
+@@ -44,6 +44,14 @@
+ type: string
+ description: Specifies the default CA cert to use if TLS is used for
+ services in the internal network.
++ ODLUpdateLevel:
++ default: 1
++ description: Specify the level of update
++ type: number
++ constraints:
++ - allowed_values:
++ - 1
++ - 2
+
+ conditions:
+
+@@ -167,23 +175,25 @@
+ - opendaylight_enabled.rc == 0
+ service: name=opendaylight state=stopped enabled=no
+ # Containarised deployment upgrade steps
+- - name: remove journal and snapshots
+- when: step|int == 0
+- file:
+- path: /var/lib/opendaylight/{{item}}
+- state: absent
+- with_items:
+- - snapshots
+- - journal
+- - name: Set ODL upgrade flag to True
+- copy:
+- dest: /var/lib/opendaylight/etc/opendaylight/datastore/initial/config/genius-mdsalutil-config.xml
+- content: |
+- <config xmlns="urn:opendaylight:params:xml:ns:yang:mdsalutil">
+- <upgradeInProgress>true</upgradeInProgress>
+- </config>
+- when: step|int == 1
+- post_upgrade_tasks:
++ - name: ODL container L2 update and upgrade tasks
++ block: &odl_container_upgrade_tasks
++ - name: remove journal and snapshots
++ when: step|int == 0
++ file:
++ path: /var/lib/opendaylight/{{item}}
++ state: absent
++ with_items:
++ - snapshots
++ - journal
++ - name: Set ODL upgrade flag to True
++ copy:
++ dest: /var/lib/opendaylight/etc/opendaylight/datastore/initial/config/genius-mdsalutil-config.xml
++ content: |
++ <config xmlns="urn:opendaylight:params:xml:ns:yang:mdsalutil">
++ <upgradeInProgress>true</upgradeInProgress>
++ </config>
++ when: step|int == 1
++ post_upgrade_tasks: &odl_container_post_upgrade_tasks
+ - name: Unset upgrade flag in ODL
+ shell:
+ str_replace:
+@@ -192,7 +202,20 @@
+ -H "Content-Type: application/json" \
+ $ODL_URI/restconf/config/genius-mdsalutil:config'
+ params:
+- $ODL_USERNAME: {get_param: [OpenDaylightBase, OpenDaylightUsername]}
+- $ODL_PASSWORD: {get_param: [OpenDaylightBase, OpenDaylightPassword]}
++ $ODL_USERNAME: {get_attr: [OpenDaylightBase, role_data, config_settings, 'opendaylight::username']}
++ $ODL_PASSWORD: {get_attr: [OpenDaylightBase, role_data, config_settings, 'opendaylight::password']}
+ $ODL_URI: {get_param: [EndpointMap, OpenDaylightInternal, uri]}
+ when: step|int == 0
++ update_tasks:
++ - name: Get ODL update level
++ block: &get_odl_update_level
++ - name: store update level to update_level variable
++ set_fact:
++ odl_update_level: {get_param: ODLUpdateLevel}
++ - name: Run L2 update tasks that are similar to upgrade_tasks when update level is 2
++ block: *odl_container_upgrade_tasks
++ when: odl_update_level == 2
++ post_update_tasks:
++ - block: *get_odl_update_level
++ - block: *odl_container_post_upgrade_tasks
++ when: odl_update_level == 2
+\ No newline at end of file
+diff --git a/environments/services-docker/update-odl.yaml b/environments/services-docker/update-odl.yaml
+new file mode 100644
+index 0000000..87d74ef
+--- /dev/null
++++ b/environments/services-docker/update-odl.yaml
+@@ -0,0 +1,11 @@
++# This file describes parameters needed for ODL update.
++# This file is to be used along with other env files during
++# level 2 minor update.
++# Level 2 update involves yang changes in ODL within same ODL release and
++# hence needs DB wipe and resync.
++# Level 1 is simple update - stop ODL, pull new image, start ODL
++# This file is not be used during level1 update or major upgrade.
++# In case doubt, please reach out to ODL developers on #tripleo IRC channel
++
++parameter_defaults:
++ ODLUpdateLevel: 2
+\ No newline at end of file
+diff --git a/puppet/services/opendaylight-ovs.yaml b/puppet/services/opendaylight-ovs.yaml
+index 3390645..958e1bb 100644
+--- a/puppet/services/opendaylight-ovs.yaml
++++ b/puppet/services/opendaylight-ovs.yaml
+@@ -104,6 +104,14 @@
+ type: string
+ description: Specifies the default CA cert to use if TLS is used for
+ services in the internal network.
++ ODLUpdateLevel:
++ default: 1
++ description: Specify the level of update
++ type: number
++ constraints:
++ - allowed_values:
++ - 1
++ - 2
+
+ parameter_groups:
+ - label: deprecated
+@@ -230,14 +238,16 @@
+ - openvswitch_enabled.rc == 0
+ service: name=openvswitch state=stopped
+ # Container upgrade steps.
+- - name: Block connections to ODL. #This rule will be inserted at the top.
+- iptables: chain=OUTPUT action=insert protocol=tcp destination_port={{ item }} jump=DROP
+- when: step|int == 0
+- with_items:
+- - 6640
+- - 6653
+- - 6633
+- post_upgrade_tasks:
++ - name: ODL container L2 update and upgrade tasks
++ block: &odl_container_upgrade_tasks
++ - name: Block connections to ODL. #This rule will be inserted at the top.
++ iptables: chain=OUTPUT action=insert protocol=tcp destination_port={{ item }} jump=DROP
++ when: step|int == 0
++ with_items:
++ - 6640
++ - 6653
++ - 6633
++ post_upgrade_tasks: &odl_container_post_upgrade_tasks
+ - name: Check service openvswitch is running
+ command: systemctl is-active --quiet openvswitch
+ tags: common
+@@ -260,6 +270,20 @@
+ - name: start openvswitch service
+ when: step|int == 3
+ service : name=openvswitch state=started
++ update_tasks:
++ - name: Get ODL update level
++ block: &get_odl_update_level
++ - name: store update level to update_level variable
++ set_fact:
++ odl_update_level: {get_param: ODLUpdateLevel}
++ - name: Run L2 update tasks that are similar to upgrade_tasks when update level is 2
++ block: *odl_container_upgrade_tasks
++ when: odl_update_level == 2
++ post_update_tasks:
++ - block: *get_odl_update_level
++ - block: *odl_container_post_upgrade_tasks
++ when: odl_update_level == 2
++
+ metadata_settings:
+ if:
+ - internal_tls_enabled
+@@ -267,4 +291,4 @@
+ - service: ovs
+ network: {get_param: [ServiceNetMap, OpendaylightApiNetwork]}
+ type: node
+- - null
++ - null
+\ No newline at end of file
+diff --git a/releasenotes/notes/odl_upgrade-f5540d242b9a6b52.yaml b/releasenotes/notes/odl_upgrade-f5540d242b9a6b52.yaml
+index 45703d0..e2943de 100644
+--- a/releasenotes/notes/odl_upgrade-f5540d242b9a6b52.yaml
++++ b/releasenotes/notes/odl_upgrade-f5540d242b9a6b52.yaml
+@@ -1,6 +1,6 @@
+ ---
+
+-features:
++upgrade:
+ - Add ODL upgradability
+ Steps of upgrade are as follows
+ 1. Block OVS instances to connect to ODL done in upgrade_tasks
+diff --git a/releasenotes/notes/update_odl-cb997ce5c136ebb7.yaml b/releasenotes/notes/update_odl-cb997ce5c136ebb7.yaml
+new file mode 100644
+index 0000000..1bcf8ed
+--- /dev/null
++++ b/releasenotes/notes/update_odl-cb997ce5c136ebb7.yaml
+@@ -0,0 +1,19 @@
++---
++features:
++ - Minor update ODL steps are added. ODL minor update (within same ODL
++ release) can have 2 different workflow. These are called level 1 and
++ level2. Level 1 is simple - stop, update and start ODL. Level 2 is
++ complex and involved yang model changes. This requires wiping of
++ DB and resync to repopulate the data.
++ Steps involved in level 2 update are
++ 1. Block OVS instances to connect to ODL
++ 2. Set ODL upgrade flag to True
++ 3. Start ODL
++ 4. Start Neutron re-sync and wait for it to finish
++ 5. Delete OVS groups and ports
++ 6. Stop OVS
++ 7. Unblock OVS ports
++ 8. Start OVS
++ 9. Unset ODL upgrade flag
++ To achieve L2 update, use "-e environments/services-docker/
++ update-odl.yaml" along with other env files to the update command.
+\ No newline at end of file
+diff --git a/tools/yaml-validate.py b/tools/yaml-validate.py
+index 59473f5..9ab6a87 100755
+--- a/tools/yaml-validate.py
++++ b/tools/yaml-validate.py
+@@ -46,11 +46,11 @@
+ OPTIONAL_DOCKER_SECTIONS = ['docker_puppet_tasks', 'upgrade_tasks',
+ 'fast_forward_upgrade_tasks',
+ 'post_upgrade_tasks', 'update_tasks',
+- 'service_config_settings', 'host_prep_tasks',
+- 'metadata_settings', 'kolla_config',
+- 'global_config_settings', 'logging_source',
+- 'logging_groups', 'external_deploy_tasks',
+- 'external_post_deploy_tasks',
++ 'post_update_tasks', 'service_config_settings',
++ 'host_prep_tasks', 'metadata_settings',
++ 'kolla_config', 'global_config_settings',
++ 'logging_source', 'logging_groups',
++ 'external_deploy_tasks', 'external_post_deploy_tasks',
+ 'docker_config_scripts', 'step_config']
+ REQUIRED_DOCKER_PUPPET_CONFIG_SECTIONS = ['config_volume', 'step_config',
+ 'config_image']
diff --git a/apex/tests/test_apex_build_utils.py b/apex/tests/test_apex_build_utils.py
index d9d542d6..f18103c8 100644
--- a/apex/tests/test_apex_build_utils.py
+++ b/apex/tests/test_apex_build_utils.py
@@ -9,17 +9,20 @@
import argparse
import git
+import os
+import unittest
from mock import patch
from apex import build_utils
+from apex.tests import constants as con
from nose.tools import (
assert_is_instance,
assert_raises)
-class TestBuildUtils(object):
+class TestBuildUtils(unittest.TestCase):
@classmethod
def setup_class(cls):
"""This method is run once for each class before any tests are run"""
@@ -165,3 +168,19 @@ class TestBuildUtils(object):
def test_main_debug(self, mock_get_parser):
with patch.object(build_utils.sys, 'argv', self.sys_argv_debug):
build_utils.main()
+
+ def test_strip_patch_sections(self):
+ with open(os.path.join(con.TEST_DUMMY_CONFIG, '98faaca.diff')) as fh:
+ dummy_patch = fh.read()
+ tmp_patch = build_utils.strip_patch_sections(dummy_patch)
+ self.assertNotRegex(tmp_patch, 'releasenotes')
+ self.assertNotRegex(tmp_patch, 'Minor update ODL steps')
+ self.assertNotRegex(tmp_patch, 'Steps of upgrade are as follows')
+ self.assertNotRegex(tmp_patch, 'Steps invlolved in level 2 update')
+
+ def test_strip_no_patch_sections(self):
+ with open(os.path.join(con.TEST_DUMMY_CONFIG, '98faaca.diff')) as fh:
+ dummy_patch = fh.read()
+ tmp_patch = build_utils.strip_patch_sections(dummy_patch,
+ sections=[])
+ self.assertEqual(dummy_patch, tmp_patch)
diff --git a/apex/tests/test_apex_common_builder.py b/apex/tests/test_apex_common_builder.py
index c32f72c9..d501746c 100644
--- a/apex/tests/test_apex_common_builder.py
+++ b/apex/tests/test_apex_common_builder.py
@@ -10,11 +10,20 @@
import unittest
from apex.builders import common_builder as c_builder
+from apex.builders import exceptions
from apex.common import constants as con
from mock import patch
from mock import mock_open
from mock import MagicMock
+DOCKER_YAML = {
+ 'resource_registry': {
+ 'OS::TripleO::Services::NovaApi': '../docker/services/nova-api.yaml',
+ 'OS::TripleO::Services::NovaConductor':
+ '../docker/services/nova-conductor.yaml'
+ }
+}
+
class TestCommonBuilder(unittest.TestCase):
@classmethod
@@ -68,6 +77,54 @@ class TestCommonBuilder(unittest.TestCase):
mock_customize.assert_called_once_with(test_virt_ops, 'dummy.qcow2')
@patch('builtins.open', mock_open())
+ @patch('apex.build_utils.get_patch')
+ @patch('apex.virtual.utils.virt_customize')
+ def test_add_upstream_patches_docker_puppet(
+ self, mock_customize, mock_get_patch):
+ change_id = 'I301370fbf47a71291614dd60e4c64adc7b5ebb42'
+ patches = [{
+ 'change-id': change_id,
+ 'project': 'openstack/puppet-tripleo'
+ }]
+ project_path = '/etc/puppet/modules/tripleo'
+ patch_file = "{}.patch".format(change_id)
+ patch_file_path = "/dummytmp/{}".format(patch_file)
+ test_virt_ops = [
+ {con.VIRT_INSTALL: 'patch'},
+ {con.VIRT_UPLOAD: "{}:{}".format(patch_file_path,
+ project_path)},
+ {con.VIRT_RUN_CMD: "cd {} && patch -p1 < {}".format(
+ project_path, patch_file)}]
+ mock_get_patch.return_value = 'some random diff'
+ c_builder.add_upstream_patches(patches, 'dummy.qcow2', '/dummytmp/',
+ uc_ip='192.0.2.1',
+ docker_tag='latest')
+ mock_customize.assert_called_once_with(test_virt_ops, 'dummy.qcow2')
+
+ @patch('builtins.open', mock_open())
+ @patch('apex.builders.common_builder.project_to_docker_image')
+ @patch('apex.builders.overcloud_builder.build_dockerfile')
+ @patch('apex.build_utils.get_patch')
+ @patch('apex.virtual.utils.virt_customize')
+ def test_add_upstream_patches_docker_python(
+ self, mock_customize, mock_get_patch, mock_build_docker_file,
+ mock_project2docker):
+ mock_project2docker.return_value = ['NovaApi']
+ change_id = 'I301370fbf47a71291614dd60e4c64adc7b5ebb42'
+ patches = [{
+ 'change-id': change_id,
+ 'project': 'openstack/nova'
+ }]
+ mock_get_patch.return_value = 'some random diff'
+ services = c_builder.add_upstream_patches(patches, 'dummy.qcow2',
+ '/dummytmp/',
+ uc_ip='192.0.2.1',
+ docker_tag='latest')
+ assert mock_customize.not_called
+ assert mock_build_docker_file.called
+ self.assertSetEqual(services, {'NovaApi'})
+
+ @patch('builtins.open', mock_open())
@patch('apex.virtual.utils.virt_customize')
def test_add_repo(self, mock_customize):
c_builder.add_repo('fake/url', 'dummyrepo', 'dummy.qcow2',
@@ -85,3 +142,15 @@ class TestCommonBuilder(unittest.TestCase):
self.assertEqual(c_builder.create_git_archive('fake/url', 'dummyrepo',
'/dummytmp/'),
'/dummytmp/dummyrepo.tar')
+
+ def test_project_to_docker_image(self):
+ found_services = c_builder.project_to_docker_image(project='nova')
+ assert 'nova-api' in found_services
+
+ @patch('apex.common.utils.open_webpage')
+ def test_project_to_docker_image_bad_web_content(
+ self, mock_open_web):
+ mock_open_web.return_value = b'{"blah": "blah"}'
+ self.assertRaises(exceptions.ApexCommonBuilderException,
+ c_builder.project_to_docker_image,
+ 'nova')
diff --git a/apex/tests/test_apex_common_utils.py b/apex/tests/test_apex_common_utils.py
index 6f2a9476..0e4041ca 100644
--- a/apex/tests/test_apex_common_utils.py
+++ b/apex/tests/test_apex_common_utils.py
@@ -12,12 +12,14 @@ import os
import shutil
import urllib.error
+from apex.common import exceptions
from apex.common import utils
from apex.settings.network_settings import NetworkSettings
from apex.tests.constants import (
TEST_CONFIG_DIR,
TEST_PLAYBOOK_DIR)
+from mock import patch, mock_open
from nose.tools import (
assert_equal,
assert_is_instance,
@@ -25,6 +27,7 @@ from nose.tools import (
assert_raises)
NET_SETS = os.path.join(TEST_CONFIG_DIR, 'network', 'network_settings.yaml')
+a_mock_open = mock_open(read_data=None)
class TestCommonUtils:
@@ -100,3 +103,48 @@ class TestCommonUtils:
url, ['dummy_test.tar'])
assert os.path.isfile('/tmp/fetch_test/test.txt')
shutil.rmtree('/tmp/fetch_test')
+
+ def test_nofetch_upstream_and_unpack(self):
+ test_file = 'overcloud-full.tar.md5'
+ url = 'https://images.rdoproject.org/master/delorean/' \
+ 'current-tripleo/stable/'
+ os.makedirs('/tmp/fetch_test', exist_ok=True)
+ target = "/tmp/fetch_test/{}".format(test_file)
+ open(target, 'w').close()
+ target_mtime = os.path.getmtime(target)
+ utils.fetch_upstream_and_unpack('/tmp/fetch_test',
+ url, [test_file], fetch=False)
+ post_target_mtime = os.path.getmtime(target)
+ shutil.rmtree('/tmp/fetch_test')
+ assert_equal(target_mtime, post_target_mtime)
+
+ def test_nofetch_upstream_and_unpack_no_target(self):
+ test_file = 'overcloud-full.tar.md5'
+ url = 'https://images.rdoproject.org/master/delorean/' \
+ 'current-tripleo/stable/'
+ utils.fetch_upstream_and_unpack('/tmp/fetch_test',
+ url, [test_file])
+ assert os.path.isfile("/tmp/fetch_test/{}".format(test_file))
+ shutil.rmtree('/tmp/fetch_test')
+
+ def test_open_webpage(self):
+ output = utils.open_webpage('http://opnfv.org')
+ assert output is not None
+
+ def test_open_invalid_webpage(self):
+ assert_raises(urllib.request.URLError, utils.open_webpage,
+ 'http://inv4lIdweb-page.com')
+
+ @patch('builtins.open', a_mock_open)
+ @patch('yaml.safe_dump')
+ @patch('yaml.safe_load')
+ def test_edit_tht_env(self, mock_yaml_load, mock_yaml_dump):
+ settings = {'SomeParameter': 'some_value'}
+ mock_yaml_load.return_value = {
+ 'parameter_defaults': {'SomeParameter': 'dummy'}
+ }
+ utils.edit_tht_env('/dummy-environment.yaml', 'parameter_defaults',
+ settings)
+ new_data = {'parameter_defaults': settings}
+ mock_yaml_dump.assert_called_once_with(new_data, a_mock_open(),
+ default_flow_style=False)
diff --git a/apex/tests/test_apex_deploy.py b/apex/tests/test_apex_deploy.py
index 403b7099..6c2a185d 100644
--- a/apex/tests/test_apex_deploy.py
+++ b/apex/tests/test_apex_deploy.py
@@ -143,7 +143,8 @@ class TestDeploy(unittest.TestCase):
'sfc': False,
'vpn': False,
'yardstick': 'test',
- 'os_version': DEFAULT_OS_VERSION}}
+ 'os_version': DEFAULT_OS_VERSION,
+ 'containers': False}}
args = mock_parser.return_value.parse_args.return_value
args.virtual = False
args.quickstart = False
@@ -216,7 +217,8 @@ class TestDeploy(unittest.TestCase):
'sfc': False,
'vpn': False,
'yardstick': 'test',
- 'os_version': DEFAULT_OS_VERSION}}
+ 'os_version': DEFAULT_OS_VERSION,
+ 'containers': False}}
args = mock_parser.return_value.parse_args.return_value
args.virtual = True
args.quickstart = False
@@ -236,3 +238,67 @@ class TestDeploy(unittest.TestCase):
args.virt_compute_ram = 16
args.virt_default_ram = 10
main()
+
+ @patch('apex.deploy.c_builder')
+ @patch('apex.deploy.uc_builder')
+ @patch('apex.deploy.oc_builder')
+ @patch('apex.deploy.network_data.create_network_data')
+ @patch('apex.deploy.shutil')
+ @patch('apex.deploy.oc_deploy')
+ @patch('apex.deploy.uc_lib')
+ @patch('apex.deploy.build_vms')
+ @patch('apex.deploy.Inventory')
+ @patch('apex.deploy.virt_utils')
+ @patch('apex.deploy.oc_cfg')
+ @patch('apex.deploy.parsers')
+ @patch('apex.deploy.utils')
+ @patch('apex.deploy.NetworkEnvironment')
+ @patch('apex.deploy.NetworkSettings')
+ @patch('apex.deploy.DeploySettings')
+ @patch('apex.deploy.os')
+ @patch('apex.deploy.json')
+ @patch('apex.deploy.jumphost')
+ @patch('apex.deploy.validate_cross_settings')
+ @patch('apex.deploy.validate_deploy_args')
+ @patch('apex.deploy.create_deploy_parser')
+ @patch('builtins.open', a_mock_open, create=True)
+ def test_main_virt_containers_upstream(
+ self, mock_parser, mock_val_args, mock_cross_sets, mock_jumphost,
+ mock_json, mock_os, mock_deploy_sets, mock_net_sets, mock_net_env,
+ mock_utils, mock_parsers, mock_oc_cfg, mock_virt_utils,
+ mock_inv, mock_build_vms, mock_uc_lib, mock_oc_deploy,
+ mock_shutil, mock_network_data, mock_oc_builder,
+ mock_uc_builder, mock_c_builder):
+
+ ds_opts_dict = {'global_params': MagicMock(),
+ 'deploy_options': {'gluon': False,
+ 'congress': False,
+ 'sdn_controller': 'opendaylight',
+ 'dataplane': 'ovs',
+ 'sfc': False,
+ 'vpn': False,
+ 'yardstick': 'test',
+ 'os_version': DEFAULT_OS_VERSION,
+ 'containers': True}}
+ args = mock_parser.return_value.parse_args.return_value
+ args.virtual = True
+ args.quickstart = False
+ args.debug = True
+ args.virt_default_ram = 10
+ args.ha_enabled = True
+ args.virt_compute_nodes = 1
+ args.virt_compute_ram = None
+ args.virt_default_ram = 12
+ args.upstream = True
+ net_sets = mock_net_sets.return_value
+ net_sets.enabled_network_list = ['admin']
+ deploy_sets = mock_deploy_sets.return_value
+ deploy_sets.__getitem__.side_effect = ds_opts_dict.__getitem__
+ deploy_sets.__contains__.side_effect = ds_opts_dict.__contains__
+ main()
+ args.virt_compute_ram = 16
+ args.virt_default_ram = 10
+ main()
+ mock_oc_deploy.prep_image.assert_called
+ # TODO(trozet) add assertions here with arguments for functions in
+ # deploy main
diff --git a/apex/tests/test_apex_overcloud_builder.py b/apex/tests/test_apex_overcloud_builder.py
index e9a6e6cf..46b5f871 100644
--- a/apex/tests/test_apex_overcloud_builder.py
+++ b/apex/tests/test_apex_overcloud_builder.py
@@ -11,7 +11,9 @@ import unittest
from apex.builders import overcloud_builder as oc_builder
from apex.common import constants as con
-from mock import patch
+from mock import patch, mock_open
+
+a_mock_open = mock_open(read_data=None)
class TestOvercloudBuilder(unittest.TestCase):
@@ -37,14 +39,69 @@ class TestOvercloudBuilder(unittest.TestCase):
mock_git_archive.return_value = '/dummytmp/puppet-opendaylight.tar'
archive = '/dummytmp/puppet-opendaylight.tar'
test_virt_ops = [
- {con.VIRT_INSTALL: 'opendaylight'},
{con.VIRT_UPLOAD: "{}:/etc/puppet/modules/".format(archive)},
{con.VIRT_RUN_CMD: 'rm -rf /etc/puppet/modules/opendaylight'},
{con.VIRT_RUN_CMD: "cd /etc/puppet/modules/ && tar xvf "
- "puppet-opendaylight.tar"}
+ "puppet-opendaylight.tar"},
+ {con.VIRT_INSTALL: 'opendaylight'}
]
oc_builder.inject_opendaylight(con.DEFAULT_ODL_VERSION, 'dummy.qcow2',
- '/dummytmp/')
+ '/dummytmp/', uc_ip='192.0.2.2',
+ os_version=con.DEFAULT_OS_VERSION)
+ assert mock_git_archive.called
+ assert mock_add_repo.called
+ mock_customize.assert_called_once_with(test_virt_ops, 'dummy.qcow2')
+
+ @patch('apex.builders.overcloud_builder.build_dockerfile')
+ @patch('apex.builders.common_builder.create_git_archive')
+ @patch('apex.builders.common_builder.add_repo')
+ @patch('apex.virtual.utils.virt_customize')
+ def test_inject_opendaylight_docker(self, mock_customize, mock_add_repo,
+ mock_git_archive, mock_build_docker):
+ mock_git_archive.return_value = '/dummytmp/puppet-opendaylight.tar'
+ archive = '/dummytmp/puppet-opendaylight.tar'
+ test_virt_ops = [
+ {con.VIRT_UPLOAD: "{}:/etc/puppet/modules/".format(archive)},
+ {con.VIRT_RUN_CMD: 'rm -rf /etc/puppet/modules/opendaylight'},
+ {con.VIRT_RUN_CMD: "cd /etc/puppet/modules/ && tar xvf "
+ "puppet-opendaylight.tar"},
+ ]
+ oc_builder.inject_opendaylight('oxygen', 'dummy.qcow2',
+ '/dummytmp/', uc_ip='192.0.2.2',
+ os_version=con.DEFAULT_OS_VERSION,
+ docker_tag='latest')
+ odl_url = "https://nexus.opendaylight.org/content/repositories" \
+ "/opendaylight-oxygen-epel-7-x86_64-devel/"
+ docker_cmds = [
+ "RUN yum remove opendaylight -y",
+ "RUN echo $'[opendaylight]\\n\\",
+ "baseurl={}\\n\\".format(odl_url),
+ "gpgcheck=0\\n\\",
+ "enabled=1' > /etc/yum.repos.d/opendaylight.repo",
+ "RUN yum -y install opendaylight"
+ ]
+ src_img_uri = "192.0.2.1:8787/nova-api/centos-binary-master:latest"
assert mock_git_archive.called
assert mock_add_repo.called
+ assert mock_build_docker.called_once_with(
+ 'opendaylight', '/dummytmp', docker_cmds, src_img_uri
+ )
mock_customize.assert_called_once_with(test_virt_ops, 'dummy.qcow2')
+
+ @patch('builtins.open', a_mock_open)
+ @patch('os.makedirs')
+ @patch('os.path.isfile')
+ @patch('os.path.isdir')
+ def test_build_dockerfile(self, mock_isdir, mock_isfile, mock_makedirs):
+ src_img_uri = "192.0.2.1:8787/nova-api/centos-binary-master:latest"
+ oc_builder.build_dockerfile('nova-api', '/tmpdummy/', ['RUN dummy'],
+ src_img_uri)
+ a_mock_open.assert_called_with(
+ '/tmpdummy/containers/nova-api/Dockerfile', 'a+')
+ a_mock_open().write.assert_called_once_with('RUN dummy')
+
+ @patch('tarfile.open')
+ @patch('os.path.isdir')
+ def test_archive_docker_patches(self, mock_isdir, mock_tarfile):
+ oc_builder.archive_docker_patches('/tmpdummy/')
+ assert mock_tarfile.assert_called
diff --git a/apex/tests/test_apex_overcloud_deploy.py b/apex/tests/test_apex_overcloud_deploy.py
index b5b1b75e..54f95bb7 100644
--- a/apex/tests/test_apex_overcloud_deploy.py
+++ b/apex/tests/test_apex_overcloud_deploy.py
@@ -29,6 +29,7 @@ from apex.overcloud.deploy import prep_sriov_env
from apex.overcloud.deploy import external_network_cmds
from apex.overcloud.deploy import create_congress_cmds
from apex.overcloud.deploy import SDN_FILE_MAP
+from apex.overcloud.deploy import get_docker_sdn_file
from nose.tools import (
assert_regexp_matches,
@@ -88,14 +89,17 @@ class TestOvercloudDeploy(unittest.TestCase):
def test_create_deploy_cmd(self, mock_sdn_list, mock_prep_storage,
mock_prep_sriov):
mock_sdn_list.return_value = []
- ds = {'deploy_options': MagicMock(),
+ ds = {'deploy_options':
+ {'ha_enabled': True,
+ 'congress': True,
+ 'tacker': True,
+ 'containers': False,
+ 'barometer': True,
+ 'ceph': False,
+ 'sriov': False
+ },
'global_params': MagicMock()}
- ds['global_params'].__getitem__.side_effect = \
- lambda i: True if i == 'ha_enabled' else MagicMock()
- ds['deploy_options'].__getitem__.side_effect = \
- lambda i: True if i == 'congress' else MagicMock()
- ds['deploy_options'].__contains__.side_effect = \
- lambda i: True if i == 'congress' else MagicMock()
+
ns = {'ntp': ['ntp']}
inv = MagicMock()
inv.get_node_counts.return_value = (3, 2)
@@ -111,6 +115,44 @@ class TestOvercloudDeploy(unittest.TestCase):
@patch('apex.overcloud.deploy.prep_sriov_env')
@patch('apex.overcloud.deploy.prep_storage_env')
+ @patch('builtins.open', mock_open())
+ def test_create_deploy_cmd_containers_sdn(self, mock_prep_storage,
+ mock_prep_sriov):
+ ds = {'deploy_options':
+ {'ha_enabled': True,
+ 'congress': False,
+ 'tacker': False,
+ 'containers': True,
+ 'barometer': False,
+ 'ceph': True,
+ 'sdn_controller': 'opendaylight',
+ 'sriov': False
+ },
+ 'global_params': MagicMock()}
+
+ ns = {'ntp': ['ntp']}
+ inv = MagicMock()
+ inv.get_node_counts.return_value = (3, 2)
+ virt = True
+ result_cmd = create_deploy_cmd(ds, ns, inv, '/tmp', virt)
+ assert_in('--ntp-server ntp', result_cmd)
+ assert_not_in('enable_tacker.yaml', result_cmd)
+ assert_not_in('enable_congress.yaml', result_cmd)
+ assert_not_in('enable_barometer.yaml', result_cmd)
+ assert_in('virtual-environment.yaml', result_cmd)
+ assert_in('--control-scale 3', result_cmd)
+ assert_in('--compute-scale 2', result_cmd)
+ assert_in('docker-images.yaml', result_cmd)
+ assert_in('sdn-images.yaml', result_cmd)
+ assert_in('/usr/share/openstack-tripleo-heat-templates/environments'
+ '/docker.yaml', result_cmd)
+ assert_in('/usr/share/openstack-tripleo-heat-templates/environments/'
+ 'storage-environment.yaml', result_cmd)
+ assert_in('/usr/share/openstack-tripleo-heat-templates/environments'
+ '/services-docker/neutron-opendaylight.yaml', result_cmd)
+
+ @patch('apex.overcloud.deploy.prep_sriov_env')
+ @patch('apex.overcloud.deploy.prep_storage_env')
@patch('apex.overcloud.deploy.build_sdn_env_list')
@patch('builtins.open', mock_open())
def test_create_deploy_cmd_no_ha_bm(self, mock_sdn_list,
@@ -155,7 +197,8 @@ class TestOvercloudDeploy(unittest.TestCase):
def test_prep_image(self, mock_os_path, mock_shutil, mock_virt_utils):
ds_opts = {'dataplane': 'fdio',
'sdn_controller': 'opendaylight',
- 'odl_version': 'master'}
+ 'odl_version': 'master',
+ 'sriov': False}
ds = {'deploy_options': MagicMock(),
'global_params': MagicMock()}
ds['deploy_options'].__getitem__.side_effect = \
@@ -200,6 +243,35 @@ class TestOvercloudDeploy(unittest.TestCase):
prep_image(ds, ns, 'undercloud.qcow2', '/tmp', root_pw='test')
mock_virt_utils.virt_customize.assert_called()
+ @patch('apex.overcloud.deploy.c_builder')
+ @patch('apex.overcloud.deploy.oc_builder')
+ @patch('apex.overcloud.deploy.virt_utils')
+ @patch('apex.overcloud.deploy.shutil')
+ @patch('apex.overcloud.deploy.os.path')
+ @patch('builtins.open', mock_open())
+ def test_prep_image_sdn_odl_upstream_containers_patches(
+ self, mock_os_path, mock_shutil, mock_virt_utils,
+ mock_oc_builder, mock_c_builder):
+ ds_opts = {'dataplane': 'ovs',
+ 'sdn_controller': 'opendaylight',
+ 'odl_version': con.DEFAULT_ODL_VERSION,
+ 'odl_vpp_netvirt': True}
+ ds = {'deploy_options': MagicMock(),
+ 'global_params': MagicMock()}
+ ds['deploy_options'].__getitem__.side_effect = \
+ lambda i: ds_opts.get(i, MagicMock())
+ ds['deploy_options'].__contains__.side_effect = \
+ lambda i: True if i in ds_opts else MagicMock()
+ ns = MagicMock()
+ mock_c_builder.add_upstream_patches.return_value = ['nova-api']
+ patches = ['dummy_nova_patch']
+ rv = prep_image(ds, ns, 'undercloud.qcow2', '/tmp', root_pw='test',
+ docker_tag='latest', patches=patches, upstream=True)
+ mock_oc_builder.inject_opendaylight.assert_called
+ mock_virt_utils.virt_customize.assert_called()
+ mock_c_builder.add_upstream_patches.assert_called
+ self.assertListEqual(sorted(rv), ['nova-api', 'opendaylight'])
+
@patch('apex.overcloud.deploy.virt_utils')
@patch('apex.overcloud.deploy.shutil')
@patch('apex.overcloud.deploy.os.path')
@@ -380,19 +452,60 @@ class TestOvercloudDeploy(unittest.TestCase):
mock_ceph_key):
mock_fileinput.input.return_value = \
['CephClusterFSID', 'CephMonKey', 'CephAdminKey', 'random_key']
- ds = {'deploy_options': MagicMock()}
- ds['deploy_options'].__getitem__.side_effect = \
- lambda i: '/dev/sdx' if i == 'ceph_device' else MagicMock()
- ds['deploy_options'].__contains__.side_effect = \
- lambda i: True if i == 'ceph_device' else MagicMock()
- prep_storage_env(ds, '/tmp')
+ ds = {'deploy_options': {
+ 'ceph_device': '/dev/sdx',
+ 'containers': False
+ }}
+ ns = {}
+ prep_storage_env(ds, ns, virtual=False, tmp_dir='/tmp')
+
+ @patch('apex.overcloud.deploy.utils.edit_tht_env')
+ @patch('apex.overcloud.deploy.generate_ceph_key')
+ @patch('apex.overcloud.deploy.fileinput')
+ @patch('apex.overcloud.deploy.os.path.isfile')
+ @patch('builtins.open', mock_open())
+ def test_prep_storage_env_containers(self, mock_isfile, mock_fileinput,
+ mock_ceph_key, mock_edit_tht):
+ mock_fileinput.input.return_value = \
+ ['CephClusterFSID', 'CephMonKey', 'CephAdminKey', 'random_key']
+ ds = {'deploy_options': {
+ 'ceph_device': '/dev/sdx',
+ 'containers': True,
+ 'os_version': 'master'
+ }, 'global_params': {'ha_enabled': False}}
+ ns = {'networks': {con.ADMIN_NETWORK: {'installer_vm':
+ {'ip': '192.0.2.1'}}}
+ }
+ prep_storage_env(ds, ns, virtual=True, tmp_dir='/tmp')
+ ceph_params = {
+ 'DockerCephDaemonImage':
+ '192.0.2.1:8787/ceph/daemon:tag-build-master-luminous-centos'
+ '-7',
+ 'CephPoolDefaultSize': 1,
+ 'CephAnsibleExtraConfig': {
+ 'centos_package_dependencies': [],
+ 'ceph_osd_docker_memory_limit': '1g',
+ 'ceph_mds_docker_memory_limit': '1g'
+ },
+ 'CephPoolDefaultPgNum': 32,
+ 'CephAnsibleDisksConfig': {
+ 'devices': ['/dev/sdx'],
+ 'journal_size': 512,
+ 'osd_scenario': 'collocated'
+ }
+ }
+ mock_edit_tht.assert_called_with('/tmp/storage-environment.yaml',
+ 'parameter_defaults',
+ ceph_params)
@patch('apex.overcloud.deploy.os.path.isfile')
@patch('builtins.open', mock_open())
def test_prep_storage_env_raises(self, mock_isfile):
mock_isfile.return_value = False
ds = {'deploy_options': MagicMock()}
- assert_raises(ApexDeployException, prep_storage_env, ds, '/tmp')
+ ns = {}
+ assert_raises(ApexDeployException, prep_storage_env, ds,
+ ns, virtual=False, tmp_dir='/tmp')
@patch('apex.overcloud.deploy.generate_ceph_key')
@patch('apex.overcloud.deploy.fileinput')
@@ -487,3 +600,19 @@ class TestOvercloudDeploy(unittest.TestCase):
def test_create_congress_cmds_raises(self, mock_parsers):
mock_parsers.return_value.__getitem__.side_effect = KeyError()
assert_raises(KeyError, create_congress_cmds, 'overcloud_file')
+
+ def test_get_docker_sdn_file(self):
+ ds_opts = {'ha_enabled': True,
+ 'congress': True,
+ 'tacker': True,
+ 'containers': False,
+ 'barometer': True,
+ 'ceph': False,
+ 'sdn_controller': 'opendaylight'
+ }
+ output = get_docker_sdn_file(ds_opts)
+ self.assertEqual(output,
+ ('/usr/share/openstack-tripleo-heat-templates'
+ '/environments/services-docker/neutron-opendaylight'
+ '.yaml')
+ )
diff --git a/apex/tests/test_apex_undercloud.py b/apex/tests/test_apex_undercloud.py
index 0df785f9..9e2752ad 100644
--- a/apex/tests/test_apex_undercloud.py
+++ b/apex/tests/test_apex_undercloud.py
@@ -7,6 +7,7 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
+import ipaddress
import libvirt
import os
import subprocess
@@ -187,13 +188,27 @@ class TestUndercloud(unittest.TestCase):
@patch.object(Undercloud, '_get_vm', return_value=None)
@patch.object(Undercloud, 'create')
def test_generate_config(self, mock_get_vm, mock_create):
- ns_net = MagicMock()
- ns_net.__getitem__.side_effect = \
- lambda i: '1234/24' if i is 'cidr' else MagicMock()
- ns = {'apex': MagicMock(),
- 'dns-domain': 'dns',
- 'networks': {'admin': ns_net,
- 'external': [ns_net]}}
+ ns = MagicMock()
+ ns.enabled_network_list = ['admin', 'external']
+ ns_dict = {
+ 'apex': MagicMock(),
+ 'dns-domain': 'dns',
+ 'networks': {'admin':
+ {'cidr': ipaddress.ip_network('192.0.2.0/24'),
+ 'installer_vm': {'ip': '192.0.2.1',
+ 'vlan': 'native'},
+ 'dhcp_range': ['192.0.2.15', '192.0.2.30']
+ },
+ 'external':
+ [{'enabled': True,
+ 'cidr': ipaddress.ip_network('192.168.0.0/24'),
+ 'installer_vm': {'ip': '192.168.0.1',
+ 'vlan': 'native'}
+ }]
+ }
+ }
+ ns.__getitem__.side_effect = ns_dict.__getitem__
+ ns.__contains__.side_effect = ns_dict.__contains__
ds = {'global_params': {}}
Undercloud('img_path', 'tplt_path').generate_config(ns, ds)
diff --git a/apex/undercloud/undercloud.py b/apex/undercloud/undercloud.py
index 915c85f3..e799d371 100644
--- a/apex/undercloud/undercloud.py
+++ b/apex/undercloud/undercloud.py
@@ -115,13 +115,14 @@ class Undercloud:
"correctly")
def configure(self, net_settings, deploy_settings,
- playbook, apex_temp_dir):
+ playbook, apex_temp_dir, virtual_oc=False):
"""
Configures undercloud VM
:param net_settings: Network settings for deployment
:param deploy_settings: Deployment settings for deployment
:param playbook: playbook to use to configure undercloud
:param apex_temp_dir: temporary apex directory to hold configs/logs
+ :param virtual_oc: Boolean to determine if overcloud is virt
:return: None
"""
@@ -130,6 +131,7 @@ class Undercloud:
ansible_vars = Undercloud.generate_config(net_settings,
deploy_settings)
ansible_vars['apex_temp_dir'] = apex_temp_dir
+ ansible_vars['virtual_overcloud'] = virtual_oc
try:
utils.run_ansible(ansible_vars, playbook, host=self.ip,
user='stack')
@@ -239,7 +241,16 @@ class Undercloud:
"prefix": str(ns_external['cidr']).split('/')[1],
"enabled": ns_external['enabled']
}
-
+ # TODO(trozet): clean this logic up and merge with above
+ if 'external' in ns.enabled_network_list:
+ nat_cidr = ns_external['cidr']
+ else:
+ nat_cidr = ns['networks']['admin']['cidr']
+ config['nat_cidr'] = str(nat_cidr)
+ if nat_cidr.version == 6:
+ config['nat_network_ipv6'] = True
+ else:
+ config['nat_network_ipv6'] = False
config['http_proxy'] = ns.get('http_proxy', '')
config['https_proxy'] = ns.get('https_proxy', '')