Example #1
0
 def test_prep_env_round_two(self, mock_shutil, mock_fileinput):
     mock_fileinput.input.return_value = \
         ['NeutronVPPAgentPhysnets']
     ds = {
         'deploy_options': {
             'sdn_controller': False,
             'dataplane': 'fdio',
             'sriov': 'xxx',
             'performance': {
                 'Compute': {},
                 'Controller': {}
             }
         }
     }
     ns = {
         'domain_name': 'test.domain',
         'networks': {
             'tenant': {
                 'nic_mapping': {
                     'controller': {
                         'members': ['tenant_nic']
                     },
                     'compute': {
                         'members': ['tenant_nic']
                     }
                 }
             },
             'external': [{
                 'nic_mapping': {
                     'controller': {
                         'members': ['ext_nic']
                     },
                     'compute': {
                         'members': ['ext_nic']
                     }
                 }
             }]
         }
     }
     inv = None
     try:
         # Swap stdout
         saved_stdout = sys.stdout
         out = StringIO()
         sys.stdout = out
         # run test
         prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
         output = out.getvalue().strip()
         assert_in(
             'NeutronVPPAgentPhysnets: '
             '\'datacentre:tenant_nic,external:tap0\'', output)
         assert_in('NeutronVPPAgentPhysnets', output)
     finally:
         # put stdout back
         sys.stdout = saved_stdout
Example #2
0
 def test_prep_env_round_three(self, mock_shutil, mock_fileinput):
     mock_fileinput.input.return_value = \
         ['OS::TripleO::Services::NeutronDhcpAgent',
          'NeutronDhcpAgentsPerNetwork', 'ComputeServices']
     ds = {
         'deploy_options': {
             'sdn_controller': 'opendaylight',
             'dataplane': 'fdio',
             'sriov': 'xxx',
             'dvr': True
         }
     }
     ns = {
         'domain_name': 'test.domain',
         'networks': {
             'tenant': {
                 'nic_mapping': {
                     'controller': {
                         'members': ['tenant_nic']
                     },
                     'compute': {
                         'members': ['tenant_nic']
                     }
                 }
             },
             'external': [{
                 'nic_mapping': {
                     'controller': {
                         'members': ['ext_nic']
                     },
                     'compute': {
                         'members': ['ext_nic']
                     }
                 }
             }]
         }
     }
     inv = MagicMock()
     inv.get_node_counts.return_value = (3, 2)
     try:
         # Swap stdout
         saved_stdout = sys.stdout
         out = StringIO()
         sys.stdout = out
         # run test
         prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
         output = out.getvalue().strip()
         assert_in('NeutronDhcpAgentsPerNetwork: 2', output)
     finally:
         # put stdout back
         sys.stdout = saved_stdout
Example #3
0
 def test_prep_env_tenant_vlan_odl(self, mock_shutil, mock_fileinput):
     mock_fileinput.input.return_value = \
         ['NeutronNetworkVLANRanges',
          'NeutronNetworkType',
          'NeutronBridgeMappings',
          'OpenDaylightProviderMappings']
     ds = {'global_params': {'ha_enabled': False},
           'deploy_options':
           {'sdn_controller': 'opendaylight',
            'dataplane': 'ovs',
            'sriov': 'xxx',
            'dvr': True}}
     ns_dict = {'domain_name': 'test.domain',
                'networks':
                {'tenant':
                 {'nic_mapping': {'controller':
                                  {'members': ['tenant_nic']},
                                  'compute':
                                  {'members': ['tenant_nic']}},
                  'segmentation_type': 'vlan',
                  'overlay_id_range': 'vlan:500:600'
                  },
                 'external':
                 [{'nic_mapping': {'controller':
                                   {'members': ['ext_nic']},
                                   'compute':
                                   {'members': ['ext_nic']}}}]}}
     inv = MagicMock()
     inv.get_node_counts.return_value = (3, 2)
     try:
         # Swap stdout
         saved_stdout = sys.stdout
         out = StringIO()
         sys.stdout = out
         ns = MagicMock()
         ns.enabled_network_list = ['external', 'tenant']
         ns.__getitem__.side_effect = lambda i: ns_dict.get(i, MagicMock())
         # run test
         prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
         output = out.getvalue().strip()
         assert_in('NeutronNetworkVLANRanges: '
                   'vlan:500:600,datacentre:1:1000', output)
         assert_in('NeutronNetworkType: vlan', output)
         assert_in('NeutronBridgeMappings: '
                   'vlan:br-vlan,datacentre:br-ex', output)
         assert_in('OpenDaylightProviderMappings: '
                   'vlan:br-vlan,datacentre:br-ex', output)
     finally:
         # put stdout back
         sys.stdout = saved_stdout
