def read_input_inventory(self):
     auth_url = os.environ.get('OS_AUTH_URL')
     os_username = os.environ.get('OS_USERNAME')
     os_project_name = os.environ.get(
         'OS_PROJECT_NAME', os.environ.get('OS_TENANT_NAME'))
     os_password = os.environ.get('OS_PASSWORD')
     os_auth_token = os.environ.get('OS_AUTH_TOKEN')
     os_cacert = os.environ.get('OS_CACERT')
     ansible_ssh_user = os.environ.get('ANSIBLE_SSH_USER', 'heat-admin')
     self.plan_name = (os.environ.get('TRIPLEO_PLAN_NAME') or
                       os.environ.get('STACK_NAME_NAME') or
                       self.get_tripleo_plan_name())
     session = get_auth_session(auth_url,
                                os_username,
                                os_project_name,
                                os_password,
                                os_auth_token,
                                os_cacert)
     hclient = heat_client.Client('1', session=session)
     inventory = TripleoInventory(
         session=session,
         hclient=hclient,
         auth_url=auth_url,
         cacert=os_cacert,
         project_name=os_project_name,
         username=os_username,
         ansible_ssh_user=ansible_ssh_user,
         plan_name=self.plan_name)
     return inventory.list()
示例#2
0
    def read_input_inventory(self):
        auth_url = os.environ.get('OS_AUTH_URL')
        os_username = os.environ.get('OS_USERNAME')
        os_project_name = os.environ.get('OS_PROJECT_NAME',
                                         os.environ.get('OS_TENANT_NAME'))
        os_password = os.environ.get('OS_PASSWORD')
        os_auth_token = os.environ.get('OS_AUTH_TOKEN')
        os_cacert = os.environ.get('OS_CACERT')
        os_user_domain_name = os.environ.get('OS_USER_DOMAIN_NAME')
        os_compute_api_version = os.environ.get('OS_COMPUTE_API_VERSION')
        ansible_ssh_user = os.environ.get('ANSIBLE_SSH_USER', 'heat-admin')

        self.osc_conn = openstack.connect()

        self.undercloud_stack = next(self.osc_conn.orchestration.stacks())
        self.plan_name = (os.environ.get('TRIPLEO_PLAN_NAME')
                          or os.environ.get('STACK_NAME_NAME')
                          or self.get_tripleo_plan_name())

        #session = get_auth_session(auth_variables),
        session = self.osc_conn.session
        heat_api_version = (
            self.osc_conn.orchestration.get_api_major_version()[0])
        self.hclient = heat_client.Client(heat_api_version, session=session)
        inventory = TripleoInventory(session=session,
                                     hclient=self.hclient,
                                     auth_url=auth_url,
                                     cacert=os_cacert,
                                     project_name=os_project_name,
                                     username=os_username,
                                     ansible_ssh_user=ansible_ssh_user,
                                     plan_name=self.plan_name)

        return inventory.list()
 def read_input_inventory(self):
     auth_url = os.environ.get('OS_AUTH_URL')
     os_username = os.environ.get('OS_USERNAME')
     os_project_name = os.environ.get('OS_PROJECT_NAME',
                                      os.environ.get('OS_TENANT_NAME'))
     os_password = os.environ.get('OS_PASSWORD')
     os_auth_token = os.environ.get('OS_AUTH_TOKEN')
     os_cacert = os.environ.get('OS_CACERT')
     ansible_ssh_user = os.environ.get('ANSIBLE_SSH_USER', 'heat-admin')
     self.plan_name = (os.environ.get('TRIPLEO_PLAN_NAME')
                       or os.environ.get('STACK_NAME_NAME')
                       or self.get_tripleo_plan_name())
     session = get_auth_session(auth_url, os_username, os_project_name,
                                os_password, os_auth_token, os_cacert)
     hclient = heat_client.Client('1', session=session)
     inventory = TripleoInventory(session=session,
                                  hclient=hclient,
                                  auth_url=auth_url,
                                  cacert=os_cacert,
                                  project_name=os_project_name,
                                  username=os_username,
                                  ansible_ssh_user=ansible_ssh_user,
                                  plan_name=self.plan_name)
     return inventory.list()
