In Pike, plans were migrated from using Mistral environments for storing parameters values to using a plan-environment.yaml file stored together with the plan in Swift. New plans created from THT have this file included automatically from Pike, and the migration step is no longer necessary starting in Queens. As of now, fast-forward upgrades will involve upgrading the undercloud from N to N+1 until N+3 is reached so removing the step isn't an issue. Change-Id: I4227df5c692ab09b88f84ad60bf241c0b8251f80
1269 lines
58 KiB
Python
1269 lines
58 KiB
Python
# Copyright 2015 Red Hat Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import collections
|
|
import io
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
|
|
import fixtures
|
|
from keystoneauth1 import exceptions as ks_exceptions
|
|
import mock
|
|
from novaclient import exceptions
|
|
from oslo_config import fixture as config_fixture
|
|
from oslotest import base
|
|
from oslotest import log
|
|
from six.moves import configparser
|
|
|
|
from instack_undercloud import undercloud
|
|
from instack_undercloud import validator
|
|
|
|
|
|
undercloud._configure_logging(undercloud.DEFAULT_LOG_LEVEL, None)
|
|
|
|
|
|
class BaseTestCase(base.BaseTestCase):
|
|
def setUp(self):
|
|
super(BaseTestCase, self).setUp()
|
|
self.logger = self.useFixture(log.ConfigureLogging()).logger
|
|
|
|
|
|
class TestUndercloud(BaseTestCase):
|
|
@mock.patch('instack_undercloud.undercloud._handle_upgrade_fact')
|
|
@mock.patch('instack_undercloud.undercloud._configure_logging')
|
|
@mock.patch('instack_undercloud.undercloud._validate_configuration')
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
@mock.patch('instack_undercloud.undercloud._post_config')
|
|
@mock.patch('instack_undercloud.undercloud._run_orc')
|
|
@mock.patch('instack_undercloud.undercloud._run_yum_update')
|
|
@mock.patch('instack_undercloud.undercloud._run_yum_clean_all')
|
|
@mock.patch('instack_undercloud.undercloud._run_instack')
|
|
@mock.patch('instack_undercloud.undercloud._generate_environment')
|
|
@mock.patch('instack_undercloud.undercloud._load_config')
|
|
@mock.patch('instack_undercloud.undercloud._die_tuskar_die')
|
|
@mock.patch('instack_undercloud.undercloud._run_validation_groups')
|
|
def test_install(self, mock_run_validation_groups,
|
|
mock_die_tuskar_die, mock_load_config,
|
|
mock_generate_environment, mock_run_instack,
|
|
mock_run_clean_all, mock_run_yum_update, mock_run_orc,
|
|
mock_post_config, mock_run_command,
|
|
mock_validate_configuration, mock_configure_logging,
|
|
mock_upgrade_fact):
|
|
fake_env = mock.MagicMock()
|
|
mock_generate_environment.return_value = fake_env
|
|
undercloud.install('.')
|
|
self.assertTrue(mock_validate_configuration.called)
|
|
mock_generate_environment.assert_called_with('.')
|
|
mock_run_instack.assert_called_with(fake_env)
|
|
mock_run_orc.assert_called_with(fake_env)
|
|
mock_run_command.assert_called_with(
|
|
['sudo', 'rm', '-f', '/tmp/svc-map-services'], None, 'rm')
|
|
mock_upgrade_fact.assert_called_with(False)
|
|
mock_die_tuskar_die.assert_not_called()
|
|
mock_run_validation_groups.assert_not_called()
|
|
|
|
@mock.patch('instack_undercloud.undercloud._handle_upgrade_fact')
|
|
@mock.patch('instack_undercloud.undercloud._configure_logging')
|
|
@mock.patch('instack_undercloud.undercloud._validate_configuration')
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
@mock.patch('instack_undercloud.undercloud._post_config')
|
|
@mock.patch('instack_undercloud.undercloud._run_orc')
|
|
@mock.patch('instack_undercloud.undercloud._run_yum_update')
|
|
@mock.patch('instack_undercloud.undercloud._run_yum_clean_all')
|
|
@mock.patch('instack_undercloud.undercloud._run_instack')
|
|
@mock.patch('instack_undercloud.undercloud._generate_environment')
|
|
@mock.patch('instack_undercloud.undercloud._load_config')
|
|
@mock.patch('instack_undercloud.undercloud._die_tuskar_die')
|
|
@mock.patch('instack_undercloud.undercloud._run_validation_groups')
|
|
def test_install_upgrade(self, mock_run_validation_groups,
|
|
mock_die_tuskar_die, mock_load_config,
|
|
mock_generate_environment, mock_run_instack,
|
|
mock_run_yum_clean_all, mock_run_yum_update,
|
|
mock_run_orc, mock_post_config, mock_run_command,
|
|
mock_validate_configuration,
|
|
mock_configure_logging, mock_upgrade_fact):
|
|
fake_env = mock.MagicMock()
|
|
mock_generate_environment.return_value = fake_env
|
|
undercloud.install('.', upgrade=True)
|
|
self.assertTrue(mock_validate_configuration.called)
|
|
mock_generate_environment.assert_called_with('.')
|
|
mock_run_instack.assert_called_with(fake_env)
|
|
mock_run_orc.assert_called_with(fake_env)
|
|
mock_run_command.assert_called_with(
|
|
['sudo', 'rm', '-f', '/tmp/svc-map-services'], None, 'rm')
|
|
mock_upgrade_fact.assert_called_with(True)
|
|
mock_die_tuskar_die.assert_called_once()
|
|
mock_run_validation_groups.assert_called_once()
|
|
|
|
@mock.patch('instack_undercloud.undercloud._configure_logging')
|
|
def test_install_exception(self, mock_configure_logging):
|
|
mock_configure_logging.side_effect = RuntimeError('foo')
|
|
self.assertRaises(RuntimeError, undercloud.install, '.')
|
|
log_dict = {'undercloud_operation': "install",
|
|
'exception': 'foo',
|
|
'log_file': undercloud.PATHS.LOG_FILE
|
|
}
|
|
self.assertIn(undercloud.FAILURE_MESSAGE % log_dict,
|
|
self.logger.output)
|
|
|
|
@mock.patch('sys.exit')
|
|
@mock.patch('instack_undercloud.undercloud._configure_logging')
|
|
def test_install_exception_no_debug(self, mock_configure_logging,
|
|
mock_exit):
|
|
mock_configure_logging.side_effect = RuntimeError('foo')
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(undercloud_debug=False)
|
|
undercloud.install('.')
|
|
log_dict = {'undercloud_operation': "install",
|
|
'exception': 'foo',
|
|
'log_file': undercloud.PATHS.LOG_FILE
|
|
}
|
|
self.assertIn(undercloud.FAILURE_MESSAGE % log_dict,
|
|
self.logger.output)
|
|
mock_exit.assert_called_with(1)
|
|
|
|
def test_generate_password(self):
|
|
first = undercloud._generate_password()
|
|
second = undercloud._generate_password()
|
|
self.assertNotEqual(first, second)
|
|
|
|
def test_extract_from_stackrc(self):
|
|
with open(os.path.expanduser('~/stackrc'), 'w') as f:
|
|
f.write('OS_USERNAME=aturing\n')
|
|
f.write('OS_AUTH_URL=http://bletchley:5000/\n')
|
|
self.assertEqual('aturing',
|
|
undercloud._extract_from_stackrc('OS_USERNAME'))
|
|
self.assertEqual('http://bletchley:5000/',
|
|
undercloud._extract_from_stackrc('OS_AUTH_URL'))
|
|
|
|
@mock.patch('instack_undercloud.undercloud._check_hostname')
|
|
@mock.patch('instack_undercloud.undercloud._check_memory')
|
|
@mock.patch('instack_undercloud.undercloud._check_sysctl')
|
|
@mock.patch('instack_undercloud.undercloud._validate_network')
|
|
@mock.patch('instack_undercloud.undercloud._validate_no_ip_change')
|
|
@mock.patch('instack_undercloud.undercloud._validate_passwords_file')
|
|
def test_validate_configuration(self, mock_vpf, mock_vnic,
|
|
mock_validate_network,
|
|
mock_check_memory, mock_check_hostname,
|
|
mock_check_sysctl):
|
|
undercloud._validate_configuration()
|
|
self.assertTrue(mock_vpf.called)
|
|
self.assertTrue(mock_vnic.called)
|
|
self.assertTrue(mock_validate_network.called)
|
|
self.assertTrue(mock_check_memory.called)
|
|
self.assertTrue(mock_check_hostname.called)
|
|
self.assertTrue(mock_check_sysctl.called)
|
|
|
|
|
|
class TestCheckHostname(BaseTestCase):
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_correct(self, mock_run_command):
|
|
mock_run_command.side_effect = ['test-hostname', 'test-hostname']
|
|
self.useFixture(fixtures.EnvironmentVariable('HOSTNAME',
|
|
'test-hostname'))
|
|
fake_hosts = io.StringIO(u'127.0.0.1 test-hostname\n')
|
|
with mock.patch('instack_undercloud.undercloud.open',
|
|
return_value=fake_hosts, create=True):
|
|
undercloud._check_hostname()
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_static_transient_mismatch(self, mock_run_command):
|
|
mock_run_command.side_effect = ['test-hostname', 'other-hostname']
|
|
self.useFixture(fixtures.EnvironmentVariable('HOSTNAME',
|
|
'test-hostname'))
|
|
fake_hosts = io.StringIO(u'127.0.0.1 test-hostname\n')
|
|
with mock.patch('instack_undercloud.undercloud.open',
|
|
return_value=fake_hosts, create=True):
|
|
self.assertRaises(RuntimeError, undercloud._check_hostname)
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_no_substring_match(self, mock_run_command):
|
|
mock_run_command.side_effect = ['test.hostname', 'test.hostname',
|
|
None]
|
|
self.useFixture(fixtures.EnvironmentVariable('HOSTNAME',
|
|
'test.hostname'))
|
|
fake_hosts = io.StringIO(u'127.0.0.1 test-hostname-bad\n')
|
|
with mock.patch('instack_undercloud.undercloud.open',
|
|
return_value=fake_hosts, create=True):
|
|
undercloud._check_hostname()
|
|
mock_run_command.assert_called_with([
|
|
'sudo', '/bin/bash', '-c',
|
|
'sed -i "s/127.0.0.1\(\s*\)/127.0.0.1\\1test.hostname test /" '
|
|
'/etc/hosts'],
|
|
name='hostname-to-etc-hosts')
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_commented(self, mock_run_command):
|
|
mock_run_command.side_effect = ['test.hostname', 'test.hostname',
|
|
None]
|
|
self.useFixture(fixtures.EnvironmentVariable('HOSTNAME',
|
|
'test.hostname'))
|
|
fake_hosts = io.StringIO(u""" #127.0.0.1 test.hostname\n
|
|
127.0.0.1 other-hostname\n""")
|
|
with mock.patch('instack_undercloud.undercloud.open',
|
|
return_value=fake_hosts, create=True):
|
|
undercloud._check_hostname()
|
|
mock_run_command.assert_called_with([
|
|
'sudo', '/bin/bash', '-c',
|
|
'sed -i "s/127.0.0.1\(\s*\)/127.0.0.1\\1test.hostname test /" '
|
|
'/etc/hosts'],
|
|
name='hostname-to-etc-hosts')
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_set_fqdn(self, mock_run_command):
|
|
mock_run_command.side_effect = [None,
|
|
'test-hostname.domain',
|
|
'test-hostname.domain',
|
|
None]
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(undercloud_hostname='test-hostname.domain')
|
|
fake_hosts = io.StringIO(u'127.0.0.1 other-hostname\n')
|
|
with mock.patch('instack_undercloud.undercloud.open',
|
|
return_value=fake_hosts, create=True):
|
|
undercloud._check_hostname()
|
|
mock_run_command.assert_called_with([
|
|
'sudo', '/bin/bash', '-c',
|
|
'sed -i "s/127.0.0.1\(\s*\)/'
|
|
'127.0.0.1\\1test-hostname.domain test-hostname /" '
|
|
'/etc/hosts'],
|
|
name='hostname-to-etc-hosts')
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_set_not_fq(self, mock_run_command):
|
|
mock_run_command.side_effect = [None,
|
|
'test-hostname',
|
|
'test-hostname',
|
|
None]
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(undercloud_hostname='test-hostname')
|
|
self.assertRaises(RuntimeError, undercloud._check_hostname)
|
|
|
|
|
|
class TestCheckMemory(BaseTestCase):
|
|
@mock.patch('psutil.swap_memory')
|
|
@mock.patch('psutil.virtual_memory')
|
|
def test_sufficient_memory(self, mock_vm, mock_sm):
|
|
mock_vm.return_value = mock.Mock()
|
|
mock_vm.return_value.total = 8589934592
|
|
mock_sm.return_value = mock.Mock()
|
|
mock_sm.return_value.total = 0
|
|
undercloud._check_memory()
|
|
|
|
@mock.patch('psutil.swap_memory')
|
|
@mock.patch('psutil.virtual_memory')
|
|
def test_insufficient_memory(self, mock_vm, mock_sm):
|
|
mock_vm.return_value = mock.Mock()
|
|
mock_vm.return_value.total = 2071963648
|
|
mock_sm.return_value = mock.Mock()
|
|
mock_sm.return_value.total = 0
|
|
self.assertRaises(RuntimeError, undercloud._check_memory)
|
|
|
|
@mock.patch('psutil.swap_memory')
|
|
@mock.patch('psutil.virtual_memory')
|
|
def test_sufficient_swap(self, mock_vm, mock_sm):
|
|
mock_vm.return_value = mock.Mock()
|
|
mock_vm.return_value.total = 6442450944
|
|
mock_sm.return_value = mock.Mock()
|
|
mock_sm.return_value.total = 2147483648
|
|
undercloud._check_memory()
|
|
|
|
|
|
class TestCheckSysctl(BaseTestCase):
|
|
@mock.patch('os.path.isfile')
|
|
def test_missing_options(self, mock_isfile):
|
|
mock_isfile.return_value = False
|
|
self.assertRaises(RuntimeError, undercloud._check_sysctl)
|
|
|
|
@mock.patch('os.path.isfile')
|
|
def test_available_option(self, mock_isfile):
|
|
mock_isfile.return_value = True
|
|
undercloud._check_sysctl()
|
|
|
|
|
|
class TestNoIPChange(BaseTestCase):
|
|
@mock.patch('os.path.isfile', return_value=False)
|
|
def test_new_install(self, mock_isfile):
|
|
undercloud._validate_no_ip_change()
|
|
|
|
@mock.patch('instack_undercloud.undercloud.open')
|
|
@mock.patch('json.loads')
|
|
@mock.patch('os.path.isfile', return_value=True)
|
|
def test_update_matches(self, mock_isfile, mock_loads, mock_open):
|
|
mock_members = [{'name': 'eth0'},
|
|
{'name': 'br-ctlplane',
|
|
'addresses': [{'ip_netmask': '192.168.24.1/24'}]
|
|
}
|
|
]
|
|
mock_config = {'network_config': mock_members}
|
|
mock_loads.return_value = mock_config
|
|
undercloud._validate_no_ip_change()
|
|
|
|
@mock.patch('instack_undercloud.undercloud.open')
|
|
@mock.patch('json.loads')
|
|
@mock.patch('os.path.isfile', return_value=True)
|
|
def test_update_mismatch(self, mock_isfile, mock_loads, mock_open):
|
|
mock_members = [{'name': 'eth0'},
|
|
{'name': 'br-ctlplane',
|
|
'addresses': [{'ip_netmask': '192.168.0.1/24'}]
|
|
}
|
|
]
|
|
mock_config = {'network_config': mock_members}
|
|
mock_loads.return_value = mock_config
|
|
self.assertRaises(validator.FailedValidation,
|
|
undercloud._validate_no_ip_change)
|
|
|
|
@mock.patch('instack_undercloud.undercloud.open')
|
|
@mock.patch('json.loads')
|
|
@mock.patch('os.path.isfile', return_value=True)
|
|
def test_update_no_network(self, mock_isfile, mock_loads, mock_open):
|
|
mock_members = [{'name': 'eth0'}]
|
|
mock_config = {'network_config': mock_members}
|
|
mock_loads.return_value = mock_config
|
|
undercloud._validate_no_ip_change()
|
|
|
|
|
|
@mock.patch('os.path.isfile')
|
|
class TestPasswordsFileExists(BaseTestCase):
|
|
def test_new_install(self, mock_isfile):
|
|
mock_isfile.side_effect = [False]
|
|
undercloud._validate_passwords_file()
|
|
|
|
def test_update_exists(self, mock_isfile):
|
|
mock_isfile.side_effect = [True, True]
|
|
undercloud._validate_passwords_file()
|
|
|
|
def test_update_missing(self, mock_isfile):
|
|
mock_isfile.side_effect = [True, False]
|
|
self.assertRaises(validator.FailedValidation,
|
|
undercloud._validate_passwords_file)
|
|
|
|
|
|
class TestGenerateEnvironment(BaseTestCase):
|
|
def setUp(self):
|
|
super(TestGenerateEnvironment, self).setUp()
|
|
# Things that need to always be mocked out, but that the tests
|
|
# don't want to care about.
|
|
self.useFixture(fixtures.MockPatch(
|
|
'instack_undercloud.undercloud._write_password_file'))
|
|
self.useFixture(fixtures.MockPatch(
|
|
'instack_undercloud.undercloud._load_config'))
|
|
mock_isdir = fixtures.MockPatch('os.path.isdir')
|
|
self.useFixture(mock_isdir)
|
|
mock_isdir.mock.return_value = False
|
|
# Some tests do care about this, but they can override the default
|
|
# return value, and then the tests that don't care can ignore it.
|
|
self.mock_distro = fixtures.MockPatch('platform.linux_distribution')
|
|
self.useFixture(self.mock_distro)
|
|
self.mock_distro.mock.return_value = [
|
|
'Red Hat Enterprise Linux Server 7.1']
|
|
|
|
@mock.patch('socket.gethostname')
|
|
def test_hostname_set(self, mock_gethostname):
|
|
fake_hostname = 'crazy-test-hostname-!@#$%12345'
|
|
mock_gethostname.return_value = fake_hostname
|
|
env = undercloud._generate_environment('.')
|
|
self.assertEqual(fake_hostname, env['HOSTNAME'])
|
|
|
|
def test_elements_path_input(self):
|
|
test_path = '/test/elements/path'
|
|
self.useFixture(fixtures.EnvironmentVariable('ELEMENTS_PATH',
|
|
test_path))
|
|
env = undercloud._generate_environment('.')
|
|
self.assertEqual(test_path, env['ELEMENTS_PATH'])
|
|
|
|
def test_default_elements_path(self):
|
|
env = undercloud._generate_environment('.')
|
|
test_path = ('%s:%s:/usr/share/tripleo-image-elements:'
|
|
'/usr/share/diskimage-builder/elements' %
|
|
(os.path.join(os.getcwd(), 'tripleo-puppet-elements',
|
|
'elements'),
|
|
'./elements'))
|
|
self.assertEqual(test_path, env['ELEMENTS_PATH'])
|
|
|
|
def test_rhel7_distro(self):
|
|
self.useFixture(fixtures.EnvironmentVariable('NODE_DIST', None))
|
|
env = undercloud._generate_environment('.')
|
|
self.assertEqual('rhel7', env['NODE_DIST'])
|
|
self.assertEqual('./json-files/rhel-7-undercloud-packages.json',
|
|
env['JSONFILE'])
|
|
self.assertEqual('disable', env['REG_METHOD'])
|
|
self.assertEqual('1', env['REG_HALT_UNREGISTER'])
|
|
|
|
def test_centos7_distro(self):
|
|
self.useFixture(fixtures.EnvironmentVariable('NODE_DIST', None))
|
|
self.mock_distro.mock.return_value = ['CentOS Linux release 7.1']
|
|
env = undercloud._generate_environment('.')
|
|
self.assertEqual('centos7', env['NODE_DIST'])
|
|
self.assertEqual('./json-files/centos-7-undercloud-packages.json',
|
|
env['JSONFILE'])
|
|
|
|
def test_fedora_distro(self):
|
|
self.useFixture(fixtures.EnvironmentVariable('NODE_DIST', None))
|
|
self.mock_distro.mock.return_value = ['Fedora Infinity + 1']
|
|
self.assertRaises(RuntimeError, undercloud._generate_environment, '.')
|
|
|
|
def test_other_distro(self):
|
|
self.useFixture(fixtures.EnvironmentVariable('NODE_DIST', None))
|
|
self.mock_distro.mock.return_value = ['Gentoo']
|
|
self.assertRaises(RuntimeError, undercloud._generate_environment, '.')
|
|
|
|
def test_opts_in_env(self):
|
|
env = undercloud._generate_environment('.')
|
|
# Just spot check, we don't want to replicate the entire opt list here
|
|
self.assertEqual(env['INSPECTION_COLLECTORS'],
|
|
'default,extra-hardware,numa-topology,logs')
|
|
self.assertEqual('192.168.24.1/24', env['PUBLIC_INTERFACE_IP'])
|
|
self.assertEqual('192.168.24.1', env['LOCAL_IP'])
|
|
# The list is generated from a set, so we can't rely on ordering.
|
|
# Instead make sure that it looks like a valid list by parsing it.
|
|
drivers = json.loads(env['ENABLED_DRIVERS'])
|
|
self.assertEqual(sorted(drivers), ['pxe_drac', 'pxe_ilo',
|
|
'pxe_ipmitool'])
|
|
self.assertEqual(env['INSPECTION_NODE_NOT_FOUND_HOOK'], '')
|
|
|
|
def test_enabled_discovery(self):
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(enable_node_discovery=True,
|
|
discovery_default_driver='foobar')
|
|
env = undercloud._generate_environment('.')
|
|
# The list is generated from a set, so we can't rely on ordering.
|
|
# Instead make sure that it looks like a valid list by parsing it.
|
|
drivers = json.loads(env['ENABLED_DRIVERS'])
|
|
# Discovery requires enabling the default driver
|
|
self.assertEqual(sorted(drivers), ['foobar', 'pxe_drac', 'pxe_ilo',
|
|
'pxe_ipmitool'])
|
|
self.assertEqual(env['INSPECTION_NODE_NOT_FOUND_HOOK'], 'enroll')
|
|
|
|
def test_enabled_hardware_types(self):
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(enable_node_discovery=True,
|
|
discovery_default_driver='foobar',
|
|
enabled_hardware_types=['ipmi', 'foobar'])
|
|
env = undercloud._generate_environment('.')
|
|
# The list is generated from a set, so we can't rely on ordering.
|
|
# Instead make sure that it looks like a valid list by parsing it.
|
|
drivers = json.loads(env['ENABLED_DRIVERS'])
|
|
hw_types = json.loads(env['ENABLED_HARDWARE_TYPES'])
|
|
# The driver is already in hardware types, so we don't try adding it to
|
|
# the driver list.
|
|
self.assertEqual(sorted(drivers), ['pxe_drac', 'pxe_ilo',
|
|
'pxe_ipmitool'])
|
|
self.assertEqual(sorted(hw_types), ['foobar', 'ipmi'])
|
|
|
|
def test_docker_registry_mirror(self):
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(docker_registry_mirror='http://foo/bar')
|
|
env = undercloud._generate_environment('.')
|
|
# Spot check one service
|
|
self.assertEqual('http://foo/bar',
|
|
env['DOCKER_REGISTRY_MIRROR'])
|
|
|
|
def test_generate_endpoints(self):
|
|
env = undercloud._generate_environment('.')
|
|
endpoint_vars = {k: v for (k, v) in env.items()
|
|
if k.startswith('UNDERCLOUD_ENDPOINT')}
|
|
self.assertEqual(93, len(endpoint_vars))
|
|
# Spot check one service
|
|
self.assertEqual('http://192.168.24.1:5000',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_PUBLIC'])
|
|
self.assertEqual('http://192.168.24.1:5000',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_INTERNAL'])
|
|
self.assertEqual('http://192.168.24.1:35357',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_ADMIN'])
|
|
# Also check that the tenant id part is preserved
|
|
self.assertEqual('http://192.168.24.1:8080/v1/AUTH_%(tenant_id)s',
|
|
env['UNDERCLOUD_ENDPOINT_SWIFT_PUBLIC'])
|
|
|
|
def test_generate_endpoints_ssl_manual(self):
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(undercloud_service_certificate='test.pem')
|
|
env = undercloud._generate_environment('.')
|
|
# Spot check one service
|
|
self.assertEqual('https://192.168.24.2:13000',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_PUBLIC'])
|
|
self.assertEqual('http://192.168.24.3:5000',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_INTERNAL'])
|
|
self.assertEqual('http://192.168.24.3:35357',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_ADMIN'])
|
|
self.assertEqual('https://192.168.24.2:443/keystone/v3',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_UI_CONFIG_PUBLIC'])
|
|
# Also check that the tenant id part is preserved
|
|
self.assertEqual('https://192.168.24.2:13808/v1/AUTH_%(tenant_id)s',
|
|
env['UNDERCLOUD_ENDPOINT_SWIFT_PUBLIC'])
|
|
|
|
def test_generate_endpoints_ssl_auto(self):
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(generate_service_certificate=True)
|
|
env = undercloud._generate_environment('.')
|
|
# Spot check one service
|
|
self.assertEqual('https://192.168.24.2:13000',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_PUBLIC'])
|
|
self.assertEqual('http://192.168.24.3:5000',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_INTERNAL'])
|
|
self.assertEqual('http://192.168.24.3:35357',
|
|
env['UNDERCLOUD_ENDPOINT_KEYSTONE_ADMIN'])
|
|
# Also check that the tenant id part is preserved
|
|
self.assertEqual('https://192.168.24.2:13808/v1/AUTH_%(tenant_id)s',
|
|
env['UNDERCLOUD_ENDPOINT_SWIFT_PUBLIC'])
|
|
|
|
def test_absolute_cert_path(self):
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(undercloud_service_certificate='/home/stack/test.pem')
|
|
env = undercloud._generate_environment('.')
|
|
self.assertEqual('/home/stack/test.pem',
|
|
env['UNDERCLOUD_SERVICE_CERTIFICATE'])
|
|
|
|
def test_relative_cert_path(self):
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(undercloud_service_certificate='test.pem')
|
|
env = undercloud._generate_environment('.')
|
|
self.assertEqual(os.path.join(os.getcwd(), 'test.pem'),
|
|
env['UNDERCLOUD_SERVICE_CERTIFICATE'])
|
|
|
|
def test_no_cert_path(self):
|
|
env = undercloud._generate_environment('.')
|
|
self.assertEqual('', env['UNDERCLOUD_SERVICE_CERTIFICATE'])
|
|
|
|
def test_remove_dib_yum_repo_conf(self):
|
|
self.useFixture(fixtures.EnvironmentVariable('DIB_YUM_REPO_CONF',
|
|
'rum_yepo.conf'))
|
|
env = undercloud._generate_environment('.')
|
|
self.assertNotIn(env, 'DIB_YUM_REPO_CONF')
|
|
|
|
|
|
class TestWritePasswordFile(BaseTestCase):
|
|
def test_normal(self):
|
|
instack_env = {}
|
|
undercloud._write_password_file(instack_env)
|
|
test_parser = configparser.ConfigParser()
|
|
test_parser.read(undercloud.PATHS.PASSWORD_PATH)
|
|
self.assertTrue(test_parser.has_option('auth',
|
|
'undercloud_db_password'))
|
|
self.assertIn('UNDERCLOUD_DB_PASSWORD', instack_env)
|
|
self.assertEqual(32,
|
|
len(instack_env['UNDERCLOUD_HEAT_ENCRYPTION_KEY']))
|
|
|
|
def test_value_set(self):
|
|
instack_env = {}
|
|
conf = config_fixture.Config()
|
|
self.useFixture(conf)
|
|
conf.config(undercloud_db_password='test', group='auth')
|
|
undercloud._write_password_file(instack_env)
|
|
test_parser = configparser.ConfigParser()
|
|
test_parser.read(undercloud.PATHS.PASSWORD_PATH)
|
|
self.assertEqual(test_parser.get('auth', 'undercloud_db_password'),
|
|
'test')
|
|
self.assertEqual(instack_env['UNDERCLOUD_DB_PASSWORD'], 'test')
|
|
|
|
|
|
class TestRunCommand(BaseTestCase):
|
|
def test_run_command(self):
|
|
output = undercloud._run_command(['echo', 'foo'])
|
|
self.assertEqual('foo\n', output)
|
|
|
|
def test_run_live_command(self):
|
|
undercloud._run_live_command(['echo', 'bar'])
|
|
self.assertIn('bar\n', self.logger.output)
|
|
|
|
@mock.patch('subprocess.check_output')
|
|
def test_run_command_fails(self, mock_check_output):
|
|
fake_exc = subprocess.CalledProcessError(1, 'nothing', 'fake failure')
|
|
mock_check_output.side_effect = fake_exc
|
|
self.assertRaises(subprocess.CalledProcessError,
|
|
undercloud._run_command, ['nothing'])
|
|
self.assertIn('nothing failed', self.logger.output)
|
|
self.assertIn('fake failure', self.logger.output)
|
|
|
|
@mock.patch('subprocess.check_output')
|
|
def test_run_command_fails_with_name(self, mock_check_output):
|
|
fake_exc = subprocess.CalledProcessError(1, 'nothing', 'fake failure')
|
|
mock_check_output.side_effect = fake_exc
|
|
self.assertRaises(subprocess.CalledProcessError,
|
|
undercloud._run_command, ['nothing'],
|
|
name='fake_name')
|
|
self.assertIn('fake_name failed', self.logger.output)
|
|
self.assertIn('fake failure', self.logger.output)
|
|
|
|
def test_run_live_command_fails(self):
|
|
exc = self.assertRaises(RuntimeError, undercloud._run_live_command,
|
|
['ls', '/nonexistent/path'])
|
|
self.assertIn('ls failed', str(exc))
|
|
self.assertIn('ls', self.logger.output)
|
|
|
|
def test_run_live_command_fails_name(self):
|
|
exc = self.assertRaises(RuntimeError, undercloud._run_live_command,
|
|
['ls', '/nonexistent/path'],
|
|
name='fake_name')
|
|
self.assertIn('fake_name failed', str(exc))
|
|
|
|
def test_run_command_env(self):
|
|
env = {'FOO': 'foo'}
|
|
output = undercloud._run_command(['env'], env)
|
|
self.assertIn('FOO=foo', output)
|
|
|
|
def test_run_live_command_env(self):
|
|
env = {'BAR': 'bar'}
|
|
undercloud._run_live_command(['env'], env)
|
|
self.assertIn('BAR=bar', self.logger.output)
|
|
|
|
|
|
class TestRunTools(base.BaseTestCase):
|
|
@mock.patch('instack_undercloud.undercloud._run_live_command')
|
|
def test_run_instack(self, mock_run):
|
|
instack_env = {'ELEMENTS_PATH': '.', 'JSONFILE': 'file.json'}
|
|
args = ['sudo', '-E', 'instack', '-p', '.', '-j', 'file.json']
|
|
undercloud._run_instack(instack_env)
|
|
mock_run.assert_called_with(args, instack_env, 'instack')
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_live_command')
|
|
def test_run_os_refresh_config(self, mock_run):
|
|
instack_env = {}
|
|
args = ['sudo', 'os-refresh-config']
|
|
undercloud._run_orc(instack_env)
|
|
mock_run.assert_called_with(args, instack_env, 'os-refresh-config')
|
|
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
class TestConfigureSshKeys(base.BaseTestCase):
|
|
def test_ensure_user_identity(self, mock_run):
|
|
id_path = os.path.expanduser('~/.ssh/id_rsa')
|
|
undercloud._ensure_user_identity(id_path)
|
|
mock_run.assert_called_with(['ssh-keygen', '-t', 'rsa', '-N', '',
|
|
'-f', id_path])
|
|
|
|
def _create_test_id(self):
|
|
id_path = os.path.expanduser('~/.ssh/id_rsa')
|
|
os.makedirs(os.path.expanduser('~/.ssh'))
|
|
with open(id_path, 'w') as id_rsa:
|
|
id_rsa.write('test private\n')
|
|
with open(id_path + '.pub', 'w') as id_pub:
|
|
id_pub.write('test public\n')
|
|
return id_path
|
|
|
|
def test_ensure_user_identity_exists(self, mock_run):
|
|
id_path = self._create_test_id()
|
|
undercloud._ensure_user_identity(id_path)
|
|
self.assertFalse(mock_run.called)
|
|
|
|
def _test_configure_ssh_keys(self, mock_eui, exists=True):
|
|
id_path = self._create_test_id()
|
|
mock_client_instance = mock.Mock()
|
|
if not exists:
|
|
get = mock_client_instance.keypairs.get
|
|
get.side_effect = exceptions.NotFound('test')
|
|
undercloud._configure_ssh_keys(mock_client_instance)
|
|
mock_eui.assert_called_with(id_path)
|
|
mock_client_instance.keypairs.get.assert_called_with('default')
|
|
if not exists:
|
|
mock_client_instance.keypairs.create.assert_called_with(
|
|
'default', 'test public')
|
|
|
|
@mock.patch('instack_undercloud.undercloud._ensure_user_identity')
|
|
def test_configure_ssh_keys_exists(self, mock_eui, _):
|
|
self._test_configure_ssh_keys(mock_eui)
|
|
|
|
@mock.patch('instack_undercloud.undercloud._ensure_user_identity')
|
|
def test_configure_ssh_keys_missing(self, mock_eui, _):
|
|
self._test_configure_ssh_keys(mock_eui, False)
|
|
|
|
|
|
class TestPostConfig(base.BaseTestCase):
|
|
@mock.patch('os_client_config.make_client')
|
|
@mock.patch('instack_undercloud.undercloud._migrate_to_convergence')
|
|
@mock.patch('instack_undercloud.undercloud._ensure_node_resource_classes')
|
|
@mock.patch('instack_undercloud.undercloud._member_role_exists')
|
|
@mock.patch('instack_undercloud.undercloud._get_session')
|
|
@mock.patch('ironicclient.client.get_client', autospec=True)
|
|
@mock.patch('novaclient.client.Client', autospec=True)
|
|
@mock.patch('swiftclient.client.Connection', autospec=True)
|
|
@mock.patch('mistralclient.api.client.client', autospec=True)
|
|
@mock.patch('instack_undercloud.undercloud._delete_default_flavors')
|
|
@mock.patch('instack_undercloud.undercloud._copy_stackrc')
|
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
|
@mock.patch('instack_undercloud.undercloud._configure_ssh_keys')
|
|
@mock.patch('instack_undercloud.undercloud._ensure_flavor')
|
|
@mock.patch('instack_undercloud.undercloud._post_config_mistral')
|
|
def test_post_config(self, mock_post_config_mistral, mock_ensure_flavor,
|
|
mock_configure_ssh_keys, mock_get_auth_values,
|
|
mock_copy_stackrc, mock_delete, mock_mistral_client,
|
|
mock_swift_client, mock_nova_client, mock_ir_client,
|
|
mock_get_session, mock_member_role_exists,
|
|
mock_resource_classes, mock_migrate_to_convergence,
|
|
mock_make_client):
|
|
instack_env = {
|
|
'UNDERCLOUD_ENDPOINT_MISTRAL_PUBLIC':
|
|
'http://192.168.24.1:8989/v2',
|
|
}
|
|
mock_get_auth_values.return_value = ('aturing', '3nigma', 'hut8',
|
|
'http://bletchley:5000/')
|
|
mock_instance_nova = mock.Mock()
|
|
mock_nova_client.return_value = mock_instance_nova
|
|
mock_get_session.return_value = mock.MagicMock()
|
|
mock_instance_swift = mock.Mock()
|
|
mock_swift_client.return_value = mock_instance_swift
|
|
mock_instance_mistral = mock.Mock()
|
|
mock_mistral_client.return_value = mock_instance_mistral
|
|
mock_instance_ironic = mock_ir_client.return_value
|
|
flavors = [mock.Mock(spec=['name']),
|
|
mock.Mock(spec=['name'])]
|
|
# The mock library treats "name" attribute differently, and we cannot
|
|
# pass it through __init__
|
|
flavors[0].name = 'baremetal'
|
|
flavors[1].name = 'ceph-storage'
|
|
mock_instance_nova.flavors.list.return_value = flavors
|
|
mock_heat = mock.Mock()
|
|
mock_make_client.return_value = mock_heat
|
|
|
|
undercloud._post_config(instack_env, True)
|
|
mock_nova_client.assert_called_with(
|
|
2, session=mock_get_session.return_value)
|
|
self.assertTrue(mock_copy_stackrc.called)
|
|
mock_configure_ssh_keys.assert_called_with(mock_instance_nova)
|
|
calls = [mock.call(mock_instance_nova, flavors[0], 'baremetal', None),
|
|
mock.call(mock_instance_nova, None, 'control', 'control'),
|
|
mock.call(mock_instance_nova, None, 'compute', 'compute'),
|
|
mock.call(mock_instance_nova, flavors[1],
|
|
'ceph-storage', 'ceph-storage'),
|
|
mock.call(mock_instance_nova, None,
|
|
'block-storage', 'block-storage'),
|
|
mock.call(mock_instance_nova, None,
|
|
'swift-storage', 'swift-storage'),
|
|
]
|
|
mock_ensure_flavor.assert_has_calls(calls)
|
|
mock_resource_classes.assert_called_once_with(mock_instance_ironic)
|
|
mock_post_config_mistral.assert_called_once_with(
|
|
instack_env, mock_instance_mistral, mock_instance_swift)
|
|
mock_migrate_to_convergence.assert_called_once_with(mock_heat)
|
|
|
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
|
@mock.patch('instack_undercloud.undercloud._get_session')
|
|
@mock.patch('mistralclient.api.client.client', autospec=True)
|
|
def test_run_validation_groups_success(self, mock_mistral_client,
|
|
mock_get_session,
|
|
mock_auth_values):
|
|
mock_mistral = mock.Mock()
|
|
mock_mistral_client.return_value = mock_mistral
|
|
mock_mistral.environments.list.return_value = []
|
|
mock_mistral.executions.get.return_value = mock.Mock(state="SUCCESS")
|
|
mock_get_session.return_value = mock.MagicMock()
|
|
undercloud._run_validation_groups(["post-upgrade"])
|
|
mock_mistral.executions.create.assert_called_once_with(
|
|
'tripleo.validations.v1.run_groups',
|
|
workflow_input={
|
|
'group_names': ['post-upgrade'],
|
|
}
|
|
)
|
|
|
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
|
@mock.patch('instack_undercloud.undercloud._get_session')
|
|
@mock.patch('mistralclient.api.client.client', autospec=True)
|
|
@mock.patch('time.strptime')
|
|
def test_run_validation_groups_fail(self, mock_strptime,
|
|
mock_mistral_client, mock_get_session,
|
|
mock_auth_values):
|
|
mock_mistral = mock.Mock()
|
|
mock_mistral_client.return_value = mock_mistral
|
|
mock_mistral.environments.list.return_value = []
|
|
mock_mistral.executions.get.return_value = mock.Mock(state="FAIL")
|
|
mock_mistral.executions.get_output.return_value = "ERROR!"
|
|
mock_mistral.executions.get.id = "1234"
|
|
mock_mistral.action_executions.list.return_value = []
|
|
mock_strptime.return_value = time.mktime(time.localtime())
|
|
mock_get_session.return_value = mock.MagicMock()
|
|
self.assertRaises(
|
|
RuntimeError, undercloud._run_validation_groups, ["post-upgrade"],
|
|
"", 360, True)
|
|
|
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
|
@mock.patch('instack_undercloud.undercloud._get_session')
|
|
@mock.patch('mistralclient.api.client.client', autospec=True)
|
|
@mock.patch('time.strptime')
|
|
def test_run_validation_groups_timeout(self, mock_strptime,
|
|
mock_mistral_client,
|
|
mock_get_session, mock_auth_values):
|
|
mock_mistral = mock.Mock()
|
|
mock_mistral_client.return_value = mock_mistral
|
|
mock_mistral.environments.list.return_value = []
|
|
mock_mistral.executions.get.id = "1234"
|
|
mock_mistral.action_executions.list.return_value = []
|
|
mock_get_session.return_value = mock.MagicMock()
|
|
mock_time = mock.MagicMock()
|
|
mock_time.return_value = time.mktime(time.localtime())
|
|
mock_strptime.return_value = time.mktime(time.localtime())
|
|
with mock.patch('time.time', mock_time):
|
|
self.assertRaisesRegexp(RuntimeError, ("TIMEOUT waiting for "
|
|
"execution"),
|
|
undercloud._run_validation_groups,
|
|
["post-upgrade"], "", -1, True)
|
|
|
|
def test_create_default_plan(self):
|
|
mock_mistral = mock.Mock()
|
|
mock_mistral.environments.list.return_value = []
|
|
mock_mistral.executions.get.return_value = mock.Mock(state="SUCCESS")
|
|
|
|
undercloud._create_default_plan(mock_mistral, [])
|
|
mock_mistral.executions.create.assert_called_once_with(
|
|
'tripleo.plan_management.v1.create_default_deployment_plan',
|
|
workflow_input={
|
|
'container': 'overcloud',
|
|
'queue_name': mock.ANY
|
|
}
|
|
)
|
|
|
|
def test_create_default_plan_existing(self):
|
|
mock_mistral = mock.Mock()
|
|
undercloud._create_default_plan(mock_mistral, ['overcloud'])
|
|
mock_mistral.executions.create.assert_not_called()
|
|
|
|
def test_create_config_environment(self):
|
|
mock_mistral = mock.Mock()
|
|
mock_mistral.environments.get.side_effect = (
|
|
ks_exceptions.NotFound)
|
|
|
|
env = {
|
|
"UNDERCLOUD_CEILOMETER_SNMPD_PASSWORD": "snmpd-pass"
|
|
}
|
|
json_string = '{"undercloud_ceilometer_snmpd_password": "snmpd-pass"}'
|
|
|
|
undercloud._create_mistral_config_environment(env, mock_mistral)
|
|
|
|
mock_mistral.environments.create.assert_called_once_with(
|
|
name="tripleo.undercloud-config",
|
|
variables=json_string)
|
|
|
|
def test_create_config_environment_existing(self):
|
|
mock_mistral = mock.Mock()
|
|
environment = collections.namedtuple('environment', ['name'])
|
|
mock_mistral.environments.get.return_value = environment(
|
|
name='overcloud')
|
|
env = {
|
|
"UNDERCLOUD_CEILOMETER_SNMPD_PASSWORD": "snmpd-pass"
|
|
}
|
|
|
|
undercloud._create_mistral_config_environment(env, mock_mistral)
|
|
mock_mistral.executions.create.assert_not_called()
|
|
|
|
def test_prepare_ssh_environment(self):
|
|
mock_mistral = mock.Mock()
|
|
undercloud._prepare_ssh_environment(mock_mistral)
|
|
mock_mistral.executions.create.assert_called_once_with(
|
|
'tripleo.validations.v1.copy_ssh_key')
|
|
|
|
@mock.patch('time.sleep')
|
|
def test_create_default_plan_timeout(self, mock_sleep):
|
|
mock_mistral = mock.Mock()
|
|
mock_mistral.executions.get.return_value = mock.Mock(state="RUNNING")
|
|
|
|
self.assertRaises(
|
|
RuntimeError,
|
|
undercloud._create_default_plan, mock_mistral, [], timeout=0)
|
|
|
|
@mock.patch('time.strptime')
|
|
def test_create_default_plan_failed(self, mock_strptime):
|
|
mock_mistral = mock.Mock()
|
|
mock_mistral.executions.get.return_value = mock.Mock(state="ERROR")
|
|
mock_mistral.action_executions.list.return_value = []
|
|
mock_strptime.return_value = time.mktime(time.localtime())
|
|
self.assertRaises(
|
|
RuntimeError,
|
|
undercloud._create_default_plan, mock_mistral, [])
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_copy_stackrc(self, mock_run):
|
|
undercloud._copy_stackrc()
|
|
calls = [mock.call(['sudo', 'cp', '/root/stackrc', mock.ANY],
|
|
name='Copy stackrc'),
|
|
mock.call(['sudo', 'chown', mock.ANY, mock.ANY],
|
|
name='Chown stackrc'),
|
|
]
|
|
mock_run.assert_has_calls(calls)
|
|
|
|
def _mock_ksclient_roles(self, mock_auth_values, mock_ksdiscover, roles):
|
|
mock_auth_values.return_value = ('user', 'password',
|
|
'project', 'http://test:123')
|
|
mock_discover = mock.Mock()
|
|
mock_ksdiscover.return_value = mock_discover
|
|
mock_client = mock.Mock()
|
|
mock_roles = mock.Mock()
|
|
mock_role_list = []
|
|
for role in roles:
|
|
mock_role = mock.Mock()
|
|
mock_role.name = role
|
|
mock_role_list.append(mock_role)
|
|
mock_roles.list.return_value = mock_role_list
|
|
mock_client.roles = mock_roles
|
|
mock_discover.create_client.return_value = mock_client
|
|
|
|
mock_client.version = 'v3'
|
|
|
|
mock_project_list = [mock.Mock(), mock.Mock()]
|
|
mock_project_list[0].name = 'admin'
|
|
mock_project_list[0].id = 'admin-id'
|
|
mock_project_list[1].name = 'service'
|
|
mock_project_list[1].id = 'service-id'
|
|
mock_client.projects.list.return_value = mock_project_list
|
|
|
|
mock_user_list = [mock.Mock(), mock.Mock()]
|
|
mock_user_list[0].name = 'admin'
|
|
mock_user_list[1].name = 'nova'
|
|
mock_client.users.list.return_value = mock_user_list
|
|
return mock_client
|
|
|
|
@mock.patch('keystoneclient.discover.Discover')
|
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
|
@mock.patch('os.path.isfile')
|
|
def test_member_role_exists(self, mock_isfile, mock_auth_values,
|
|
mock_ksdiscover):
|
|
mock_isfile.return_value = True
|
|
mock_client = self._mock_ksclient_roles(mock_auth_values,
|
|
mock_ksdiscover,
|
|
['admin'])
|
|
undercloud._member_role_exists()
|
|
self.assertFalse(mock_client.projects.list.called)
|
|
|
|
@mock.patch('keystoneclient.discover.Discover')
|
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
|
@mock.patch('os.path.isfile')
|
|
def test_member_role_exists_true(self, mock_isfile,
|
|
mock_auth_values, mock_ksdiscover):
|
|
mock_isfile.return_value = True
|
|
mock_client = self._mock_ksclient_roles(mock_auth_values,
|
|
mock_ksdiscover,
|
|
['admin', '_member_'])
|
|
undercloud._member_role_exists()
|
|
mock_user = mock_client.users.list.return_value[0]
|
|
mock_role = mock_client.roles.list.return_value[1]
|
|
mock_client.roles.grant.assert_called_once_with(
|
|
mock_role, user=mock_user, project='admin-id')
|
|
|
|
@mock.patch('keystoneclient.discover.Discover')
|
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
|
@mock.patch('os.path.isfile')
|
|
def test_has_member_role(self, mock_isfile, mock_auth_values,
|
|
mock_ksdiscover):
|
|
mock_isfile.return_value = True
|
|
mock_client = self._mock_ksclient_roles(mock_auth_values,
|
|
mock_ksdiscover,
|
|
['admin', '_member_'])
|
|
fake_exception = ks_exceptions.http.Conflict('test')
|
|
mock_client.roles.grant.side_effect = fake_exception
|
|
undercloud._member_role_exists()
|
|
mock_user = mock_client.users.list.return_value[0]
|
|
mock_role = mock_client.roles.list.return_value[1]
|
|
mock_client.roles.grant.assert_called_once_with(
|
|
mock_role, user=mock_user, project='admin-id')
|
|
|
|
def _create_flavor_mocks(self):
|
|
mock_nova = mock.Mock()
|
|
mock_nova.flavors.create = mock.Mock()
|
|
mock_flavor = mock.Mock()
|
|
mock_nova.flavors.create.return_value = mock_flavor
|
|
mock_flavor.set_keys = mock.Mock()
|
|
return mock_nova, mock_flavor
|
|
|
|
def test_ensure_flavor_no_profile(self):
|
|
mock_nova, mock_flavor = self._create_flavor_mocks()
|
|
undercloud._ensure_flavor(mock_nova, None, 'test')
|
|
mock_nova.flavors.create.assert_called_with('test', 4096, 1, 40)
|
|
keys = {'capabilities:boot_option': 'local',
|
|
'resources:CUSTOM_BAREMETAL': '1',
|
|
'resources:DISK_GB': '0',
|
|
'resources:MEMORY_MB': '0',
|
|
'resources:VCPU': '0'}
|
|
mock_flavor.set_keys.assert_called_with(keys)
|
|
|
|
def test_ensure_flavor_profile(self):
|
|
mock_nova, mock_flavor = self._create_flavor_mocks()
|
|
undercloud._ensure_flavor(mock_nova, None, 'test', 'test')
|
|
mock_nova.flavors.create.assert_called_with('test', 4096, 1, 40)
|
|
keys = {'capabilities:boot_option': 'local',
|
|
'capabilities:profile': 'test',
|
|
'resources:CUSTOM_BAREMETAL': '1',
|
|
'resources:DISK_GB': '0',
|
|
'resources:MEMORY_MB': '0',
|
|
'resources:VCPU': '0'}
|
|
mock_flavor.set_keys.assert_called_with(keys)
|
|
|
|
def test_ensure_flavor_exists(self):
|
|
mock_nova, mock_flavor = self._create_flavor_mocks()
|
|
mock_nova.flavors.create.side_effect = exceptions.Conflict(None)
|
|
flavor = mock.Mock(spec=['name', 'get_keys', 'set_keys'])
|
|
flavor.get_keys.return_value = {'foo': 'bar'}
|
|
|
|
undercloud._ensure_flavor(mock_nova, flavor, 'test')
|
|
|
|
keys = {'foo': 'bar',
|
|
'resources:CUSTOM_BAREMETAL': '1',
|
|
'resources:DISK_GB': '0',
|
|
'resources:MEMORY_MB': '0',
|
|
'resources:VCPU': '0'}
|
|
flavor.set_keys.assert_called_with(keys)
|
|
mock_nova.flavors.create.assert_not_called()
|
|
|
|
@mock.patch.object(undercloud.LOG, 'warning', autospec=True)
|
|
def test_ensure_flavor_exists_conflicting_rc(self, mock_warn):
|
|
mock_nova, mock_flavor = self._create_flavor_mocks()
|
|
mock_nova.flavors.create.side_effect = exceptions.Conflict(None)
|
|
flavor = mock.Mock(spec=['name', 'get_keys', 'set_keys'])
|
|
flavor.get_keys.return_value = {'foo': 'bar',
|
|
'resources:CUSTOM_FOO': '42'}
|
|
|
|
undercloud._ensure_flavor(mock_nova, flavor, 'test')
|
|
|
|
flavor.set_keys.assert_not_called()
|
|
mock_warn.assert_called_once_with(mock.ANY, flavor.name,
|
|
'resources:CUSTOM_FOO')
|
|
mock_nova.flavors.create.assert_not_called()
|
|
|
|
def test_ensure_node_resource_classes(self):
|
|
nodes = [mock.Mock(uuid='1', resource_class=None),
|
|
mock.Mock(uuid='2', resource_class='foobar')]
|
|
ironic_mock = mock.Mock()
|
|
ironic_mock.node.list.return_value = nodes
|
|
|
|
undercloud._ensure_node_resource_classes(ironic_mock)
|
|
|
|
ironic_mock.node.update.assert_called_once_with(
|
|
'1', [{'path': '/resource_class', 'op': 'add',
|
|
'value': 'baremetal'}])
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_migrate_to_convergence(self, mock_run_command):
|
|
stacks = [mock.Mock(id='1'), mock.Mock(id='2')]
|
|
mock_heat = mock.Mock()
|
|
mock_heat.stacks.list.return_value = stacks
|
|
undercloud._migrate_to_convergence(mock_heat)
|
|
self.assertEqual([mock.call(['sudo', 'heat-manage',
|
|
'migrate_convergence_1', '1'],
|
|
name='heat-manage'),
|
|
mock.call(['sudo', 'heat-manage',
|
|
'migrate_convergence_1', '2'],
|
|
name='heat-manage')],
|
|
mock_run_command.mock_calls)
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_migrate_to_convergence_no_stacks(self, mock_run_command):
|
|
stacks = []
|
|
mock_heat = mock.Mock()
|
|
mock_heat.stacks.list.return_value = stacks
|
|
undercloud._migrate_to_convergence(mock_heat)
|
|
mock_run_command.assert_not_called()
|
|
|
|
@mock.patch('instack_undercloud.undercloud._extract_from_stackrc')
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
def test_get_auth_values(self, mock_run, mock_extract):
|
|
mock_run.return_value = '3nigma'
|
|
mock_extract.side_effect = ['aturing', 'hut8',
|
|
'http://bletchley:5000/v2.0']
|
|
values = undercloud._get_auth_values()
|
|
expected = ('aturing', '3nigma', 'hut8', 'http://bletchley:5000/v2.0')
|
|
self.assertEqual(expected, values)
|
|
|
|
def test_delete_default_flavors(self):
|
|
class FakeFlavor(object):
|
|
def __init__(self, id_, name):
|
|
self.id = id_
|
|
self.name = name
|
|
mock_instance = mock.Mock()
|
|
mock_flavors = [FakeFlavor('f00', 'foo'),
|
|
FakeFlavor('8ar', 'm1.large')]
|
|
mock_instance.flavors.list.return_value = mock_flavors
|
|
undercloud._delete_default_flavors(mock_instance)
|
|
mock_instance.flavors.delete.assert_called_once_with('8ar')
|
|
|
|
@mock.patch('os.path.isfile', return_value=True)
|
|
@mock.patch('os.listdir')
|
|
@mock.patch('instack_undercloud.undercloud._create_mistral_config_'
|
|
'environment')
|
|
@mock.patch('instack_undercloud.undercloud._create_default_plan')
|
|
def test_post_config_mistral(self, mock_create, mock_cmce, mock_listdir,
|
|
mock_isfile):
|
|
instack_env = {}
|
|
mock_mistral = mock.Mock()
|
|
mock_swift = mock.Mock()
|
|
mock_swift.get_account.return_value = [None, [{'name': 'hut8'}]]
|
|
|
|
mock_workbooks = [mock.Mock() for m in range(2)]
|
|
mock_workbooks[0].name = 'foo'
|
|
mock_workbooks[1].name = 'tripleo.bar'
|
|
mock_mistral.workbooks.list.return_value = mock_workbooks
|
|
|
|
mock_triggers = [mock.Mock() for m in range(2)]
|
|
mock_triggers[0].name = 'foobar'
|
|
mock_triggers[0].workflow_name = 'foo'
|
|
mock_triggers[1].name = 'delete_me'
|
|
mock_triggers[1].workflow_name = 'tripleo.bar'
|
|
mock_mistral.cron_triggers.list.return_value = mock_triggers
|
|
|
|
mock_workflows = [mock.Mock() for m in range(2)]
|
|
mock_workflows[0].name = 'foo'
|
|
mock_workflows[1].name = 'tripleo.bar'
|
|
mock_workflows[0].tags = []
|
|
mock_workflows[1].tags = []
|
|
mock_mistral.workflows.list.return_value = mock_workflows
|
|
mock_listdir.return_value = ['foo.yaml', 'bar.yaml']
|
|
undercloud._post_config_mistral(instack_env, mock_mistral, mock_swift)
|
|
self.assertEqual([mock.call('tripleo.bar')],
|
|
mock_mistral.workbooks.delete.mock_calls)
|
|
self.assertEqual([mock.call('tripleo.bar')],
|
|
mock_mistral.workflows.delete.mock_calls)
|
|
self.assertEqual([mock.call('delete_me')],
|
|
mock_mistral.cron_triggers.delete.mock_calls)
|
|
self.assertEqual([mock.call(undercloud.PATHS.WORKBOOK_PATH +
|
|
'/foo.yaml'),
|
|
mock.call(undercloud.PATHS.WORKBOOK_PATH +
|
|
'/bar.yaml')],
|
|
mock_mistral.workbooks.create.mock_calls)
|
|
mock_cmce.assert_called_once_with(instack_env, mock_mistral)
|
|
mock_create.assert_called_once_with(mock_mistral, ['hut8'])
|
|
|
|
@mock.patch('os.path.isfile', return_value=True)
|
|
@mock.patch('os.listdir')
|
|
@mock.patch('instack_undercloud.undercloud._create_mistral_config_'
|
|
'environment')
|
|
@mock.patch('instack_undercloud.undercloud._create_default_plan')
|
|
def test_post_config_mistral_with_tags(self, mock_create, mock_cmce,
|
|
mock_listdir, mock_isfile):
|
|
instack_env = {}
|
|
mock_mistral = mock.Mock()
|
|
mock_swift = mock.Mock()
|
|
mock_swift.get_account.return_value = [None, [{'name': 'hut8'}]]
|
|
|
|
mock_workbooks = [mock.Mock() for m in range(2)]
|
|
mock_workbooks[0].name = 'foo'
|
|
mock_workbooks[1].name = 'tripleo.bar'
|
|
mock_mistral.workbooks.list.return_value = mock_workbooks
|
|
mock_triggers = [mock.Mock() for m in range(2)]
|
|
mock_triggers[0].name = 'dont_delete_me'
|
|
mock_triggers[0].workflow_name = 'tripleo.foo'
|
|
mock_triggers[1].name = 'delete_me'
|
|
mock_triggers[1].workflow_name = 'tripleo.bar'
|
|
mock_mistral.cron_triggers.list.return_value = mock_triggers
|
|
mock_workflows = [mock.Mock() for m in range(2)]
|
|
mock_workflows[0].name = 'tripleo.foo'
|
|
mock_workflows[1].name = 'tripleo.bar'
|
|
mock_workflows[0].tags = []
|
|
mock_workflows[1].tags = ['tripleo-common-managed', ]
|
|
mock_mistral.workflows.list.return_value = mock_workflows
|
|
|
|
mock_listdir.return_value = ['foo.yaml', 'bar.yaml']
|
|
undercloud._post_config_mistral(instack_env, mock_mistral, mock_swift)
|
|
self.assertEqual([mock.call('tripleo.bar')],
|
|
mock_mistral.workbooks.delete.mock_calls)
|
|
self.assertEqual([mock.call('tripleo.bar')],
|
|
mock_mistral.workflows.delete.mock_calls)
|
|
self.assertEqual([mock.call('delete_me')],
|
|
mock_mistral.cron_triggers.delete.mock_calls)
|
|
self.assertEqual([mock.call(undercloud.PATHS.WORKBOOK_PATH +
|
|
'/foo.yaml'),
|
|
mock.call(undercloud.PATHS.WORKBOOK_PATH +
|
|
'/bar.yaml')],
|
|
mock_mistral.workbooks.create.mock_calls)
|
|
mock_cmce.assert_called_once_with(instack_env, mock_mistral)
|
|
mock_create.assert_called_once_with(mock_mistral, ['hut8'])
|
|
|
|
|
|
class TestUpgradeFact(base.BaseTestCase):
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
@mock.patch('os.path.dirname')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file'))
|
|
def test_upgrade_fact(self, mock_mkstemp, mock_exists, mock_dirname,
|
|
mock_run):
|
|
fact_path = '/etc/facter/facts.d/undercloud_upgrade.txt'
|
|
mock_dirname.return_value = '/etc/facter/facts.d'
|
|
mock_exists.side_effect = [False, True]
|
|
|
|
with mock.patch('instack_undercloud.undercloud.open') as mock_open:
|
|
undercloud._handle_upgrade_fact(True)
|
|
mock_open.assert_called_with('/tmp/file', 'w')
|
|
|
|
run_calls = [
|
|
mock.call(['sudo', 'mkdir', '-p', '/etc/facter/facts.d']),
|
|
mock.call(['sudo', 'mv', '/tmp/file', fact_path]),
|
|
mock.call(['sudo', 'chmod', '0644', fact_path])
|
|
]
|
|
mock_run.assert_has_calls(run_calls)
|
|
self.assertEqual(mock_run.call_count, 3)
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
@mock.patch('os.path.dirname')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file'))
|
|
def test_upgrade_fact_install(self, mock_mkstemp, mock_exists,
|
|
mock_dirname, mock_run):
|
|
mock_dirname.return_value = '/etc/facter/facts.d'
|
|
mock_exists.return_value = False
|
|
|
|
with mock.patch('instack_undercloud.undercloud.open') as mock_open:
|
|
undercloud._handle_upgrade_fact(False)
|
|
mock_open.assert_not_called()
|
|
|
|
mock_run.assert_not_called()
|
|
|
|
@mock.patch('instack_undercloud.undercloud._run_command')
|
|
@mock.patch('os.path.dirname')
|
|
@mock.patch('os.path.exists')
|
|
@mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file'))
|
|
def test_upgrade_fact_upgrade_after_install(self, mock_mkstemp,
|
|
mock_exists, mock_dirname,
|
|
mock_run):
|
|
fact_path = '/etc/facter/facts.d/undercloud_upgrade.txt'
|
|
mock_dirname.return_value = '/etc/facter/facts.d'
|
|
mock_exists.return_value = True
|
|
|
|
with mock.patch('instack_undercloud.undercloud.open') as open_m:
|
|
undercloud._handle_upgrade_fact(True)
|
|
open_m.assert_called_with('/tmp/file', 'w')
|
|
|
|
run_calls = [
|
|
mock.call(['sudo', 'mv', '/tmp/file', fact_path]),
|
|
mock.call(['sudo', 'chmod', '0644', fact_path])
|
|
]
|
|
mock_run.assert_has_calls(run_calls)
|
|
self.assertEqual(mock_run.call_count, 2)
|
|
|
|
|
|
class TestInstackEnvironment(BaseTestCase):
|
|
def test_set_allowed_keys(self):
|
|
env = undercloud.InstackEnvironment()
|
|
env['HOSTNAME'] = 'localhost1'
|
|
env['INSPECTION_COLLECTORS'] = 'a,b,c'
|
|
|
|
def test_set_unknown_keys(self):
|
|
env = undercloud.InstackEnvironment()
|
|
|
|
def _set():
|
|
env['CATS_AND_DOGS_PATH'] = '/home'
|
|
|
|
self.assertRaisesRegex(KeyError, 'CATS_AND_DOGS_PATH', _set)
|
|
|
|
def test_get_always_allowed(self):
|
|
env = undercloud.InstackEnvironment()
|
|
env.get('HOSTNAME')
|
|
env.get('CATS_AND_DOGS_PATH')
|