Example #4
0
 def test_prep_env_round_two(self, mock_shutil, mock_fileinput):
     mock_fileinput.input.return_value = \
         ['NeutronVPPAgentPhysnets']
     ds = {'global_params': {'ha_enabled': False},
           'deploy_options':
           {'sdn_controller': False,
            'dataplane': 'fdio',
            'sriov': 'xxx',
            'performance': {'Compute': {},
                            'Controller': {}}}}
     ns_dict = {'domain_name': 'test.domain',
                'networks':
                {'tenant':
                 {'nic_mapping': {'controller':
                                  {'members': ['tenant_nic']},
                                  'compute':
                                  {'members': ['tenant_nic']}}},
                 'external':
                 [{'nic_mapping': {'controller':
                                   {'members': ['ext_nic']},
                                   'compute':
                                   {'members': ['ext_nic']}}}]}}
     inv = MagicMock()
     inv.get_node_counts.return_value = (3, 2)
     try:
         # Swap stdout
         saved_stdout = sys.stdout
         out = StringIO()
         sys.stdout = out
         ns = MagicMock()
         ns.enabled_network_list = ['external', 'tenant']
         ns.__getitem__.side_effect = lambda i: ns_dict.get(i, MagicMock())
         # run test
         prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
         output = out.getvalue().strip()
         assert_in('NeutronVPPAgentPhysnets: '
                   '\'datacentre:tenant_nic,external:tap0\'',
                   output)
         assert_in('NeutronVPPAgentPhysnets', output)
     finally:
         # put stdout back
         sys.stdout = saved_stdout
