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:
Ben Nemec
2015-11-25 23:18:10 +00:00
parent 1eafb55f4c
commit 8f714d8b4c
2 changed files with 137 additions and 35 deletions

View File

@@ -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)

View File

@@ -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')})