diff --git a/instack_undercloud/tests/test_undercloud.py b/instack_undercloud/tests/test_undercloud.py index 58fbe65c9..20a011d94 100644 --- a/instack_undercloud/tests/test_undercloud.py +++ b/instack_undercloud/tests/test_undercloud.py @@ -40,6 +40,7 @@ class BaseTestCase(base.BaseTestCase): class TestUndercloud(BaseTestCase): @mock.patch('instack_undercloud.undercloud._configure_logging') @mock.patch('instack_undercloud.undercloud._check_hostname') + @mock.patch('instack_undercloud.undercloud._check_memory') @mock.patch('instack_undercloud.undercloud._run_command') @mock.patch('instack_undercloud.undercloud._post_config') @mock.patch('instack_undercloud.undercloud._run_orc') @@ -48,11 +49,13 @@ class TestUndercloud(BaseTestCase): @mock.patch('instack_undercloud.undercloud._load_config') def test_install(self, mock_load_config, mock_generate_environment, mock_run_instack, mock_run_orc, mock_post_config, - mock_run_command, mock_check_hostname, + mock_run_command, mock_check_memory, mock_check_hostname, mock_configure_logging): fake_env = mock.MagicMock() mock_generate_environment.return_value = fake_env undercloud.install('.') + self.assertEqual(True, mock_check_hostname.called) + self.assertEqual(True, mock_check_memory.called) mock_generate_environment.assert_called_with('.') mock_run_instack.assert_called_with(fake_env) mock_run_orc.assert_called_with(fake_env) @@ -127,6 +130,20 @@ class TestCheckHostname(BaseTestCase): self.assertRaises(RuntimeError, undercloud._check_hostname) +class TestCheckMemory(BaseTestCase): + @mock.patch('psutil.virtual_memory') + def test_sufficient_memory(self, mock_vm): + mock_vm.return_value = mock.Mock() + mock_vm.return_value.total = 4143927296 + undercloud._check_memory() + + @mock.patch('psutil.virtual_memory') + def test_insufficient_memory(self, mock_vm): + mock_vm.return_value = mock.Mock() + mock_vm.return_value.total = 2071963648 + self.assertRaises(RuntimeError, undercloud._check_memory) + + class TestGenerateEnvironment(BaseTestCase): def setUp(self): super(TestGenerateEnvironment, self).setUp() diff --git a/instack_undercloud/undercloud.py b/instack_undercloud/undercloud.py index 731214de5..0e9d33905 100644 --- a/instack_undercloud/undercloud.py +++ b/instack_undercloud/undercloud.py @@ -27,6 +27,7 @@ import uuid from novaclient import client as novaclient from novaclient import exceptions from oslo_config import cfg +import psutil import six from six.moves import configparser @@ -71,6 +72,9 @@ secured. ############################################################################# """ +# We need 4 GB, leave a little room for variation in what 4 GB means on +# different platforms. +REQUIRED_MB = 3750 # When adding new options to the lists below, make sure to regenerate the @@ -362,6 +366,22 @@ def _check_hostname(): raise RuntimeError('Static hostname not set in /etc/hosts') +def _check_memory(): + """Check system memory + + The undercloud will not run properly in less than 4 GB of memory. + This function verifies that at least that much is available before + proceeding with install. + """ + mem = psutil.virtual_memory() + total_mb = mem.total / 1024 / 1024 + if total_mb < REQUIRED_MB: + LOG.error('At least 4 GB of memory is required for undercloud ' + 'installation. A minimum of 6 GB is recommended. ' + 'Only detected %d MB' % total_mb) + raise RuntimeError('Insufficient memory available') + + def _generate_password(length=40): """Create a random password @@ -595,6 +615,7 @@ def install(instack_root): LOG.info('Logging to %s', PATHS.LOG_FILE) _load_config() _check_hostname() + _check_memory() instack_env = _generate_environment(instack_root) _run_instack(instack_env) # NOTE(bnemec): I removed the conditional running of os-refresh-config. diff --git a/requirements.txt b/requirements.txt index a503d1a7d..0dcbf9b44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ six>=1.9.0 python-novaclient oslo.config +psutil>=1.1.1,<2.0.0