Example #5
0
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)
    utils.install_ansible()
    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)))

    if not args.snapshot:
        net_settings = NetworkSettings(args.network_settings_file)
        logging.info("Network settings are:\n {}".format(
            pprint.pformat(net_settings)))
        os_version = deploy_settings['deploy_options']['os_version']
        net_env_file = os.path.join(args.deploy_dir, constants.NET_ENV_FILE)
        net_env = NetworkEnvironment(net_settings,
                                     net_env_file,
                                     os_version=os_version)
        net_env_target = os.path.join(APEX_TEMP_DIR, constants.NET_ENV_FILE)
        utils.dump_yaml(dict(net_env), net_env_target)

        # get global deploy params
        ha_enabled = deploy_settings['global_params']['ha_enabled']
        introspect = deploy_settings['global_params'].get('introspect', True)
        net_list = net_settings.enabled_network_list
        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:
                if platform.machine() == 'aarch64':
                    control_ram = 16
                    logging.warning('RAM per controller is too low for '
                                    'aarch64 ')
                    logging.info('Increasing RAM per controller to 16GB')
                else:
                    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 platform.machine() == 'aarch64' and args.virt_cpus < 16:
                vcpus = 16
                logging.warning('aarch64 requires at least 16 vCPUS per '
                                'target VM. Increasing to 16.')
            else:
                vcpus = args.virt_cpus
            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=vcpus)
        inventory = Inventory(args.inventory_file, ha_enabled, args.virtual)
        logging.info("Inventory is:\n {}".format(pprint.pformat(inventory)))

        validate_cross_settings(deploy_settings, net_settings, inventory)
    else:
        # only one network with snapshots
        net_list = [constants.ADMIN_NETWORK]

    ds_opts = deploy_settings['deploy_options']
    ansible_args = {
        'virsh_enabled_networks': net_list,
        'snapshot': args.snapshot
    }
    utils.run_ansible(
        ansible_args,
        os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
                     'deploy_dependencies.yml'))
    all_in_one = not bool(args.virt_compute_nodes)
    if args.snapshot:
        # Start snapshot Deployment
        logging.info('Executing Snapshot Deployment...')
        SnapshotDeployment(deploy_settings=deploy_settings,
                           snap_cache_dir=args.snap_cache,
                           fetch=not args.no_fetch,
                           all_in_one=all_in_one)
    else:
        # Start Standard TripleO Deployment
        deployment = ApexDeployment(deploy_settings, args.patches_file,
                                    args.deploy_settings_file)
        # TODO (trozet): add logic back from:
        # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR)
        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, args.deploy_dir)
        else:
            # Attach interfaces to jumphost for baremetal deployment
            jump_networks = ['admin']
            if uc_external:
                jump_networks.append('external')
            for network in jump_networks:
                if network == 'external':
                    # TODO(trozet): enable vlan secondary external networks
                    iface = net_settings['networks'][network][0][
                        'installer_vm']['members'][0]
                else:
                    iface = net_settings['networks'][network]['installer_vm'][
                        'members'][0]
                bridge = "br-{}".format(network)
                jumphost.attach_interface_to_ovs(bridge, iface, network)
        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

        if not args.upstream:
            logging.warning("Using upstream is now required for Apex. "
                            "Forcing upstream to true")
        if os_version == 'master':
            branch = 'master'
        else:
            branch = "stable/{}".format(os_version)

        logging.info("Deploying with upstream artifacts for OpenStack "
                     "{}".format(os_version))
        args.image_dir = os.path.join(args.image_dir, os_version)
        upstream_url = constants.UPSTREAM_RDO.replace(
            constants.DEFAULT_OS_VERSION, os_version)

        upstream_targets = ['overcloud-full.tar', 'ironic-python-agent.tar']
        if platform.machine() == 'aarch64':
            upstream_targets.append('undercloud.qcow2')
        utils.fetch_upstream_and_unpack(args.image_dir,
                                        upstream_url,
                                        upstream_targets,
                                        fetch=not args.no_fetch)
        # Copy ironic files and overcloud ramdisk and kernel into temp dir
        # to be copied by ansible into undercloud /home/stack
        # Note the overcloud disk does not need to be copied here as it will
        # be modified and copied later
        for tmp_file in UC_DISK_FILES:
            shutil.copyfile(os.path.join(args.image_dir, tmp_file),
                            os.path.join(APEX_TEMP_DIR, tmp_file))
        if platform.machine() == 'aarch64':
            sdn_image = os.path.join(args.image_dir, 'undercloud.qcow2')
        else:
            sdn_image = os.path.join(args.image_dir, 'overcloud-full.qcow2')
        # copy undercloud so we don't taint upstream fetch
        uc_image = os.path.join(args.image_dir, 'undercloud_mod.qcow2')
        uc_fetch_img = sdn_image
        shutil.copyfile(uc_fetch_img, uc_image)
        # prep undercloud with required packages
        if platform.machine() != 'aarch64':
            uc_builder.update_repos(image=uc_image,
                                    branch=branch.replace('stable/', ''))
        uc_builder.add_upstream_packages(uc_image)
        uc_builder.inject_calipso_installer(APEX_TEMP_DIR, uc_image)
        # add patches from upstream to undercloud and overcloud
        logging.info('Adding patches to undercloud')
        patches = deployment.determine_patches()
        c_builder.add_upstream_patches(patches['undercloud'], uc_image,
                                       APEX_TEMP_DIR, branch)

        # Create/Start Undercloud VM
        undercloud = uc_lib.Undercloud(args.image_dir,
                                       args.deploy_dir,
                                       root_pw=root_pw,
                                       external_network=uc_external,
                                       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 ds_opts['containers']:
            tag = constants.DOCKER_TAG
        else:
            tag = None

        # Generate nic templates
        for role in 'compute', 'controller':
            oc_cfg.create_nic_template(net_settings, deploy_settings, role,
                                       args.deploy_dir, APEX_TEMP_DIR)
        # Prepare/Upload docker images
        docker_env = 'containers-prepare-parameter.yaml'
        shutil.copyfile(os.path.join(args.deploy_dir, docker_env),
                        os.path.join(APEX_TEMP_DIR, docker_env))
        # Upload extra ansible.cfg
        if platform.machine() == 'aarch64':
            ansible_env = 'ansible.cfg'
            shutil.copyfile(os.path.join(args.deploy_dir, ansible_env),
                            os.path.join(APEX_TEMP_DIR, ansible_env))

        c_builder.prepare_container_images(
            os.path.join(APEX_TEMP_DIR, docker_env),
            branch=branch.replace('stable/', ''),
            neutron_driver=c_builder.get_neutron_driver(ds_opts))
        # Install Undercloud
        undercloud.configure(net_settings,
                             deploy_settings,
                             os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
                                          'configure_undercloud.yml'),
                             APEX_TEMP_DIR,
                             virtual_oc=args.virtual)

        # Prepare overcloud-full.qcow2
        logging.info("Preparing Overcloud for deployment...")
        if os_version != 'ocata':
            net_data_file = os.path.join(APEX_TEMP_DIR, 'network_data.yaml')
            net_data = network_data.create_network_data(
                net_settings, net_data_file)
        else:
            net_data = False

        shutil.copyfile(os.path.join(args.deploy_dir, 'build_ovs_nsh.sh'),
                        os.path.join(APEX_TEMP_DIR, 'build_ovs_nsh.sh'))

        # TODO(trozet): Either fix opnfv env or default to use upstream env
        if args.env_file == 'opnfv-environment.yaml':
            # Override the env_file if it is defaulted to opnfv
            # opnfv env file will not work with upstream
            args.env_file = 'upstream-environment.yaml'
        opnfv_env = os.path.join(args.deploy_dir, args.env_file)
        oc_deploy.prep_env(deploy_settings, net_settings, inventory, opnfv_env,
                           net_env_target, APEX_TEMP_DIR)
        if not args.virtual:
            oc_deploy.LOOP_DEVICE_SIZE = "50G"
        if platform.machine() == 'aarch64':
            oc_image = os.path.join(args.image_dir, 'overcloud-full.qcow2')
        else:
            oc_image = sdn_image
        patched_containers = oc_deploy.prep_image(deploy_settings,
                                                  net_settings,
                                                  oc_image,
                                                  APEX_TEMP_DIR,
                                                  root_pw=root_pw,
                                                  docker_tag=tag,
                                                  patches=patches['overcloud'])

        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, constants.ANSIBLE_PATH,
                                       'prepare_overcloud_containers.yml')
        if ds_opts['containers']:
            logging.info("Preparing Undercloud with Docker containers")
            sdn_env = oc_deploy.get_docker_sdn_files(ds_opts)
            sdn_env_files = str()
            for sdn_file in sdn_env:
                sdn_env_files += " -e {}".format(sdn_file)
            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['sdn'] = ds_opts['sdn_controller']
            container_vars['undercloud_ip'] = undercloud_admin_ip
            container_vars['os_version'] = os_version
            container_vars['aarch64'] = platform.machine() == 'aarch64'
            container_vars['sdn_env_file'] = sdn_env_files
            try:
                utils.run_ansible(container_vars,
                                  docker_playbook,
                                  host=undercloud.ip,
                                  user='******',
                                  tmp_dir=APEX_TEMP_DIR)
                logging.info("Container preparation complete")
            except Exception:
                logging.error("Unable to complete container prep on "
                              "Undercloud")
                for tmp_file in UC_DISK_FILES:
                    os.remove(os.path.join(APEX_TEMP_DIR, tmp_file))
                os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
                raise

        deploy_playbook = os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
                                       'deploy_overcloud.yml')
        virt_env = 'virtual-environment.yaml'
        bm_env = 'baremetal-environment.yaml'
        k8s_env = 'kubernetes-environment.yaml'
        for p_env in virt_env, bm_env, k8s_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['aarch64'] = platform.machine() == 'aarch64'
        deploy_vars['introspect'] = not (args.virtual or deploy_vars['aarch64']
                                         or not introspect)
        deploy_vars['dns_server_args'] = ''
        deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
        deploy_vars['apex_env_file'] = os.path.basename(opnfv_env)
        deploy_vars['stackrc'] = 'source /home/stack/stackrc'
        deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
        deploy_vars['undercloud_ip'] = undercloud_admin_ip
        deploy_vars['ha_enabled'] = ha_enabled
        deploy_vars['os_version'] = os_version
        deploy_vars['http_proxy'] = net_settings.get('http_proxy', '')
        deploy_vars['https_proxy'] = net_settings.get('https_proxy', '')
        deploy_vars['vim'] = ds_opts['vim']
        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='******',
                              tmp_dir=APEX_TEMP_DIR)
            logging.info("Overcloud deployment complete")
        except Exception:
            logging.error("Deployment Failed.  Please check deploy log as "
                          "well as mistral logs in "
                          "{}".format(
                              os.path.join(APEX_TEMP_DIR,
                                           'mistral_logs.tar.gz')))
            raise
        finally:
            os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
            for tmp_file in UC_DISK_FILES:
                os.remove(os.path.join(APEX_TEMP_DIR, tmp_file))

        # 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'] = \
            oc_deploy.external_network_cmds(net_settings, deploy_settings)
        # TODO(trozet): just parse all ds_opts as deploy vars one time
        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'] = \
                oc_deploy.create_congress_cmds(overcloudrc)
            deploy_vars['congress'] = True
        else:
            deploy_vars['congress'] = False
        deploy_vars['calipso'] = ds_opts.get('calipso', False)
        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:
            deploy_vars['overcloudrc_files'] = ['overcloudrc']

        post_undercloud = os.path.join(args.lib_dir, constants.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='******',
                              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

        # Deploy kubernetes if enabled
        # (TODO)zshi move handling of kubernetes deployment
        # to its own deployment class
        if deploy_vars['vim'] == 'k8s':
            # clone kubespray repo
            git.Repo.clone_from(constants.KUBESPRAY_URL,
                                os.path.join(APEX_TEMP_DIR, 'kubespray'))
            shutil.copytree(
                os.path.join(APEX_TEMP_DIR, 'kubespray', 'inventory',
                             'sample'),
                os.path.join(APEX_TEMP_DIR, 'kubespray', 'inventory', 'apex'))
            k8s_node_inventory = {
                'all': {
                    'hosts': {},
                    'children': {
                        'k8s-cluster': {
                            'children': {
                                'kube-master': {
                                    'hosts': {}
                                },
                                'kube-node': {
                                    'hosts': {}
                                }
                            }
                        },
                        'etcd': {
                            'hosts': {}
                        }
                    }
                }
            }
            for node, ip in deploy_vars['overcloud_nodes'].items():
                k8s_node_inventory['all']['hosts'][node] = {
                    'ansible_become': True,
                    'ansible_ssh_host': ip,
                    'ansible_become_user': '******',
                    'ip': ip
                }
                if 'controller' in node:
                    k8s_node_inventory['all']['children']['k8s-cluster'][
                        'children']['kube-master']['hosts'][node] = None
                    k8s_node_inventory['all']['children']['etcd']['hosts'][
                        node] = None
                elif 'compute' in node:
                    k8s_node_inventory['all']['children']['k8s-cluster'][
                        'children']['kube-node']['hosts'][node] = None

            kubespray_dir = os.path.join(APEX_TEMP_DIR, 'kubespray')
            with open(
                    os.path.join(kubespray_dir, 'inventory', 'apex',
                                 'apex.yaml'), 'w') as invfile:
                yaml.dump(k8s_node_inventory,
                          invfile,
                          default_flow_style=False)
            k8s_deploy_vars = {}
            # Add kubespray ansible control variables in k8s_deploy_vars,
            # example: 'kube_network_plugin': 'flannel'
            k8s_deploy = os.path.join(kubespray_dir, 'cluster.yml')
            k8s_deploy_inv_file = os.path.join(kubespray_dir, 'inventory',
                                               'apex', 'apex.yaml')

            k8s_remove_pkgs = os.path.join(args.lib_dir,
                                           constants.ANSIBLE_PATH,
                                           'k8s_remove_pkgs.yml')
            try:
                logging.debug("Removing any existing overcloud docker "
                              "packages")
                utils.run_ansible(k8s_deploy_vars,
                                  k8s_remove_pkgs,
                                  host=k8s_deploy_inv_file,
                                  user='******',
                                  tmp_dir=APEX_TEMP_DIR)
                logging.info("k8s Deploy Remove Existing Docker Related "
                             "Packages Complete")
            except Exception:
                logging.error("k8s Deploy Remove Existing Docker Related "
                              "Packages failed. Please check log")
                raise

            try:
                utils.run_ansible(k8s_deploy_vars,
                                  k8s_deploy,
                                  host=k8s_deploy_inv_file,
                                  user='******',
                                  tmp_dir=APEX_TEMP_DIR)
                logging.info("k8s Deploy Overcloud Configuration Complete")
            except Exception:
                logging.error("k8s Deploy Overcloud 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']
        deploy_vars['l2gw'] = ds_opts.get('l2gw')
        deploy_vars['sriov'] = ds_opts.get('sriov')
        deploy_vars['tacker'] = ds_opts.get('tacker')
        deploy_vars['all_in_one'] = all_in_one
        # TODO(trozet): pull all logs and store in tmp dir in overcloud
        # playbook
        post_overcloud = os.path.join(args.lib_dir, constants.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='******',
                                  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))
Example #6
0
 def test_prep_env(self, mock_shutil, mock_fileinput):
     mock_fileinput.input.return_value = \
         ['CloudDomain', 'replace_private_key', 'replace_public_key',
          'opendaylight::vpp_routing_node', 'ControllerExtraConfig',
          'NovaComputeExtraConfig', 'ComputeKernelArgs', 'HostCpusList',
          'ComputeExtraConfigPre', 'resource_registry',
          'NovaSchedulerDefaultFilters']
     ds = {
         'deploy_options': {
             'sdn_controller': 'opendaylight',
             'odl_vpp_routing_node': 'test',
             'dataplane': 'ovs_dpdk',
             'sriov': 'xxx',
             'performance': {
                 'Compute': {
                     'vpp': {
                         'main-core': 'test',
                         'corelist-workers': 'test'
                     },
                     'ovs': {
                         'dpdk_cores': 'test'
                     },
                     'kernel': {
                         'test': 'test'
                     }
                 },
                 'Controller': {
                     'vpp': 'test'
                 }
             }
         }
     }
     ns = {
         'domain_name': 'test.domain',
         'networks': {
             'tenant': {
                 'nic_mapping': {
                     'controller': {
                         'members': ['tenant_nic']
                     },
                     'compute': {
                         'members': ['tenant_nic']
                     }
                 }
             },
             'external': [{
                 'nic_mapping': {
                     'controller': {
                         'members': ['ext_nic']
                     },
                     'compute': {
                         'members': ['ext_nic']
                     }
                 }
             }]
         }
     }
     inv = None
     try:
         # Swap stdout
         saved_stdout = sys.stdout
         out = StringIO()
         sys.stdout = out
         # run test
         prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
         output = out.getvalue().strip()
         assert_in('CloudDomain: test.domain', output)
         assert_in('ssh-rsa', output)
         assert_in('ComputeKernelArgs: \'test=test \'', output)
         assert_in('fdio::vpp_cpu_main_core: \'test\'', output)
     finally:
         # put stdout back
         sys.stdout = saved_stdout
Example #7
0
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)
    utils.install_ansible()
    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)))
    os_version = deploy_settings['deploy_options']['os_version']
    net_env_file = os.path.join(args.deploy_dir, constants.NET_ENV_FILE)
    net_env = NetworkEnvironment(net_settings,
                                 net_env_file,
                                 os_version=os_version)
    net_env_target = os.path.join(APEX_TEMP_DIR, constants.NET_ENV_FILE)
    utils.dump_yaml(dict(net_env), net_env_target)

    # get global deploy params
    ha_enabled = deploy_settings['global_params']['ha_enabled']
    introspect = deploy_settings['global_params'].get('introspect', True)

    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)
    ds_opts = deploy_settings['deploy_options']
    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
        }
        utils.run_ansible(
            ansible_args,
            os.path.join(args.lib_dir, constants.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, args.deploy_dir)
        else:
            # Attach interfaces to jumphost for baremetal deployment
            jump_networks = ['admin']
            if uc_external:
                jump_networks.append('external')
            for network in jump_networks:
                if network == 'external':
                    # TODO(trozet): enable vlan secondary external networks
                    iface = net_settings['networks'][network][0][
                        'installer_vm']['members'][0]
                else:
                    iface = net_settings['networks'][network]['installer_vm'][
                        'members'][0]
                bridge = "br-{}".format(network)
                jumphost.attach_interface_to_ovs(bridge, iface, network)
        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

        upstream = (os_version != constants.DEFAULT_OS_VERSION
                    or args.upstream)
        if os_version == 'master':
            branch = 'master'
        else:
            branch = "stable/{}".format(os_version)
        if upstream:
            logging.info("Deploying with upstream artifacts for OpenStack "
                         "{}".format(os_version))
            args.image_dir = os.path.join(args.image_dir, os_version)
            upstream_url = constants.UPSTREAM_RDO.replace(
                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,
                                            fetch=not args.no_fetch)
            sdn_image = os.path.join(args.image_dir, 'overcloud-full.qcow2')
            # 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')
            shutil.copyfile(uc_fetch_img, uc_image)
            # prep undercloud with required packages
            uc_builder.add_upstream_packages(uc_image)
            # add patches from upstream to undercloud and overcloud
            logging.info('Adding patches to undercloud')
            patches = deploy_settings['global_params']['patches']
            c_builder.add_upstream_patches(patches['undercloud'], uc_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,
                                       external_network=uc_external,
                                       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':
            oc_cfg.create_nic_template(net_settings, deploy_settings, role,
                                       args.deploy_dir, APEX_TEMP_DIR)
        # Install Undercloud
        undercloud.configure(net_settings,
                             deploy_settings,
                             os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
                                          'configure_undercloud.yml'),
                             APEX_TEMP_DIR,
                             virtual_oc=args.virtual)

        # Prepare overcloud-full.qcow2
        logging.info("Preparing Overcloud for deployment...")
        if os_version != 'ocata':
            net_data_file = os.path.join(APEX_TEMP_DIR, 'network_data.yaml')
            net_data = network_data.create_network_data(
                net_settings, net_data_file)
        else:
            net_data = False
        if upstream and args.env_file == 'opnfv-environment.yaml':
            # Override the env_file if it is defaulted to opnfv
            # opnfv env file will not work with upstream
            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)
        else:
            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, constants.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='******',
                                  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, constants.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['aarch64'] = platform.machine() == 'aarch64'
        deploy_vars['introspect'] = not (args.virtual or deploy_vars['aarch64']
                                         or not introspect)
        deploy_vars['dns_server_args'] = ''
        deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
        deploy_vars['apex_env_file'] = os.path.basename(opnfv_env)
        deploy_vars['stackrc'] = 'source /home/stack/stackrc'
        deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
        deploy_vars['upstream'] = upstream
        deploy_vars['os_version'] = os_version
        deploy_vars['http_proxy'] = net_settings.get('http_proxy', '')
        deploy_vars['https_proxy'] = net_settings.get('https_proxy', '')
        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='******',
                              tmp_dir=APEX_TEMP_DIR)
            logging.info("Overcloud deployment complete")
        except Exception:
            logging.error("Deployment Failed.  Please check log")
            raise
        finally:
            os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))

        # 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'] = \
            oc_deploy.external_network_cmds(net_settings, deploy_settings)
        # TODO(trozet): just parse all ds_opts as deploy vars one time
        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'] = \
                oc_deploy.create_congress_cmds(overcloudrc)
            deploy_vars['congress'] = True
        else:
            deploy_vars['congress'] = False
        deploy_vars['calipso'] = ds_opts.get('calipso', False)
        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:
            deploy_vars['overcloudrc_files'] = ['overcloudrc']

        post_undercloud = os.path.join(args.lib_dir, constants.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='******',
                              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']
        deploy_vars['l2gw'] = ds_opts.get('l2gw')
        deploy_vars['sriov'] = ds_opts.get('sriov')
        # TODO(trozet): pull all logs and store in tmp dir in overcloud
        # playbook
        post_overcloud = os.path.join(args.lib_dir, constants.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='******',
                                  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))