class TestInventory(base.TestCase):
    def setUp(self):
        super(TestInventory, self).setUp()
        self.outputs_data = {'outputs': [
            {'output_key': 'EnabledServices',
             'output_value': {
                 'Controller': ['sa', 'sb'],
                 'Compute': ['sd', 'se'],
                 'CustomRole': ['sg', 'sh']}},
            {'output_key': 'KeystoneURL',
             'output_value': 'xyz://keystone'},
            {'output_key': 'ServerIdData',
             'output_value': {
                 'server_ids': {
                     'Controller': ['a', 'b', 'c'],
                     'Compute': ['d'],
                     'CustomRole': ['e']},
                 'bootstrap_server_id': 'a'}},
            {'output_key': 'RoleNetHostnameMap',
             'output_value': {
                 'Controller': {
                     'ctlplane': ['c-0.ctlplane.localdomain',
                                  'c-1.ctlplane.localdomain',
                                  'c-2.ctlplane.localdomain']},
                 'Compute': {
                     'ctlplane': ['cp-0.ctlplane.localdomain']},
                 'CustomRole': {
                     'ctlplane': ['cs-0.ctlplane.localdomain']}}},
            {'output_key': 'RoleNetIpMap',
             'output_value': {
                 'Controller': {
                     'ctlplane': ['x.x.x.1',
                                  'x.x.x.2',
                                  'x.x.x.3']},
                 'Compute': {
                     'ctlplane': ['y.y.y.1']},
                 'CustomRole': {
                     'ctlplane': ['z.z.z.1']}}},
            {'output_key': 'VipMap',
             'output_value': {
                 'ctlplane': 'x.x.x.4',
                 'redis': 'x.x.x.6'}},
            {'output_key': 'RoleData',
             'output_value': {
                 'Controller': {'config_settings': 'foo1'},
                 'Compute': {'config_settings': 'foo2'},
                 'CustomRole': {'config_settings': 'foo3'}}}]}
        self.plan_name = 'overcloud'

        self.hclient = MagicMock()
        self.hclient.stacks.environment.return_value = {
            'parameter_defaults': {'AdminPassword': '******',
                                   'ContainerCli': 'podman'}}
        self.mock_stack = MagicMock()
        self.mock_stack.outputs = self.outputs_data['outputs']
        self.hclient.stacks.get.return_value = self.mock_stack

        self.session = MagicMock()
        self.session.get_token.return_value = 'atoken'
        self.session.get_endpoint.return_value = 'anendpoint'

        self.outputs = StackOutputs('overcloud', self.hclient)
        self.inventory = TripleoInventory(
            session=self.session,
            hclient=self.hclient,
            plan_name=self.plan_name,
            auth_url='xyz://keystone.local',
            cacert='acacert',
            project_name='admin',
            username='******',
            ansible_ssh_user='******')
        self.inventory.stack_outputs = self.outputs

    def test_get_roles_by_service(self):
        services = TripleoInventory.get_roles_by_service(
            MOCK_ENABLED_SERVICES)
        expected = {
            'kernel': ['BlockStorage', 'CephStorage', 'Compute', 'Controller',
                       'ObjectStorage'],
            'swift_storage': ['ObjectStorage'],
            'tripleo_packages': ['BlockStorage', 'CephStorage', 'Compute',
                                 'Controller', 'ObjectStorage'],
            'keystone': ['Controller'],
            'nova_compute': ['Compute'],
            'cinder_volume': ['BlockStorage'],
        }
        self.assertDictEqual(services, expected)

    def test_outputs_are_empty_if_stack_doesnt_exist(self):
        self.hclient.stacks.get.side_effect = HTTPNotFound('not found')
        stack_outputs = StackOutputs('no-plan', self.hclient)
        self.assertEqual(list(stack_outputs), [])

    def test_outputs_valid_key_calls_api(self):
        expected = 'xyz://keystone'
        self.hclient.stacks.output_show.return_value = dict(output=dict(
            output_value=expected))
        self.assertEqual(expected, self.outputs['KeystoneURL'])
        # This should also support the get method
        self.assertEqual(expected, self.outputs.get('KeystoneURL'))
        self.assertTrue(self.hclient.called_once_with('overcloud',
                                                      'KeystoneURL'))

    def test_outputs_invalid_key_raises_keyerror(self):
        self.assertRaises(KeyError, lambda: self.outputs['Invalid'])

    def test_outputs_get_method_returns_default(self):
        default = 'default value'
        self.assertEqual(default, self.outputs.get('Invalid', default))

    def test_outputs_iterating_returns_list_of_output_keys(self):
        self.assertEqual(
            {'EnabledServices', 'KeystoneURL', 'ServerIdData',
             'RoleNetHostnameMap', 'RoleNetIpMap', 'VipMap',
             'RoleData'},
            set([o for o in self.outputs]))

    def test_inventory_list(self):
        self.inventory.undercloud_connection = 'local'
        self._inventory_list(self.inventory)

    def _inventory_list(self, inventory):
        ansible_ssh_user = '******'
        expected = {
            'Compute': {
                'hosts': ['cp-0'],
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'Compute'}},
            'Controller': {
                'hosts': ['c-0', 'c-1', 'c-2'],
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'Controller'}},
            'CustomRole': {
                'hosts': ['cs-0'],
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'CustomRole'}},

            'overcloud': {
                'children': ['Compute', 'Controller', 'CustomRole'],
                'vars': {
                    'container_cli': 'podman',
                    'ctlplane_vip': 'x.x.x.4',
                    'redis_vip': 'x.x.x.6'}},
            'Undercloud': {
                'hosts': ['undercloud'],
                'vars': {'ansible_connection': 'local',
                         'ansible_host': 'localhost',
                         'ansible_python_interpreter': sys.executable,
                         'ansible_remote_tmp': '/tmp/ansible-${USER}',
                         'auth_url': 'xyz://keystone.local',
                         'cacert': 'acacert',
                         'os_auth_token': 'atoken',
                         'overcloud_keystone_url': 'xyz://keystone',
                         'overcloud_admin_password': '******',
                         'plan': 'overcloud',
                         'project_name': 'admin',
                         'undercloud_service_list': [
                             'openstack-nova-compute',
                             'openstack-heat-engine',
                             'openstack-ironic-conductor',
                             'openstack-swift-container',
                             'openstack-swift-object',
                             'openstack-mistral-engine'],
                         'undercloud_swift_url': 'anendpoint',
                         'username': '******'}}}
        inv_list = inventory.list()
        for k in expected:
            self.assertEqual(expected[k], inv_list[k])

    def test_ansible_ssh_user(self):
        self._try_alternative_args(
            ansible_ssh_user='******',
            undercloud_connection='ssh',
            session=self.session,)

    def test_no_session(self):
        self._try_alternative_args(
            ansible_ssh_user='******',
            undercloud_connection='ssh',
            session=None)

    def _try_alternative_args(self, ansible_ssh_user, session,
                              undercloud_connection):
        key_file = '/var/lib/mistral/.ssh/%s-key' % ansible_ssh_user
        self.inventory = TripleoInventory(
            session=session,
            hclient=self.hclient,
            plan_name=self.plan_name,
            auth_url='xyz://keystone.local',
            project_name='admin',
            username='******',
            cacert='acacert',
            ansible_ssh_user=ansible_ssh_user,
            undercloud_connection=undercloud_connection,
            undercloud_key_file=key_file,
            ansible_python_interpreter='foo')

        self.inventory.stack_outputs = self.outputs

        expected = {
            'Compute': {
                'hosts': ['cp-0'],
                'vars': {'ansible_python_interpreter': 'foo',
                         'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'Compute'}},
            'Controller': {
                'hosts': ['c-0', 'c-1', 'c-2'],
                'vars': {'ansible_python_interpreter': 'foo',
                         'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'Controller'}},
            'CustomRole': {
                'hosts': ['cs-0'],
                'vars': {'ansible_python_interpreter': 'foo',
                         'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'CustomRole'}},
            'overcloud': {
                'children': ['Compute', 'Controller', 'CustomRole'],
                'vars': {
                    'container_cli': 'podman',
                    'ctlplane_vip': 'x.x.x.4',
                    'redis_vip': 'x.x.x.6'}},
            'Undercloud': {
                'hosts': ['undercloud'],
                'vars': {'ansible_connection': 'ssh',
                         'ansible_ssh_private_key_file': key_file,
                         'ansible_ssh_user': '******',
                         'ansible_host': 'localhost',
                         'ansible_python_interpreter': 'foo',
                         'ansible_remote_tmp': '/tmp/ansible-${USER}',
                         'auth_url': 'xyz://keystone.local',
                         'cacert': 'acacert',
                         'os_auth_token':
                         'atoken' if session else None,
                         'overcloud_keystone_url': 'xyz://keystone',
                         'overcloud_admin_password': '******',
                         'plan': 'overcloud',
                         'project_name': 'admin',
                         'undercloud_service_list': [
                             'openstack-nova-compute',
                             'openstack-heat-engine',
                             'openstack-ironic-conductor',
                             'openstack-swift-container',
                             'openstack-swift-object',
                             'openstack-mistral-engine'],
                         'undercloud_swift_url':
                         'anendpoint' if session else None,
                         'username': '******'}}}

        inv_list = self.inventory.list()
        for k in expected:
            self.assertEqual(expected[k], inv_list[k])

    def test_inventory_write_static(self):
        self.inventory.undercloud_connection = 'local'
        self._inventory_write_static()

    def test_inventory_write_static_extra_vars(self):
        self.inventory.undercloud_connection = 'local'
        extra_vars = {'Undercloud': {'anextravar': 123}}
        self._inventory_write_static(extra_vars=extra_vars)

    def _inventory_write_static(self, extra_vars=None):
        tmp_dir = self.useFixture(fixtures.TempDir()).path
        inv_path = os.path.join(tmp_dir, "inventory.yaml")
        self.inventory.write_static_inventory(inv_path, extra_vars)
        ansible_ssh_user = '******'
        expected = {
            'Compute': {
                'hosts': {
                    'cp-0': {
                        'ansible_host': 'y.y.y.1',
                        'ctlplane_ip': 'y.y.y.1',
                        'deploy_server_id': 'd',
                        'enabled_networks': ['ctlplane']}},
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'Compute'}},
            'Controller': {
                'hosts': {
                    'c-0': {
                        'ansible_host': 'x.x.x.1',
                        'ctlplane_ip': 'x.x.x.1',
                        'deploy_server_id': 'a',
                        'enabled_networks': ['ctlplane']},
                    'c-1': {
                        'ansible_host': 'x.x.x.2',
                        'ctlplane_ip': 'x.x.x.2',
                        'deploy_server_id': 'b',
                        'enabled_networks': ['ctlplane']},
                    'c-2': {
                        'ansible_host': 'x.x.x.3',
                        'ctlplane_ip': 'x.x.x.3',
                        'deploy_server_id': 'c',
                        'enabled_networks': ['ctlplane']}},
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'Controller'}},
            'CustomRole': {
                'hosts': {
                    'cs-0': {
                        'ansible_host': 'z.z.z.1',
                        'ctlplane_ip': 'z.z.z.1',
                        'deploy_server_id': 'e',
                        'enabled_networks': ['ctlplane']}},
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'tripleo_role_name': 'CustomRole'}},
            'overcloud': {'children': {'Compute': {},
                                       'Controller': {},
                                       'CustomRole': {}},
                          'vars': {'container_cli': 'podman',
                                   'ctlplane_vip': 'x.x.x.4',
                                   'redis_vip': 'x.x.x.6'}},
            'sa': {'children': {'Controller': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'sb': {'children': {'Controller': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'sd': {'children': {'Compute': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'se': {'children': {'Compute': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'sg': {'children': {'CustomRole': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'sh': {'children': {'CustomRole': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'Undercloud': {'hosts': {'undercloud': {}},
                           'vars': {'ansible_connection': 'local',
                                    'ansible_host': 'localhost',
                                    'ansible_python_interpreter':
                                        sys.executable,
                                    'ansible_remote_tmp':
                                        '/tmp/ansible-${USER}',
                                    'auth_url': 'xyz://keystone.local',
                                    'cacert': 'acacert',
                                    'os_auth_token': 'atoken',
                                    'overcloud_admin_password': '******',
                                    'overcloud_keystone_url': 'xyz://keystone',
                                    'plan': 'overcloud',
                                    'project_name': 'admin',
                                    'undercloud_service_list': [
                                        'openstack-nova-compute',
                                        'openstack-heat-engine',
                                        'openstack-ironic-conductor',
                                        'openstack-swift-container',
                                        'openstack-swift-object',
                                        'openstack-mistral-engine'],
                                    'undercloud_swift_url': 'anendpoint',
                                    'username': '******'}}}
        if extra_vars:
            expected['Undercloud']['vars']['anextravar'] = 123

        with open(inv_path, 'r') as f:
            loaded_inv = yaml.safe_load(f)
        self.assertEqual(expected, loaded_inv)
class TestInventory(base.TestCase):
    def setUp(self):
        super(TestInventory, self).setUp()
        self.outputs_data = {'outputs': [
            {'output_key': 'EnabledServices',
             'output_value': {
                 'Controller': ['sa', 'sb'],
                 'Compute': ['sd', 'se', 'ceph_client'],
                 'CustomRole': ['sg', 'sh']}},
            {'output_key': 'KeystoneURL',
             'output_value': 'xyz://keystone'},
            {'output_key': 'ServerIdData',
             'output_value': {
                 'server_ids': {
                     'Controller': ['a', 'b', 'c'],
                     'Compute': ['d'],
                     'CustomRole': ['e']},
                 'bootstrap_server_id': 'a'}},
            {'output_key': 'RoleNetHostnameMap',
             'output_value': {
                 'Controller': {
                     'ctlplane': ['c-0.ctlplane.localdomain',
                                  'c-1.ctlplane.localdomain',
                                  'c-2.ctlplane.localdomain'],
                     'internal_api': ['c-0.internal_api.localdomain',
                                      'c-1.internal_api.localdomain',
                                      'c-2.internal_api.localdomain']},
                 'Compute': {
                     'ctlplane': ['cp-0.ctlplane.localdomain']},
                 'CustomRole': {
                     'ctlplane': ['cs-0.ctlplane.localdomain']}}},
            {'output_key': 'RoleNetIpMap',
             'output_value': {
                 'Controller': {
                     'ctlplane': ['x.x.x.1',
                                  'x.x.x.2',
                                  'x.x.x.3'],
                     'internal_api': ['x.x.x.4',
                                      'x.x.x.5',
                                      'x.x.x.6']},
                 'Compute': {
                     'ctlplane': ['y.y.y.1']},
                 'CustomRole': {
                     'ctlplane': ['z.z.z.1']}}},
            {'output_key': 'VipMap',
             'output_value': {
                 'ctlplane': 'x.x.x.4',
                 'redis': 'x.x.x.6'}},
            {'output_key': 'RoleData',
             'output_value': {
                 'Controller': {'config_settings': 'foo1'},
                 'Compute': {'config_settings': 'foo2'},
                 'CustomRole': {'config_settings': 'foo3'}}}]}
        self.plan_name = 'overcloud'

        self.hclient = MagicMock()
        self.hclient.stacks.environment.return_value = {
            'parameter_defaults': {'AdminPassword': '******',
                                   'ContainerCli': 'podman'}}
        self.mock_stack = MagicMock()
        self.mock_stack.outputs = self.outputs_data['outputs']
        self.hclient.stacks.get.return_value = self.mock_stack

        self.session = MagicMock()
        self.session.get_token.return_value = 'atoken'
        self.session.get_endpoint.return_value = 'anendpoint'

        self.outputs = StackOutputs(self.mock_stack)
        self.inventory = TripleoInventory(
            session=self.session,
            hclient=self.hclient,
            plan_name=self.plan_name,
            auth_url='xyz://keystone.local',
            cacert='acacert',
            project_name='admin',
            username='******',
            ansible_ssh_user='******')
        self.inventory.stack_outputs = self.outputs

    def test_get_roles_by_service(self):
        services = TripleoInventory.get_roles_by_service(
            MOCK_ENABLED_SERVICES)
        expected = {
            'kernel': ['BlockStorage', 'CephStorage', 'Compute', 'Controller',
                       'ObjectStorage'],
            'swift_storage': ['ObjectStorage'],
            'tripleo_packages': ['BlockStorage', 'CephStorage', 'Compute',
                                 'Controller', 'ObjectStorage'],
            'keystone': ['Controller'],
            'nova_compute': ['Compute'],
            'cinder_volume': ['BlockStorage'],
            'ceph_client': ['Compute'],
        }
        self.assertDictEqual(services, expected)

    def test_stack_not_found(self):
        self.hclient.stacks.get.side_effect = HTTPNotFound('not found')
        self.assertEqual(None,
                         self.inventory._get_stack())

    def test_outputs_valid_key_calls_api(self):
        expected = 'xyz://keystone'
        self.hclient.stacks.output_show.return_value = dict(output=dict(
            output_value=expected))
        self.assertEqual(expected, self.outputs['KeystoneURL'])
        # This should also support the get method
        self.assertEqual(expected, self.outputs.get('KeystoneURL'))
        self.assertTrue(self.hclient.called_once_with('overcloud',
                                                      'KeystoneURL'))

    def test_no_ips(self):
        for output in self.outputs_data['outputs']:
            if output['output_key'] == 'RoleNetIpMap':
                output['output_value'] = dict(Controller=dict(ctlplane=[]))
        self.assertRaises(Exception, self.inventory.list)

    def test_outputs_invalid_key_raises_keyerror(self):
        self.assertRaises(KeyError, lambda: self.outputs['Invalid'])

    def test_outputs_get_method_returns_default(self):
        default = 'default value'
        self.assertEqual(default, self.outputs.get('Invalid', default))

    def test_outputs_iterating_returns_list_of_output_keys(self):
        self.assertEqual(
            {'EnabledServices', 'KeystoneURL', 'ServerIdData',
             'RoleNetHostnameMap', 'RoleNetIpMap', 'VipMap',
             'RoleData'},
            set([o for o in self.outputs]))

    def test_inventory_list(self):
        self.inventory.undercloud_connection = 'local'
        self._inventory_list(self.inventory)

    def _inventory_list(self, inventory):
        ansible_ssh_user = '******'
        expected = {
            'Compute': {
                'hosts': ['cp-0'],
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'Compute',
                         'tripleo_role_networks': ['ctlplane']}},
            'Controller': {
                'hosts': ['c-0', 'c-1', 'c-2'],
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'Controller',
                         'tripleo_role_networks': ['ctlplane',
                                                   'internal_api']}},
            'CustomRole': {
                'hosts': ['cs-0'],
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'CustomRole',
                         'tripleo_role_networks': ['ctlplane']}},
            'overcloud': {
                'children': ['Compute', 'Controller', 'CustomRole'],
                'vars': {
                    'container_cli': 'podman',
                    'ctlplane_vip': 'x.x.x.4',
                    'redis_vip': 'x.x.x.6'}},
            'Undercloud': {
                'hosts': ['undercloud'],
                'vars': {'ansible_connection': 'local',
                         'ansible_host': 'localhost',
                         'ansible_python_interpreter': sys.executable,
                         'ansible_remote_tmp': '/tmp/ansible-${USER}',
                         'auth_url': 'xyz://keystone.local',
                         'cacert': 'acacert',
                         'os_auth_token': 'atoken',
                         'overcloud_keystone_url': 'xyz://keystone',
                         'overcloud_admin_password': '******',
                         'plan': 'overcloud',
                         'project_name': 'admin',
                         'undercloud_service_list': [
                             'tripleo_nova_compute',
                             'tripleo_heat_engine',
                             'tripleo_ironic_conductor',
                             'tripleo_swift_container_server',
                             'tripleo_swift_object_server',
                             'tripleo_mistral_engine'],
                         'undercloud_swift_url': 'anendpoint',
                         'username': '******'}}}
        inv_list = inventory.list()
        for k in expected:
            self.assertEqual(expected[k], inv_list[k])

    def test_ansible_ssh_user(self):
        self._try_alternative_args(
            ansible_ssh_user='******',
            undercloud_connection='ssh',
            session=self.session,)

    def test_no_session(self):
        self._try_alternative_args(
            ansible_ssh_user='******',
            undercloud_connection='ssh',
            session=None)

    def _try_alternative_args(self, ansible_ssh_user, session,
                              undercloud_connection):
        key_file = '/var/lib/mistral/.ssh/%s-key' % ansible_ssh_user
        self.inventory = TripleoInventory(
            session=session,
            hclient=self.hclient,
            plan_name=self.plan_name,
            auth_url='xyz://keystone.local',
            project_name='admin',
            username='******',
            cacert='acacert',
            ansible_ssh_user=ansible_ssh_user,
            undercloud_connection=undercloud_connection,
            undercloud_key_file=key_file,
            ansible_python_interpreter='foo')

        self.inventory.stack_outputs = self.outputs

        expected = {
            'Compute': {
                'hosts': ['cp-0'],
                'vars': {'ansible_python_interpreter': 'foo',
                         'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'Compute',
                         'tripleo_role_networks': ['ctlplane']}},
            'Controller': {
                'hosts': ['c-0', 'c-1', 'c-2'],
                'vars': {'ansible_python_interpreter': 'foo',
                         'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'Controller',
                         'tripleo_role_networks': ['ctlplane',
                                                   'internal_api']}},
            'CustomRole': {
                'hosts': ['cs-0'],
                'vars': {'ansible_python_interpreter': 'foo',
                         'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'CustomRole',
                         'tripleo_role_networks': ['ctlplane']}},
            'overcloud': {
                'children': ['Compute', 'Controller', 'CustomRole'],
                'vars': {
                    'container_cli': 'podman',
                    'ctlplane_vip': 'x.x.x.4',
                    'redis_vip': 'x.x.x.6'}},
            'sa': {'children': ['Controller'],
                   'vars': {'ansible_ssh_user': '******',
                            'ansible_python_interpreter': 'foo'}},
            'sb': {'children': ['Controller'],
                   'vars': {'ansible_ssh_user': '******',
                            'ansible_python_interpreter': 'foo'}},
            'sd': {'children': ['Compute'],
                   'vars': {'ansible_ssh_user': '******',
                            'ansible_python_interpreter': 'foo'}},
            'se': {'children': ['Compute'],
                   'vars': {'ansible_ssh_user': '******',
                            'ansible_python_interpreter': 'foo'}},
            'ceph_client': {'children': ['Compute'],
                            'vars': {'ansible_ssh_user': '******',
                                     'ansible_python_interpreter': 'foo'}},
            'clients': {'children': ['Compute'],
                        'vars': {'ansible_ssh_user': '******',
                                 'ansible_python_interpreter': 'foo'}},
            'sg': {'children': ['CustomRole'],
                   'vars': {'ansible_ssh_user': '******',
                            'ansible_python_interpreter': 'foo'}},
            'sh': {'children': ['CustomRole'],
                   'vars': {'ansible_ssh_user': '******',
                            'ansible_python_interpreter': 'foo'}},
            'Undercloud': {
                'hosts': ['undercloud'],
                'vars': {'ansible_connection': 'ssh',
                         'ansible_ssh_private_key_file': key_file,
                         'ansible_ssh_user': '******',
                         'ansible_host': 'localhost',
                         'ansible_python_interpreter': 'foo',
                         'ansible_remote_tmp': '/tmp/ansible-${USER}',
                         'auth_url': 'xyz://keystone.local',
                         'cacert': 'acacert',
                         'os_auth_token':
                         'atoken' if session else None,
                         'overcloud_keystone_url': 'xyz://keystone',
                         'overcloud_admin_password': '******',
                         'plan': 'overcloud',
                         'project_name': 'admin',
                         'undercloud_service_list': [
                             'tripleo_nova_compute',
                             'tripleo_heat_engine',
                             'tripleo_ironic_conductor',
                             'tripleo_swift_container_server',
                             'tripleo_swift_object_server',
                             'tripleo_mistral_engine'],
                         'undercloud_swift_url':
                         'anendpoint' if session else None,
                         'username': '******'}}}

        inv_list = self.inventory.list()
        for k in expected:
            self.assertEqual(expected[k], inv_list[k])

    def test_inventory_write_static(self):
        self.inventory.undercloud_connection = 'local'
        self._inventory_write_static()

    def test_inventory_write_static_extra_vars(self):
        self.inventory.undercloud_connection = 'local'
        extra_vars = {'Undercloud': {'anextravar': 123}}
        self._inventory_write_static(extra_vars=extra_vars)

    def _inventory_write_static(self, extra_vars=None):
        tmp_dir = self.useFixture(fixtures.TempDir()).path
        inv_path = os.path.join(tmp_dir, "inventory.yaml")
        self.inventory.write_static_inventory(inv_path, extra_vars)
        ansible_ssh_user = '******'
        expected = {
            'Compute': {
                'hosts': {
                    'cp-0': {
                        'ansible_host': 'y.y.y.1',
                        'ctlplane_ip': 'y.y.y.1',
                        'deploy_server_id': 'd',
                        'ctlplane_hostname': 'cp-0.ctlplane.localdomain'}},
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'Compute',
                         'tripleo_role_networks': ['ctlplane']}},
            'Controller': {
                'hosts': {
                    'c-0': {
                        'ansible_host': 'x.x.x.1',
                        'ctlplane_ip': 'x.x.x.1',
                        'deploy_server_id': 'a',
                        'ctlplane_hostname': 'c-0.ctlplane.localdomain',
                        'internal_api_hostname':
                            'c-0.internal_api.localdomain',
                        'internal_api_ip': 'x.x.x.4'},
                    'c-1': {
                        'ansible_host': 'x.x.x.2',
                        'ctlplane_ip': 'x.x.x.2',
                        'deploy_server_id': 'b',
                        'ctlplane_hostname': 'c-1.ctlplane.localdomain',
                        'internal_api_hostname':
                            'c-1.internal_api.localdomain',
                        'internal_api_ip': 'x.x.x.5'},
                    'c-2': {
                        'ansible_host': 'x.x.x.3',
                        'ctlplane_ip': 'x.x.x.3',
                        'deploy_server_id': 'c',
                        'ctlplane_hostname': 'c-2.ctlplane.localdomain',
                        'internal_api_hostname':
                            'c-2.internal_api.localdomain',
                        'internal_api_ip': 'x.x.x.6'}},
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'Controller',
                         'tripleo_role_networks': ['ctlplane',
                                                   'internal_api']}},
            'CustomRole': {
                'hosts': {
                    'cs-0': {
                        'ansible_host': 'z.z.z.1',
                        'ctlplane_ip': 'z.z.z.1',
                        'deploy_server_id': 'e',
                        'ctlplane_hostname': 'cs-0.ctlplane.localdomain'}},
                'vars': {'ansible_ssh_user': ansible_ssh_user,
                         'bootstrap_server_id': 'a',
                         'serial': 1,
                         'tripleo_role_name': 'CustomRole',
                         'tripleo_role_networks': ['ctlplane']}},
            'overcloud': {'children': {'Compute': {},
                                       'Controller': {},
                                       'CustomRole': {}},
                          'vars': {'container_cli': 'podman',
                                   'ctlplane_vip': 'x.x.x.4',
                                   'redis_vip': 'x.x.x.6'}},
            'sa': {'children': {'Controller': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'sb': {'children': {'Controller': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'sd': {'children': {'Compute': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'se': {'children': {'Compute': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'ceph_client': {'children': {'Compute': {}},
                            'vars': {'ansible_ssh_user': '******'}},
            'clients': {'children': {'Compute': {}},
                        'vars': {'ansible_ssh_user': '******'}},
            'sg': {'children': {'CustomRole': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'sh': {'children': {'CustomRole': {}},
                   'vars': {'ansible_ssh_user': '******'}},
            'Undercloud': {'hosts': {'undercloud': {}},
                           'vars': {'ansible_connection': 'local',
                                    'ansible_host': 'localhost',
                                    'ansible_python_interpreter':
                                        sys.executable,
                                    'ansible_remote_tmp':
                                        '/tmp/ansible-${USER}',
                                    'auth_url': 'xyz://keystone.local',
                                    'cacert': 'acacert',
                                    'os_auth_token': 'atoken',
                                    'overcloud_admin_password': '******',
                                    'overcloud_keystone_url': 'xyz://keystone',
                                    'plan': 'overcloud',
                                    'project_name': 'admin',
                                    'undercloud_service_list': [
                                        'tripleo_nova_compute',
                                        'tripleo_heat_engine',
                                        'tripleo_ironic_conductor',
                                        'tripleo_swift_container_server',
                                        'tripleo_swift_object_server',
                                        'tripleo_mistral_engine'],
                                    'undercloud_swift_url': 'anendpoint',
                                    'username': '******'}}}
        if extra_vars:
            expected['Undercloud']['vars']['anextravar'] = 123

        with open(inv_path, 'r') as f:
            loaded_inv = yaml.safe_load(f)
        self.assertEqual(expected, loaded_inv)
示例#6
0
class TestInventory(base.TestCase):
    def setUp(self):
        super(TestInventory, self).setUp()
        self.outputs_data = {
            'outputs': [{
                'output_key': 'EnabledServices',
                'output_value': {
                    'Controller': ['sa', 'sb'],
                    'Compute': ['sd', 'se'],
                    'CustomRole': ['sg', 'sh']
                }
            }, {
                'output_key': 'KeystoneURL',
                'output_value': 'xyz://keystone'
            }, {
                'output_key': 'ServerIdData',
                'output_value': {
                    'server_ids': {
                        'Controller': ['a', 'b', 'c'],
                        'Compute': ['d'],
                        'CustomRole': ['e']
                    },
                    'bootstrap_server_id': 'a'
                }
            }, {
                'output_key': 'RoleNetHostnameMap',
                'output_value': {
                    'Controller': {
                        'ctlplane': [
                            'c-0.ctlplane.localdomain',
                            'c-1.ctlplane.localdomain',
                            'c-2.ctlplane.localdomain'
                        ]
                    },
                    'Compute': {
                        'ctlplane': ['cp-0.ctlplane.localdomain']
                    },
                    'CustomRole': {
                        'ctlplane': ['cs-0.ctlplane.localdomain']
                    }
                }
            }, {
                'output_key': 'RoleNetIpMap',
                'output_value': {
                    'Controller': {
                        'ctlplane': ['x.x.x.1', 'x.x.x.2', 'x.x.x.3']
                    },
                    'Compute': {
                        'ctlplane': ['y.y.y.1']
                    },
                    'CustomRole': {
                        'ctlplane': ['z.z.z.1']
                    }
                }
            }, {
                'output_key': 'VipMap',
                'output_value': {
                    'ctlplane': 'x.x.x.4',
                    'redis': 'x.x.x.6'
                }
            }, {
                'output_key': 'RoleData',
                'output_value': {
                    'Controller': {
                        'config_settings': 'foo1'
                    },
                    'Compute': {
                        'config_settings': 'foo2'
                    },
                    'CustomRole': {
                        'config_settings': 'foo3'
                    }
                }
            }]
        }
        self.plan_name = 'overcloud'

        self.hclient = MagicMock()
        self.hclient.stacks.environment.return_value = {
            'parameter_defaults': {
                'AdminPassword': '******'
            }
        }
        self.mock_stack = MagicMock()
        self.mock_stack.outputs = self.outputs_data['outputs']
        self.hclient.stacks.get.return_value = self.mock_stack

        self.session = MagicMock()
        self.session.get_token.return_value = 'atoken'
        self.session.get_endpoint.return_value = 'anendpoint'

        self.outputs = StackOutputs('overcloud', self.hclient)
        self.inventory = TripleoInventory(session=self.session,
                                          hclient=self.hclient,
                                          plan_name=self.plan_name,
                                          auth_url='xyz://keystone.local',
                                          cacert='acacert',
                                          project_name='admin',
                                          username='******',
                                          ansible_ssh_user='******')
        self.inventory.stack_outputs = self.outputs

    def test_get_roles_by_service(self):
        services = TripleoInventory.get_roles_by_service(MOCK_ENABLED_SERVICES)
        expected = {
            'kernel': [
                'BlockStorage', 'CephStorage', 'Compute', 'Controller',
                'ObjectStorage'
            ],
            'swift_storage': ['ObjectStorage'],
            'tripleo_packages': [
                'BlockStorage', 'CephStorage', 'Compute', 'Controller',
                'ObjectStorage'
            ],
            'keystone': ['Controller'],
            'nova_compute': ['Compute'],
            'cinder_volume': ['BlockStorage'],
        }
        self.assertDictEqual(services, expected)

    def test_outputs_are_empty_if_stack_doesnt_exist(self):
        self.hclient.stacks.get.side_effect = HTTPNotFound('not found')
        stack_outputs = StackOutputs('no-plan', self.hclient)
        self.assertEqual(list(stack_outputs), [])

    def test_outputs_valid_key_calls_api(self):
        expected = 'xyz://keystone'
        self.hclient.stacks.output_show.return_value = dict(output=dict(
            output_value=expected))
        self.assertEqual(expected, self.outputs['KeystoneURL'])
        # This should also support the get method
        self.assertEqual(expected, self.outputs.get('KeystoneURL'))
        self.assertTrue(
            self.hclient.called_once_with('overcloud', 'KeystoneURL'))

    def test_outputs_invalid_key_raises_keyerror(self):
        self.assertRaises(KeyError, lambda: self.outputs['Invalid'])

    def test_outputs_get_method_returns_default(self):
        default = 'default value'
        self.assertEqual(default, self.outputs.get('Invalid', default))

    def test_outputs_iterating_returns_list_of_output_keys(self):
        self.assertEqual(
            {
                'EnabledServices', 'KeystoneURL', 'ServerIdData',
                'RoleNetHostnameMap', 'RoleNetIpMap', 'VipMap', 'RoleData'
            }, set([o for o in self.outputs]))

    def test_inventory_list(self):
        self._inventory_list(self.inventory)

    def test_inventory_list_backwards_compat_configs(self):
        # FIXME(shardy) backwards compatibility until we switch
        # tripleo-validations to pass the individual values
        configs = MagicMock()
        configs.plan = self.plan_name
        configs.auth_url = 'xyz://keystone.local'
        configs.cacert = 'acacert'
        configs.project_name = 'admin'
        configs.username = '******'
        configs.ansible_ssh_user = '******'
        inventory = TripleoInventory(configs, self.session, self.hclient)
        self._inventory_list(inventory)

    def _inventory_list(self, inventory):
        expected = {
            'c-0': {
                'hosts': ['x.x.x.1'],
                'vars': {
                    'deploy_server_id': 'a',
                    'ctlplane_ip': 'x.x.x.1',
                    'enabled_networks': ['ctlplane']
                }
            },
            'c-1': {
                'hosts': ['x.x.x.2'],
                'vars': {
                    'deploy_server_id': 'b',
                    'ctlplane_ip': 'x.x.x.2',
                    'enabled_networks': ['ctlplane']
                }
            },
            'c-2': {
                'hosts': ['x.x.x.3'],
                'vars': {
                    'deploy_server_id': 'c',
                    'ctlplane_ip': 'x.x.x.3',
                    'enabled_networks': ['ctlplane']
                }
            },
            'Compute': {
                'children': ['cp-0'],
                'vars': {
                    'ansible_ssh_user': '******',
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo2',
                    'role_name': 'Compute'
                }
            },
            'Controller': {
                'children': ['c-0', 'c-1', 'c-2'],
                'vars': {
                    'ansible_ssh_user': '******',
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo1',
                    'role_name': 'Controller'
                }
            },
            'cp-0': {
                'hosts': ['y.y.y.1'],
                'vars': {
                    'deploy_server_id': 'd',
                    'ctlplane_ip': 'y.y.y.1',
                    'enabled_networks': ['ctlplane']
                }
            },
            'cs-0': {
                'hosts': ['z.z.z.1'],
                'vars': {
                    'deploy_server_id': 'e',
                    'ctlplane_ip': 'z.z.z.1',
                    'enabled_networks': ['ctlplane']
                }
            },
            'CustomRole': {
                'children': ['cs-0'],
                'vars': {
                    'ansible_ssh_user': '******',
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo3',
                    'role_name': 'CustomRole'
                }
            },
            'overcloud': {
                'children': ['Compute', 'Controller', 'CustomRole'],
                'vars': {
                    'ctlplane_vip': 'x.x.x.4',
                    'redis_vip': 'x.x.x.6'
                }
            },
            'undercloud': {
                'hosts': ['localhost'],
                'vars': {
                    'ansible_connection':
                    'local',
                    'auth_url':
                    'xyz://keystone.local',
                    'cacert':
                    'acacert',
                    'os_auth_token':
                    'atoken',
                    'overcloud_keystone_url':
                    'xyz://keystone',
                    'overcloud_admin_password':
                    '******',
                    'plan':
                    'overcloud',
                    'project_name':
                    'admin',
                    'undercloud_service_list': [
                        'openstack-nova-compute', 'openstack-heat-engine',
                        'openstack-ironic-conductor',
                        'openstack-swift-container', 'openstack-swift-object',
                        'openstack-mistral-engine'
                    ],
                    'undercloud_swift_url':
                    'anendpoint',
                    'username':
                    '******'
                }
            }
        }
        inv_list = inventory.list()
        for k in expected:
            self.assertEqual(expected[k], inv_list[k])

    def test_ansible_ssh_user(self):
        self._try_alternative_args(
            ansible_ssh_user='******',
            session=self.session,
        )

    def test_no_session(self):
        self._try_alternative_args(ansible_ssh_user='******',
                                   session=None)

    def _try_alternative_args(self, ansible_ssh_user, session):
        self.inventory = TripleoInventory(session=session,
                                          hclient=self.hclient,
                                          plan_name=self.plan_name,
                                          auth_url='xyz://keystone.local',
                                          project_name='admin',
                                          username='******',
                                          cacert='acacert',
                                          ansible_ssh_user=ansible_ssh_user)

        self.inventory.stack_outputs = self.outputs

        expected = {
            'c-0': {
                'hosts': ['x.x.x.1'],
                'vars': {
                    'deploy_server_id': 'a',
                    'ctlplane_ip': 'x.x.x.1',
                    'enabled_networks': ['ctlplane']
                }
            },
            'c-1': {
                'hosts': ['x.x.x.2'],
                'vars': {
                    'deploy_server_id': 'b',
                    'ctlplane_ip': 'x.x.x.2',
                    'enabled_networks': ['ctlplane']
                }
            },
            'c-2': {
                'hosts': ['x.x.x.3'],
                'vars': {
                    'deploy_server_id': 'c',
                    'ctlplane_ip': 'x.x.x.3',
                    'enabled_networks': ['ctlplane']
                }
            },
            'Compute': {
                'children': ['cp-0'],
                'vars': {
                    'ansible_ssh_user': ansible_ssh_user,
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo2',
                    'role_name': 'Compute'
                }
            },
            'Controller': {
                'children': ['c-0', 'c-1', 'c-2'],
                'vars': {
                    'ansible_ssh_user': ansible_ssh_user,
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo1',
                    'role_name': 'Controller'
                }
            },
            'cp-0': {
                'hosts': ['y.y.y.1'],
                'vars': {
                    'deploy_server_id': 'd',
                    'ctlplane_ip': 'y.y.y.1',
                    'enabled_networks': ['ctlplane']
                }
            },
            'cs-0': {
                'hosts': ['z.z.z.1'],
                'vars': {
                    'deploy_server_id': 'e',
                    'ctlplane_ip': 'z.z.z.1',
                    'enabled_networks': ['ctlplane']
                }
            },
            'CustomRole': {
                'children': ['cs-0'],
                'vars': {
                    'ansible_ssh_user': ansible_ssh_user,
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo3',
                    'role_name': 'CustomRole'
                }
            },
            'overcloud': {
                'children': ['Compute', 'Controller', 'CustomRole'],
                'vars': {
                    'ctlplane_vip': 'x.x.x.4',
                    'redis_vip': 'x.x.x.6'
                }
            },
            'undercloud': {
                'hosts': ['localhost'],
                'vars': {
                    'ansible_connection':
                    'local',
                    'auth_url':
                    'xyz://keystone.local',
                    'cacert':
                    'acacert',
                    'os_auth_token':
                    'atoken' if session else None,
                    'overcloud_keystone_url':
                    'xyz://keystone',
                    'overcloud_admin_password':
                    '******',
                    'plan':
                    'overcloud',
                    'project_name':
                    'admin',
                    'undercloud_service_list': [
                        'openstack-nova-compute', 'openstack-heat-engine',
                        'openstack-ironic-conductor',
                        'openstack-swift-container', 'openstack-swift-object',
                        'openstack-mistral-engine'
                    ],
                    'undercloud_swift_url':
                    'anendpoint' if session else None,
                    'username':
                    '******'
                }
            }
        }

        inv_list = self.inventory.list()
        for k in expected:
            self.assertEqual(expected[k], inv_list[k])

    def test_inventory_write_static(self):
        self._inventory_write_static()

    def test_inventory_write_static_extra_vars(self):
        extra_vars = {'undercloud': {'anextravar': 123}}
        self._inventory_write_static(extra_vars=extra_vars)

    def _inventory_write_static(self, extra_vars=None):
        tmp_dir = self.useFixture(fixtures.TempDir()).path
        inv_path = os.path.join(tmp_dir, "inventory.yaml")
        self.inventory.write_static_inventory(inv_path, extra_vars)
        expected = {
            'Compute': {
                'children': {
                    'cp-0': {}
                },
                'vars': {
                    'ansible_ssh_user': '******',
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo2',
                    'role_name': 'Compute'
                }
            },
            'Controller': {
                'children': {
                    'c-0': {},
                    'c-1': {},
                    'c-2': {}
                },
                'vars': {
                    'ansible_ssh_user': '******',
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo1',
                    'role_name': 'Controller'
                }
            },
            'CustomRole': {
                'children': {
                    'cs-0': {}
                },
                'vars': {
                    'ansible_ssh_user': '******',
                    'bootstrap_server_id': 'a',
                    'role_data_config_settings': 'foo3',
                    'role_name': 'CustomRole'
                }
            },
            '_meta': {
                'hostvars': {}
            },
            'c-0': {
                'hosts': {
                    'x.x.x.1': {}
                },
                'vars': {
                    'ctlplane_ip': 'x.x.x.1',
                    'deploy_server_id': 'a',
                    'enabled_networks': ['ctlplane']
                }
            },
            'c-1': {
                'hosts': {
                    'x.x.x.2': {}
                },
                'vars': {
                    'ctlplane_ip': 'x.x.x.2',
                    'deploy_server_id': 'b',
                    'enabled_networks': ['ctlplane']
                }
            },
            'c-2': {
                'hosts': {
                    'x.x.x.3': {}
                },
                'vars': {
                    'ctlplane_ip': 'x.x.x.3',
                    'deploy_server_id': 'c',
                    'enabled_networks': ['ctlplane']
                }
            },
            'cp-0': {
                'hosts': {
                    'y.y.y.1': {}
                },
                'vars': {
                    'ctlplane_ip': 'y.y.y.1',
                    'deploy_server_id': 'd',
                    'enabled_networks': ['ctlplane']
                }
            },
            'cs-0': {
                'hosts': {
                    'z.z.z.1': {}
                },
                'vars': {
                    'ctlplane_ip': 'z.z.z.1',
                    'deploy_server_id': 'e',
                    'enabled_networks': ['ctlplane']
                }
            },
            'overcloud': {
                'children': {
                    'Compute': {},
                    'Controller': {},
                    'CustomRole': {}
                },
                'vars': {
                    'ctlplane_vip': 'x.x.x.4',
                    'redis_vip': 'x.x.x.6'
                }
            },
            'sa': {
                'children': {
                    'Controller': {}
                },
                'vars': {
                    'ansible_ssh_user': '******'
                }
            },
            'sb': {
                'children': {
                    'Controller': {}
                },
                'vars': {
                    'ansible_ssh_user': '******'
                }
            },
            'sd': {
                'children': {
                    'Compute': {}
                },
                'vars': {
                    'ansible_ssh_user': '******'
                }
            },
            'se': {
                'children': {
                    'Compute': {}
                },
                'vars': {
                    'ansible_ssh_user': '******'
                }
            },
            'sg': {
                'children': {
                    'CustomRole': {}
                },
                'vars': {
                    'ansible_ssh_user': '******'
                }
            },
            'sh': {
                'children': {
                    'CustomRole': {}
                },
                'vars': {
                    'ansible_ssh_user': '******'
                }
            },
            'undercloud': {
                'hosts': {
                    'localhost': {}
                },
                'vars': {
                    'ansible_connection':
                    'local',
                    'auth_url':
                    'xyz://keystone.local',
                    'cacert':
                    'acacert',
                    'os_auth_token':
                    'atoken',
                    'overcloud_admin_password':
                    '******',
                    'overcloud_keystone_url':
                    'xyz://keystone',
                    'plan':
                    'overcloud',
                    'project_name':
                    'admin',
                    'undercloud_service_list': [
                        'openstack-nova-compute', 'openstack-heat-engine',
                        'openstack-ironic-conductor',
                        'openstack-swift-container', 'openstack-swift-object',
                        'openstack-mistral-engine'
                    ],
                    'undercloud_swift_url':
                    'anendpoint',
                    'username':
                    '******'
                }
            }
        }
        if extra_vars:
            expected['undercloud']['vars']['anextravar'] = 123

        with open(inv_path, 'r') as f:
            loaded_inv = yaml.safe_load(f)
        self.assertEqual(expected, loaded_inv)