Create default flavors as part of undercloud install
We mostly do node scheduling based on profile, which means that the actual values in the flavors don't matter in many cases. To make the entire deploy process easier, just create the default flavors with some sane minimum specs. They can always be changed later for users who want to do something different. As part of this change, the post deploy code in undercloud.py was refactored to be a bit more modular and easier to test. Depends-On: I330884ec207c8bcee31961a292499165931d660a Change-Id: I5dec6dc59fb537718d5175e70fab1d9f273d686b
This commit is contained in:
@@ -41,13 +41,13 @@ class TestUndercloud(BaseTestCase):
|
||||
@mock.patch('instack_undercloud.undercloud._configure_logging')
|
||||
@mock.patch('instack_undercloud.undercloud._check_hostname')
|
||||
@mock.patch('instack_undercloud.undercloud._run_command')
|
||||
@mock.patch('instack_undercloud.undercloud._configure_ssh_keys')
|
||||
@mock.patch('instack_undercloud.undercloud._post_config')
|
||||
@mock.patch('instack_undercloud.undercloud._run_orc')
|
||||
@mock.patch('instack_undercloud.undercloud._run_instack')
|
||||
@mock.patch('instack_undercloud.undercloud._generate_environment')
|
||||
@mock.patch('instack_undercloud.undercloud._load_config')
|
||||
def test_install(self, mock_load_config, mock_generate_environment,
|
||||
mock_run_instack, mock_run_orc, mock_configure_ssh_keys,
|
||||
mock_run_instack, mock_run_orc, mock_post_config,
|
||||
mock_run_command, mock_check_hostname,
|
||||
mock_configure_logging):
|
||||
fake_env = mock.MagicMock()
|
||||
@@ -342,38 +342,100 @@ class TestConfigureSshKeys(base.BaseTestCase):
|
||||
undercloud._ensure_user_identity(id_path)
|
||||
self.assertFalse(mock_run.called)
|
||||
|
||||
def _test_configure_ssh_keys(self, mock_eui, mock_extract, mock_client,
|
||||
mock_run, exists=True):
|
||||
def _test_configure_ssh_keys(self, mock_eui, exists=True):
|
||||
id_path = self._create_test_id()
|
||||
mock_run.side_effect = [None, None, '3nigma']
|
||||
mock_extract.side_effect = ['aturing', 'http://bletchley:5000/v2.0',
|
||||
'hut8']
|
||||
|
||||
mock_client_instance = mock.Mock()
|
||||
mock_client.return_value = mock_client_instance
|
||||
if not exists:
|
||||
get = mock_client_instance.keypairs.get
|
||||
get.side_effect = exceptions.NotFound('test')
|
||||
undercloud._configure_ssh_keys()
|
||||
undercloud._configure_ssh_keys(mock_client_instance)
|
||||
mock_eui.assert_called_with(id_path)
|
||||
mock_client.assert_called_with(2, 'aturing', '3nigma', 'hut8',
|
||||
'http://bletchley:5000/v2.0')
|
||||
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('novaclient.client.Client', autospec=True)
|
||||
@mock.patch('instack_undercloud.undercloud._extract_from_stackrc')
|
||||
@mock.patch('instack_undercloud.undercloud._ensure_user_identity')
|
||||
def test_configure_ssh_keys_exists(self, mock_eui, mock_extract,
|
||||
mock_client, mock_run):
|
||||
self._test_configure_ssh_keys(mock_eui, mock_extract, mock_client,
|
||||
mock_run)
|
||||
def test_configure_ssh_keys_exists(self, mock_eui, _):
|
||||
self._test_configure_ssh_keys(mock_eui)
|
||||
|
||||
@mock.patch('novaclient.client.Client', autospec=True)
|
||||
@mock.patch('instack_undercloud.undercloud._extract_from_stackrc')
|
||||
@mock.patch('instack_undercloud.undercloud._ensure_user_identity')
|
||||
def test_configure_ssh_keys_missing(self, mock_eui, mock_extract,
|
||||
mock_client, mock_run):
|
||||
self._test_configure_ssh_keys(mock_eui, mock_extract, mock_client,
|
||||
mock_run, False)
|
||||
def test_configure_ssh_keys_missing(self, mock_eui, _):
|
||||
self._test_configure_ssh_keys(mock_eui, False)
|
||||
|
||||
|
||||
class TestPostConfig(base.BaseTestCase):
|
||||
@mock.patch('novaclient.client.Client', autospec=True)
|
||||
@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')
|
||||
def test_post_config(self, mock_ensure_flavor, mock_configure_ssh_keys,
|
||||
mock_get_auth_values, mock_copy_stackrc, mock_client):
|
||||
mock_get_auth_values.return_value = ('aturing', '3nigma', 'hut8',
|
||||
'http://bletchley:5000/v2.0')
|
||||
mock_instance = mock.Mock()
|
||||
mock_client.return_value = mock_instance
|
||||
undercloud._post_config()
|
||||
mock_client.assert_called_with(2, 'aturing', '3nigma', 'hut8',
|
||||
'http://bletchley:5000/v2.0')
|
||||
self.assertTrue(mock_copy_stackrc.called)
|
||||
mock_configure_ssh_keys.assert_called_with(mock_instance)
|
||||
calls = [mock.call(mock_instance, 'baremetal'),
|
||||
mock.call(mock_instance, 'control', 'control'),
|
||||
mock.call(mock_instance, 'compute', 'compute'),
|
||||
mock.call(mock_instance, 'ceph-storage', 'ceph-storage'),
|
||||
mock.call(mock_instance, 'block-storage', 'block-storage'),
|
||||
mock.call(mock_instance, 'swift-storage', 'swift-storage'),
|
||||
]
|
||||
mock_ensure_flavor.assert_has_calls(calls)
|
||||
|
||||
@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 _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, 'test')
|
||||
mock_nova.flavors.create.assert_called_with('test', 4096, 1, 40)
|
||||
keys = {'capabilities:boot_option': 'local'}
|
||||
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, 'test', 'test')
|
||||
mock_nova.flavors.create.assert_called_with('test', 4096, 1, 40)
|
||||
keys = {'capabilities:boot_option': 'local',
|
||||
'capabilities:profile': 'test'}
|
||||
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)
|
||||
undercloud._ensure_flavor(mock_nova, 'test')
|
||||
mock_flavor.set_keys.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)
|
||||
|
||||
@@ -515,7 +515,20 @@ def _ensure_user_identity(id_path):
|
||||
LOG.info('Generated new ssh key in ~/.ssh/id_rsa')
|
||||
|
||||
|
||||
def _configure_ssh_keys():
|
||||
def _get_auth_values():
|
||||
"""Get auth values from stackrc
|
||||
|
||||
Returns the user, password, tenant and auth_url as read from stackrc,
|
||||
in that order as a tuple.
|
||||
"""
|
||||
user = _extract_from_stackrc('OS_USERNAME')
|
||||
password = _run_command(['sudo', 'hiera', 'admin_password']).rstrip()
|
||||
tenant = _extract_from_stackrc('OS_TENANT')
|
||||
auth_url = _extract_from_stackrc('OS_AUTH_URL')
|
||||
return user, password, tenant, auth_url
|
||||
|
||||
|
||||
def _configure_ssh_keys(nova):
|
||||
"""Configure default ssh keypair in Nova
|
||||
|
||||
Generates a new ssh key for the current user if one does not already
|
||||
@@ -524,16 +537,6 @@ def _configure_ssh_keys():
|
||||
id_path = os.path.expanduser('~/.ssh/id_rsa')
|
||||
_ensure_user_identity(id_path)
|
||||
|
||||
args = ['sudo', 'cp', '/root/stackrc', os.path.expanduser('~')]
|
||||
_run_command(args, name='Copy stackrc')
|
||||
args = ['sudo', 'chown', getpass.getuser() + ':',
|
||||
os.path.expanduser('~/stackrc')]
|
||||
_run_command(args, name='Chown stackrc')
|
||||
password = _run_command(['sudo', 'hiera', 'admin_password']).rstrip()
|
||||
user = _extract_from_stackrc('OS_USERNAME')
|
||||
auth_url = _extract_from_stackrc('OS_AUTH_URL')
|
||||
tenant = _extract_from_stackrc('OS_TENANT')
|
||||
nova = novaclient.Client(2, user, password, tenant, auth_url)
|
||||
try:
|
||||
nova.keypairs.get('default')
|
||||
except exceptions.NotFound:
|
||||
@@ -541,6 +544,43 @@ def _configure_ssh_keys():
|
||||
nova.keypairs.create('default', pubkey.read().rstrip())
|
||||
|
||||
|
||||
def _ensure_flavor(nova, name, profile=None):
|
||||
try:
|
||||
flavor = nova.flavors.create(name, 4096, 1, 40)
|
||||
except exceptions.Conflict:
|
||||
LOG.info('Not creating flavor "%s" because it already exists.', name)
|
||||
return
|
||||
keys = {'capabilities:boot_option': 'local'}
|
||||
if profile is not None:
|
||||
keys['capabilities:profile'] = profile
|
||||
flavor.set_keys(keys)
|
||||
message = 'Created flavor "%s" with profile "%s"'
|
||||
LOG.info(message, name, profile)
|
||||
|
||||
|
||||
def _copy_stackrc():
|
||||
args = ['sudo', 'cp', '/root/stackrc', os.path.expanduser('~')]
|
||||
_run_command(args, name='Copy stackrc')
|
||||
args = ['sudo', 'chown', getpass.getuser() + ':',
|
||||
os.path.expanduser('~/stackrc')]
|
||||
_run_command(args, name='Chown stackrc')
|
||||
|
||||
|
||||
def _post_config():
|
||||
_copy_stackrc()
|
||||
user, password, tenant, auth_url = _get_auth_values()
|
||||
nova = novaclient.Client(2, user, password, tenant, auth_url)
|
||||
|
||||
_configure_ssh_keys(nova)
|
||||
|
||||
_ensure_flavor(nova, 'baremetal')
|
||||
_ensure_flavor(nova, 'control', 'control')
|
||||
_ensure_flavor(nova, 'compute', 'compute')
|
||||
_ensure_flavor(nova, 'ceph-storage', 'ceph-storage')
|
||||
_ensure_flavor(nova, 'block-storage', 'block-storage')
|
||||
_ensure_flavor(nova, 'swift-storage', 'swift-storage')
|
||||
|
||||
|
||||
def install(instack_root):
|
||||
"""Install the undercloud
|
||||
|
||||
@@ -560,7 +600,7 @@ def install(instack_root):
|
||||
# TODO(bnemec): Do we still need INSTACK_ROOT?
|
||||
instack_env['INSTACK_ROOT'] = os.environ.get('INSTACK_ROOT') or ''
|
||||
_run_orc(instack_env)
|
||||
_configure_ssh_keys()
|
||||
_post_config()
|
||||
_run_command(['sudo', 'rm', '-f', '/tmp/svc-map-services'], None, 'rm')
|
||||
LOG.info(COMPLETION_MESSAGE, {'password_path': PATHS.PASSWORD_PATH,
|
||||
'stackrc_path': os.path.expanduser('~/stackrc')})
|
||||
|
||||
Reference in New Issue
Block a user