Example #8
0
 def test_prep_env(self, mock_shutil, mock_fileinput, mock_yaml):
     mock_fileinput.input.return_value = \
         ['CloudDomain', 'replace_private_key', 'replace_public_key',
          'opendaylight::vpp_routing_node', 'ControllerExtraConfig',
          'NovaComputeExtraConfig', 'ComputeKernelArgs', 'HostCpusList',
          'ComputeExtraConfigPre', 'resource_registry',
          'NovaSchedulerDefaultFilters']
     mock_yaml.safe_load.return_value = {
         'parameter_defaults': {
             'ControllerServices': [1, 2, 3],
             'ComputeServices': [3, 4, 5]
         }}
     ds = {'global_params': {'ha_enabled': False},
           'deploy_options':
           {'sdn_controller': 'opendaylight',
            'odl_vpp_routing_node': 'test',
            'dataplane': 'ovs_dpdk',
            'sriov': 'xxx',
            'performance': {'Compute': {'vpp': {'main-core': 'test',
                                                'corelist-workers': 'test'},
                                        'ovs': {'dpdk_cores': 'test'},
                                        'kernel': {'test': 'test'}},
                            'Controller': {'vpp': 'test'}}}}
     ns_dict = {'domain_name': 'test.domain',
                'networks':
                {'tenant':
                 {'nic_mapping': {'controller':
                                  {'members': ['tenant_nic']},
                                  'compute':
                                  {'members': ['tenant_nic']}}},
                 'external':
                 [{'nic_mapping': {'controller':
                                   {'members': ['ext_nic']},
                                   'compute':
                                   {'members': ['ext_nic']}}}]}}
     inv = MagicMock()
     inv.get_node_counts.return_value = (1, 0)
     try:
         # Swap stdout
         saved_stdout = sys.stdout
         out = StringIO()
         sys.stdout = out
         ns = MagicMock()
         ns.enabled_network_list = ['external', 'tenant']
         ns.__getitem__.side_effect = lambda i: ns_dict.get(i, MagicMock())
         # run test
         prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
         output = out.getvalue().strip()
         assert_in('CloudDomain: test.domain', output)
         assert_in('ssh-rsa', output)
         assert_in('ComputeKernelArgs: \'test=test \'', output)
         assert_in('fdio::vpp_cpu_main_core: \'test\'', output)
         mock_yaml.safe_dump.assert_called_with(
             {'parameter_defaults': {
                 'ControllerServices': [1, 2, 3, 4, 5],
             }},
             mock.ANY, default_flow_style=False
         )
     finally:
         # put stdout back
         sys.stdout = saved_stdout