class TestBigtopUnit(Harness): ''' Unit tests for Bigtop class. ''' @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_version', new_callable=mock.PropertyMock) def setUp(self, mock_ver, mock_hookenv): mock_ver.return_value = '1.2.0' super(TestBigtopUnit, self).setUp() self.bigtop = Bigtop() def test_init(self): ''' Verify that the Bigtop class can init itself, and that it has some of the properties that we expect.. ''' # paths should be Path objects. self.assertEqual(type(self.bigtop.bigtop_base), Path) self.assertEqual(type(self.bigtop.site_yaml), Path) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.render_hiera_yaml') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.apply_patches') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.install_puppet_modules' ) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.fetch_bigtop_release') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.check_reverse_dns') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.check_localdomain') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.pin_bigtop_packages') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.install_java') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.install_swap') @mock.patch('charms.layer.apache_bigtop_base.is_container') def test_install(self, mock_container, mock_swap, mock_java, mock_pin, mock_local, mock_dns, mock_fetch, mock_puppet, mock_apply, mock_hiera): ''' Verify install calls expected class methods. ''' mock_container.return_value = False self.bigtop.install() self.assertTrue(mock_swap.called) self.assertTrue(mock_java.called) self.assertTrue(mock_pin.called) self.assertTrue(mock_local.called) self.assertTrue(mock_dns.called) self.assertTrue(mock_fetch.called) self.assertTrue(mock_puppet.called) self.assertTrue(mock_apply.called) self.assertTrue(mock_hiera.called) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.update_bigtop_repo') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.apply_patches') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.fetch_bigtop_release') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.pin_bigtop_packages') def test_refresh_bigtop_release(self, mock_pin, mock_fetch, mock_apply, mock_update): ''' Verify refresh calls expected class methods. ''' self.bigtop.refresh_bigtop_release() self.assertTrue(mock_pin.called) self.assertTrue(mock_fetch.called) self.assertTrue(mock_apply.called) self.assertTrue(mock_update.called) @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.lsb_release') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_version', new_callable=mock.PropertyMock) def test_get_repo_url(self, mock_ver, mock_lsb_release, mock_utils, mock_hookenv): ''' Verify that we setup an appropriate repository. ''' mock_ver.return_value = '1.1.0' # non-ubuntu should throw an exception mock_lsb_release.return_value = {'DISTRIB_ID': 'centos'} self.assertRaises(BigtopError, self.bigtop.get_repo_url, '1.1.0') # 1.1.0 on trusty/non-power mock_utils.cpu_arch.return_value = 'foo' mock_lsb_release.return_value = {'DISTRIB_ID': 'ubuntu'} self.assertEqual(self.bigtop.get_repo_url('1.1.0'), ('http://bigtop-repos.s3.amazonaws.com/releases/' '1.1.0/ubuntu/trusty/foo')) # 1.1.0 on trusty/power (should return vivid url) mock_utils.cpu_arch.return_value = 'ppc64le' self.assertEqual(self.bigtop.get_repo_url('1.1.0'), ('http://bigtop-repos.s3.amazonaws.com/releases/' '1.1.0/ubuntu/vivid/ppc64el')) # 1.2.0 on xenial mock_ver.return_value = '1.2.0' mock_utils.cpu_arch.return_value = 'foo' mock_lsb_release.return_value = {'DISTRIB_ID': 'ubuntu'} self.assertEqual(self.bigtop.get_repo_url('1.2.0'), ('http://bigtop-repos.s3.amazonaws.com/releases/' '1.2.0/ubuntu/16.04/foo')) # 1.2.1 on xenial/intel mock_hookenv.return_value = {'name': 'foo'} mock_ver.return_value = '1.2.1' mock_utils.cpu_arch.return_value = 'x86_64' self.assertEqual(self.bigtop.get_repo_url('1.2.1'), ('http://repos.bigtop.apache.org/releases/' '1.2.1/ubuntu/16.04/x86_64')) # 1.2.1 on xenial/non-intel mock_ver.return_value = '1.2.1' mock_utils.cpu_arch.return_value = 'foo' self.assertEqual( self.bigtop.get_repo_url('1.2.1'), ('https://ci.bigtop.apache.org/job/Bigtop-1.2.1/' 'OS=ubuntu-16.04/lastSuccessfulBuild/artifact/output/apt')) # master on xenial/intel mock_ver.return_value = 'master' mock_utils.cpu_arch.return_value = 'x86_64' self.assertEqual( self.bigtop.get_repo_url('master'), ('https://ci.bigtop.apache.org/job/Bigtop-trunk-repos/' 'OS=ubuntu-16.04,label=docker-slave/ws/output/apt')) # master on xenial/non-intel mock_ver.return_value = 'master' mock_utils.cpu_arch.return_value = 'foo' self.assertEqual( self.bigtop.get_repo_url('master'), ('https://ci.bigtop.apache.org/job/Bigtop-trunk-repos/' 'OS=ubuntu-16.04-foo,label=docker-slave/ws/output/apt')) # test bad version on xenial should throw an exception self.assertRaises(BigtopError, self.bigtop.get_repo_url, '0.0.0') @mock.patch('charms.layer.apache_bigtop_base.subprocess') def test_install_swap_when_swap_exists(self, mock_sub): ''' Verify we do attempt to install swap space if it already exists. ''' mock_sub.check_output.return_value = b"foo\nbar" mock_sub.reset_mock() self.bigtop.install_swap() # We reset the mock, so here we're verifying no other subprocess # calls were made. mock_sub.check_call.assert_not_called() @mock.patch('charms.layer.apache_bigtop_base.lsb_release') @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.fetch') @mock.patch('charms.layer.apache_bigtop_base.layer.options') def test_install_java(self, mock_options, mock_fetch, mock_utils, mock_lsb_release): ''' Test to verify that we install java when requested. ''' mock_lsb_release.return_value = {'DISTRIB_CODENAME': 'xenial'} # Should be noop if bigtop_jdk not set. self.bigtop.options.get.return_value = '' self.bigtop.install_java() self.assertFalse(mock_fetch.add_source.called) self.assertFalse(mock_fetch.apt_update.called) self.assertFalse(mock_fetch.apt_install.called) self.assertFalse(mock_utils.re_edit_in_place.called) # Should add ppa if we have set bigtop_jdk. self.bigtop.options.get.return_value = 'foo' print("options: {}".format(self.bigtop.options)) self.bigtop.install_java() self.assertFalse(mock_fetch.add_source.called) self.assertFalse(mock_fetch.apt_update.called) self.assertTrue(mock_fetch.apt_install.called) self.assertTrue(mock_utils.re_edit_in_place.called) # On trusty, should add a ppa so that we can install Java 8. mock_lsb_release.return_value = {'DISTRIB_CODENAME': 'trusty'} self.bigtop.install_java() self.assertTrue(mock_fetch.add_source.called) self.assertTrue(mock_fetch.apt_update.called) @mock.patch('charms.layer.apache_bigtop_base.Path') def test_pin_bigtop_packages(self, mock_path): ''' Verify the apt template is opened and written to a (mocked) file. ''' mock_dst = mock.Mock() mock_path.return_value = mock_dst self.bigtop.pin_bigtop_packages(priority=100) self.assertTrue(mock_dst.write_text.called) @mock.patch('charms.layer.apache_bigtop_base.subprocess') @mock.patch('charms.layer.apache_bigtop_base.lsb_release') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_apt', new_callable=mock.PropertyMock) def test_update_bigtop_repo(self, mock_apt, mock_lsb_release, mock_sub): ''' Verify a bigtop apt repository is added/removed. ''' # non-ubuntu should not invoke a subprocess call mock_lsb_release.return_value = { 'DISTRIB_CODENAME': 'foo', 'DISTRIB_ID': 'centos', 'DISTRIB_RELEASE': '7' } self.bigtop.update_bigtop_repo() mock_sub.check_call.assert_not_called() # verify args when adding a repo on ubuntu mock_apt.return_value = 'foo' mock_lsb_release.return_value = { 'DISTRIB_CODENAME': 'xenial', 'DISTRIB_ID': 'ubuntu', 'DISTRIB_RELEASE': '16.04' } self.bigtop.update_bigtop_repo() mock_sub.check_call.assert_called_with( ['add-apt-repository', '-yu', 'deb foo bigtop contrib']) # verify args when removing a repo on ubuntu self.bigtop.update_bigtop_repo(remove=True) mock_sub.check_call.assert_called_with( ['add-apt-repository', '-yur', 'deb foo bigtop contrib']) # verify we handle check_call errors class MockException(Exception): pass mock_sub.CalledProcessError = MockException def mock_raise(*args, **kwargs): raise MockException('foo!') mock_sub.check_call.side_effect = mock_raise self.bigtop.update_bigtop_repo() @mock.patch('charms.layer.apache_bigtop_base.get_package_version') @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.subprocess.Popen') @mock.patch('charms.layer.apache_bigtop_base.lsb_release') def test_check_bigtop_repo_package(self, mock_lsb_release, mock_sub, mock_hookenv, mock_pkg_ver): ''' Verify bigtop repo package queries. ''' # non-ubuntu should raise an error mock_lsb_release.return_value = { 'DISTRIB_CODENAME': 'foo', 'DISTRIB_ID': 'centos', 'DISTRIB_RELEASE': '7' } self.assertRaises(BigtopError, self.bigtop.check_bigtop_repo_package, 'foo') # reset with ubuntu mock_lsb_release.return_value = { 'DISTRIB_CODENAME': 'xenial', 'DISTRIB_ID': 'ubuntu', 'DISTRIB_RELEASE': '16.04' } madison_proc = mock.Mock() grep_proc = mock.Mock() # simulate a missing repo pkg grep_attrs = {'communicate.return_value': (b'', 'stderr')} grep_proc.configure_mock(**grep_attrs) # test a missing repo pkg (message should be logged) mock_sub.return_value = madison_proc mock_sub.return_value = grep_proc mock_pkg_ver.return_value = '' self.assertEqual(None, self.bigtop.check_bigtop_repo_package('foo')) mock_hookenv.log.assert_called_once() mock_hookenv.reset_mock() # reset our grep args to simulate the repo pkg being found grep_attrs = {'communicate.return_value': (b'pkg|1|repo', 'stderr')} grep_proc.configure_mock(**grep_attrs) # test a missing installed pkg (no log message) mock_sub.return_value = madison_proc mock_sub.return_value = grep_proc mock_pkg_ver.return_value = '' self.assertEqual('1', self.bigtop.check_bigtop_repo_package('foo')) mock_hookenv.log.assert_not_called() mock_hookenv.reset_mock() # test repo and installed pkg versions are the same (no log message) mock_sub.return_value = madison_proc mock_sub.return_value = grep_proc mock_pkg_ver.return_value = '1' self.assertEqual(None, self.bigtop.check_bigtop_repo_package('foo')) mock_hookenv.log.assert_not_called() mock_hookenv.reset_mock() # test repo pkg is newer than installed pkg (no log message) mock_sub.return_value = madison_proc mock_sub.return_value = grep_proc mock_pkg_ver.return_value = '0' self.assertEqual('1', self.bigtop.check_bigtop_repo_package('foo')) mock_hookenv.log.assert_not_called() mock_hookenv.reset_mock() @mock.patch('charms.layer.apache_bigtop_base.socket') @mock.patch('charms.layer.apache_bigtop_base.subprocess') @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.hookenv') def test_check_reverse_dns(self, mock_hookenv, mock_utils, mock_sub, mock_socket): ''' Verify that we set the reverse_dns_ok state, and handle errors correctly. ''' # Test the case where things succeed. mock_sub.check_output.return_value = b'domain' self.bigtop.check_reverse_dns() self.assertTrue(unitdata.kv().get('reverse_dns_ok')) # Test the case where we get an exception. mock_sub.check_output.return_value = b'localdomain' self.bigtop.check_reverse_dns() self.assertFalse(unitdata.kv().get('reverse_dns_ok')) class MockHError(Exception): pass def raise_herror(*args, **kwargs): raise MockHError('test') mock_socket.herror = MockHError mock_socket.gethostbyaddr = raise_herror self.bigtop.check_reverse_dns() self.assertFalse(unitdata.kv().get('reverse_dns_ok')) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_version', new_callable=mock.PropertyMock) @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.Path') def test_fetch_bigtop_release(self, mock_path, mock_hookenv, mock_ver): '''Verify we raise an exception if an invalid release is specified.''' mock_hookenv.resource_get.return_value = False mock_ver.return_value = 'foo' self.assertRaises(BigtopError, self.bigtop.fetch_bigtop_release) @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.hookenv') def test_install_puppet_modules(self, mock_hookenv, mock_utils): '''Verify that we seem to install puppet modules correctly.''' mock_hookenv.charm_dir.return_value = '/tmp' def mock_run_as(user, *args): ''' Verify that we run puppet as root. ''' self.assertEqual(user, 'root') mock_utils.run_as.side_effect = mock_run_as self.bigtop.install_puppet_modules() @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.glob') @mock.patch('charms.layer.apache_bigtop_base.chdir') def test_apply_patches(self, mock_chdir, mock_glob, mock_utils, mock_hookenv): ''' Verify that we apply patches in the correct order. ''' mock_hookenv.charm_dir.return_value = '/tmp' reverse_sorted = ['foo', 'baz', 'bar'] mock_glob.return_value = ['foo', 'baz', 'bar'] def mock_run_as(*args): patch = args[-1] self.assertEqual(args[0], 'root') # Verify that we're running on a sorted list. self.assertTrue(patch.endswith(reverse_sorted.pop())) mock_utils.run_as.side_effect = mock_run_as self.bigtop.apply_patches() @mock.patch('charms.layer.apache_bigtop_base.yaml') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_base') @mock.patch('charms.layer.apache_bigtop_base.Path') def test_render_hiera_yaml(self, mock_path, mock_base, mock_yaml): ''' Verify that we attempt to add the values that we expect our hiera object, before writing it out to a (mocked) yaml file. ''' def mock_dump(hiera_yaml, *args, **kwargs): self.assertTrue(hiera_yaml.get(':yaml')) self.assertTrue(':datadir' in hiera_yaml[':yaml']) mock_yaml.dump.side_effect = mock_dump mock_dst = mock.Mock() mock_path.return_value = mock_dst mock_yaml.load.return_value = defaultdict(lambda: {}) mock_base.__div__.side_effect = lambda rel: mock_base mock_base.__truediv__.side_effect = lambda rel: mock_base self.bigtop.render_hiera_yaml() # Verify that we attempt to write yaml::datadir to hieradata. self.assertTrue(mock_dst.write_text.called) @mock.patch('charms.layer.apache_bigtop_base.utils.run_as') @mock.patch('charms.layer.apache_bigtop_base.yaml') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.site_yaml') @mock.patch('charms.layer.apache_bigtop_base.Path') def test_render_site_yaml(self, mock_path, mock_site, mock_yaml, mock_run): ''' Verify that we attempt to put together a plausible site yaml config, before writing it out to a (mocked) yaml file. ''' # Setup mock_yaml.load.return_value = defaultdict(lambda: {}) config = {'roles': None, 'overrides': None, 'hosts': None} def verify_yaml(yaml, *args, **kwargs): ''' Verify that the dict we are trying to dump to yaml has the values that we expect. ''' self.assertTrue('bigtop::bigtop_repo_uri' in yaml) if config['roles'] is None: self.assertFalse('bigtop::roles_enabled' in yaml) else: self.assertTrue('bigtop::roles_enabled' in yaml) self.assertTrue('bigtop::roles' in yaml) self.assertEqual(yaml['bigtop::roles'], sorted(config['roles'])) if config['overrides'] is not None: for key in config['overrides']: self.assertTrue(yaml.get(key) == config['overrides'][key]) mock_yaml.dump.side_effect = verify_yaml # Test various permutations of arguments passed in. for config_set in [ { 'roles': ['foo', 'bar', 'baz'] }, # Test roles { 'overrides': { 'foo': 'bar' } } ]: # Test override config.update(config_set) # Test self.bigtop.render_site_yaml(roles=config['roles'], overrides=config['overrides'], hosts=config['hosts']) # Reset mock_yaml.load.return_value = defaultdict(lambda: {}) config['roles'] = None config['overrides'] = None config['hosts'] = None def test_queue_puppet(self): '''Verify that we set the expected 'puppet queued' state.''' self.bigtop.queue_puppet() self.assertTrue(is_state('apache-bigtop-base.puppet_queued')) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.trigger_puppet') @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_version', new_callable=mock.PropertyMock) def test_handle_queued_puppet(self, mock_ver, mock_hookenv, mock_trigger): ''' Verify that we attempt to call puppet when it has been queued, and then clear the queued state. ''' set_state('apache-bigtop-base.puppet_queued') mock_ver.return_value = '1.2.0' Bigtop._handle_queued_puppet() self.assertTrue(mock_trigger.called) self.assertFalse(is_state('apache-bigtop-base.puppet_queued')) @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.chdir') @mock.patch('charms.layer.apache_bigtop_base.unitdata') def test_trigger_puppet(self, mock_unit, mock_chdir, mock_utils): ''' Test to verify that we attempt to trigger puppet correctly. ''' def verify_utils_call(user, puppet, *args): self.assertEqual(user, 'root') self.assertEqual(puppet, 'puppet') mock_kv = mock.Mock() mock_unit.kv.return_value = mock_kv mock_kv.get.return_value = 'foo' mock_utils.run_as.side_effect = verify_utils_call self.bigtop.trigger_puppet() self.assertTrue(mock_utils.run_as.called) # TODO: verify the Java 1.7 logic. @mock.patch('charms.layer.apache_bigtop_base.subprocess') @mock.patch('charms.layer.apache_bigtop_base.utils.run_as') def test_check_hdfs_setup(self, mock_run, mock_sub): ''' Verify that our hdfs setup check works as expected, and handles errors as expected. ''' class MockException(Exception): pass mock_sub.CalledProcessError = MockException def mock_raise(*args, **kwargs): raise MockException('foo!') for s in ['ubuntu', ' ubuntu ', 'ubuntu ', ' ubuntu']: mock_run.return_value = s self.assertTrue(self.bigtop.check_hdfs_setup()) for s in ['foo', ' ', '', ' bar', 'notubuntu', 'ubuntu not ']: mock_run.return_value = s self.assertFalse(self.bigtop.check_hdfs_setup()) mock_run.side_effect = mock_raise self.assertFalse(self.bigtop.check_hdfs_setup()) @unittest.skip('noop') def test_spec(self): '''Nothing to test that the linter won't handle.''' @mock.patch('charms.layer.apache_bigtop_base.subprocess') @mock.patch('charms.layer.apache_bigtop_base.utils.run_as') @mock.patch('charms.layer.apache_bigtop_base.chdir') @mock.patch('charms.layer.apache_bigtop_base.chownr') @mock.patch('charms.layer.apache_bigtop_base.layer.options') def test_run_smoke_tests(self, mock_options, mock_ownr, mock_chdir, mock_run, mock_sub): ''' Verify that we attempt to run smoke tests correctly, and handle exceptions as expected. ''' mock_options.return_value = {} # Returns None if bigtop isn't available. remove_state('bigtop.available') self.assertEqual(None, self.bigtop.run_smoke_tests()) # Returns None if we don't pass in a 'smoke_components' arg set_state('bigtop.available') self.assertEqual(None, self.bigtop.run_smoke_tests()) # Should return 'success' if all went well. self.assertEqual( self.bigtop.run_smoke_tests(smoke_components=['foo', 'bar']), 'success') # Should return error message if subprocess raised an Exception. class MockException(Exception): pass MockException.output = "test output" mock_sub.CalledProcessError = MockException def mock_raise(*args, **kwargs): raise MockException('foo!') mock_run.side_effect = mock_raise self.assertEqual( self.bigtop.run_smoke_tests(smoke_components=['foo', 'bar']), "test output") @mock.patch('charms.layer.apache_bigtop_base.Bigtop.update_bigtop_repo') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.render_hiera_yaml') @mock.patch('charms.layer.apache_bigtop_base.Path') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.pin_bigtop_packages') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.trigger_puppet') @mock.patch('charms.layer.apache_bigtop_base.subprocess') def test_reinstall_repo_packages(self, mock_sub, mock_trigger, mock_pin, mock_path, mock_hiera, mock_update): ''' Verify that we attempt to trigger puppet during a reinstall, and handle exceptions as expected. ''' class MockException(Exception): pass MockException.output = "test output" mock_sub.CalledProcessError = MockException def mock_raise(*args, **kwargs): raise MockException('foo!') # Should return error message if apt-get remove raised an Exception. mock_sub.check_call.side_effect = mock_raise self.assertEqual( self.bigtop.reinstall_repo_packages(remove_pkgs='foo bar-*'), "test output") # Should call pin twice if trigger puppet fails (once to raise prio, # once again to drop it back down) mock_trigger.side_effect = mock_raise self.assertEqual(self.bigtop.reinstall_repo_packages(), 'failed') self.assertEqual(mock_pin.call_count, 2) # Should return 'success' if all went well. mock_trigger.side_effect = None self.assertEqual(self.bigtop.reinstall_repo_packages(), 'success') def test_get_ip_for_interface(self): ''' Test to verify that our get_ip_for_interface method does sensible things. ''' ip = self.bigtop.get_ip_for_interface('lo') self.assertEqual(ip, '127.0.0.1') ip = self.bigtop.get_ip_for_interface('127.0.0.0/24') self.assertEqual(ip, '127.0.0.1') # If passed 0.0.0.0, or something similar, the function should # treat it as a special case, and return what it was passed. for i in ['0.0.0.0', '0.0.0.0/0', '0/0', '::']: ip = self.bigtop.get_ip_for_interface(i) self.assertEqual(ip, i) self.assertRaises(BigtopError, self.bigtop.get_ip_for_interface, '2.2.2.0/24') self.assertRaises(BigtopError, self.bigtop.get_ip_for_interface, 'foo')
class TestBigtopUnit(Harness): ''' Unit tests for Bigtop class. ''' @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_version', new_callable=mock.PropertyMock) def setUp(self, mock_ver, mock_hookenv): mock_ver.return_value = '1.2.0' super(TestBigtopUnit, self).setUp() self.bigtop = Bigtop() def test_init(self): ''' Verify that the Bigtop class can init itself, and that it has some of the properties that we expect.. ''' # paths should be Path objects. self.assertEqual(type(self.bigtop.bigtop_base), Path) self.assertEqual(type(self.bigtop.site_yaml), Path) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.render_hiera_yaml') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.apply_patches') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.install_puppet_modules') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.fetch_bigtop_release') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.check_reverse_dns') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.check_localdomain') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.pin_bigtop_packages') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.install_java') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.install_swap') @mock.patch('charms.layer.apache_bigtop_base.is_container') def test_install(self, mock_container, mock_swap, mock_java, mock_pin, mock_local, mock_dns, mock_fetch, mock_puppet, mock_apply, mock_hiera): ''' Verify install calls expected class methods. ''' mock_container.return_value = False self.bigtop.install() self.assertTrue(mock_swap.called) self.assertTrue(mock_java.called) self.assertTrue(mock_pin.called) self.assertTrue(mock_local.called) self.assertTrue(mock_dns.called) self.assertTrue(mock_fetch.called) self.assertTrue(mock_puppet.called) self.assertTrue(mock_apply.called) self.assertTrue(mock_hiera.called) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.update_bigtop_repo') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.apply_patches') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.fetch_bigtop_release') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.pin_bigtop_packages') def test_refresh_bigtop_release(self, mock_pin, mock_fetch, mock_apply, mock_update): ''' Verify refresh calls expected class methods. ''' self.bigtop.refresh_bigtop_release() self.assertTrue(mock_pin.called) self.assertTrue(mock_fetch.called) self.assertTrue(mock_apply.called) self.assertTrue(mock_update.called) @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.lsb_release') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_version', new_callable=mock.PropertyMock) def test_get_repo_url(self, mock_ver, mock_lsb_release, mock_utils, mock_hookenv): ''' Verify that we setup an appropriate repository. ''' mock_ver.return_value = '1.1.0' # non-ubuntu should throw an exception mock_lsb_release.return_value = {'DISTRIB_ID': 'centos'} self.assertRaises( BigtopError, self.bigtop.get_repo_url, '1.1.0') # 1.1.0 on trusty/non-power mock_utils.cpu_arch.return_value = 'foo' mock_lsb_release.return_value = {'DISTRIB_ID': 'ubuntu'} self.assertEqual(self.bigtop.get_repo_url('1.1.0'), ('http://bigtop-repos.s3.amazonaws.com/releases/' '1.1.0/ubuntu/trusty/foo')) # 1.1.0 on trusty/power (should return vivid url) mock_utils.cpu_arch.return_value = 'ppc64le' self.assertEqual(self.bigtop.get_repo_url('1.1.0'), ('http://bigtop-repos.s3.amazonaws.com/releases/' '1.1.0/ubuntu/vivid/ppc64el')) # 1.2.0 on xenial mock_ver.return_value = '1.2.0' mock_utils.cpu_arch.return_value = 'foo' mock_lsb_release.return_value = {'DISTRIB_ID': 'ubuntu'} self.assertEqual(self.bigtop.get_repo_url('1.2.0'), ('http://bigtop-repos.s3.amazonaws.com/releases/' '1.2.0/ubuntu/16.04/foo')) # 1.2.1 on xenial/intel mock_hookenv.return_value = {'name': 'foo'} mock_ver.return_value = '1.2.1' mock_utils.cpu_arch.return_value = 'x86_64' self.assertEqual(self.bigtop.get_repo_url('1.2.1'), ('http://repos.bigtop.apache.org/releases/' '1.2.1/ubuntu/16.04/x86_64')) # 1.2.1 on xenial/non-intel mock_ver.return_value = '1.2.1' mock_utils.cpu_arch.return_value = 'foo' self.assertEqual(self.bigtop.get_repo_url('1.2.1'), ('https://ci.bigtop.apache.org/job/Bigtop-1.2.1/' 'OS=ubuntu-16.04/lastSuccessfulBuild/artifact/output/apt')) # master on xenial/intel mock_ver.return_value = 'master' mock_utils.cpu_arch.return_value = 'x86_64' self.assertEqual(self.bigtop.get_repo_url('master'), ('https://ci.bigtop.apache.org/job/Bigtop-trunk-repos/' 'OS=ubuntu-16.04,label=docker-slave/ws/output/apt')) # master on xenial/non-intel mock_ver.return_value = 'master' mock_utils.cpu_arch.return_value = 'foo' self.assertEqual(self.bigtop.get_repo_url('master'), ('https://ci.bigtop.apache.org/job/Bigtop-trunk-repos/' 'OS=ubuntu-16.04-foo,label=docker-slave/ws/output/apt')) # test bad version on xenial should throw an exception self.assertRaises( BigtopError, self.bigtop.get_repo_url, '0.0.0') @mock.patch('charms.layer.apache_bigtop_base.subprocess') def test_install_swap_when_swap_exists(self, mock_sub): ''' Verify we do attempt to install swap space if it already exists. ''' mock_sub.check_output.return_value = b"foo\nbar" mock_sub.reset_mock() self.bigtop.install_swap() # We reset the mock, so here we're verifying no other subprocess # calls were made. mock_sub.check_call.assert_not_called() @mock.patch('charms.layer.apache_bigtop_base.lsb_release') @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.fetch') @mock.patch('charms.layer.apache_bigtop_base.layer.options') def test_install_java(self, mock_options, mock_fetch, mock_utils, mock_lsb_release): ''' Test to verify that we install java when requested. ''' mock_lsb_release.return_value = {'DISTRIB_CODENAME': 'xenial'} # Should be noop if install_java layer opt is not set. self.bigtop.options.get.return_value = '' self.bigtop.install_java() self.assertFalse(mock_fetch.add_source.called) self.assertFalse(mock_fetch.apt_update.called) self.assertFalse(mock_fetch.apt_install.called) self.assertFalse(mock_utils.re_edit_in_place.called) # Should apt install if install_java layer opt is set. self.bigtop.options.get.return_value = 'foo' print("options: {}".format(self.bigtop.options)) self.bigtop.install_java() self.assertFalse(mock_fetch.add_source.called) self.assertFalse(mock_fetch.apt_update.called) self.assertTrue(mock_fetch.apt_install.called) self.assertTrue(mock_utils.re_edit_in_place.called) # On trusty, should add a ppa so that we can install Java 8. mock_lsb_release.return_value = {'DISTRIB_CODENAME': 'trusty'} self.bigtop.install_java() self.assertTrue(mock_fetch.add_source.called) self.assertTrue(mock_fetch.apt_update.called) @mock.patch('charms.layer.apache_bigtop_base.Path') def test_pin_bigtop_packages(self, mock_path): ''' Verify the apt template is opened and written to a (mocked) file. ''' mock_dst = mock.Mock() mock_path.return_value = mock_dst self.bigtop.pin_bigtop_packages(priority=100) self.assertTrue(mock_dst.write_text.called) @mock.patch('charms.layer.apache_bigtop_base.subprocess') @mock.patch('charms.layer.apache_bigtop_base.lsb_release') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_apt', new_callable=mock.PropertyMock) def test_update_bigtop_repo(self, mock_apt, mock_lsb_release, mock_sub): ''' Verify a bigtop apt repository is added/removed. ''' # non-ubuntu should not invoke a subprocess call mock_lsb_release.return_value = {'DISTRIB_CODENAME': 'foo', 'DISTRIB_ID': 'centos', 'DISTRIB_RELEASE': '7'} self.bigtop.update_bigtop_repo() mock_sub.check_call.assert_not_called() # verify args when adding a repo on ubuntu mock_apt.return_value = 'foo' mock_lsb_release.return_value = {'DISTRIB_CODENAME': 'xenial', 'DISTRIB_ID': 'ubuntu', 'DISTRIB_RELEASE': '16.04'} self.bigtop.update_bigtop_repo() mock_sub.check_call.assert_called_with( ['add-apt-repository', '-yu', 'deb foo bigtop contrib']) # verify args when removing a repo on ubuntu self.bigtop.update_bigtop_repo(remove=True) mock_sub.check_call.assert_called_with( ['add-apt-repository', '-yur', 'deb foo bigtop contrib']) # verify we handle check_call errors class MockException(Exception): pass mock_sub.CalledProcessError = MockException def mock_raise(*args, **kwargs): raise MockException('foo!') mock_sub.check_call.side_effect = mock_raise self.bigtop.update_bigtop_repo() @mock.patch('charms.layer.apache_bigtop_base.get_package_version') @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.subprocess.Popen') @mock.patch('charms.layer.apache_bigtop_base.lsb_release') def test_check_bigtop_repo_package(self, mock_lsb_release, mock_sub, mock_hookenv, mock_pkg_ver): ''' Verify bigtop repo package queries. ''' # non-ubuntu should raise an error mock_lsb_release.return_value = {'DISTRIB_CODENAME': 'foo', 'DISTRIB_ID': 'centos', 'DISTRIB_RELEASE': '7'} self.assertRaises(BigtopError, self.bigtop.check_bigtop_repo_package, 'foo') # reset with ubuntu mock_lsb_release.return_value = {'DISTRIB_CODENAME': 'xenial', 'DISTRIB_ID': 'ubuntu', 'DISTRIB_RELEASE': '16.04'} madison_proc = mock.Mock() grep_proc = mock.Mock() # simulate a missing repo pkg grep_attrs = {'communicate.return_value': (b'', 'stderr')} grep_proc.configure_mock(**grep_attrs) # test a missing repo pkg (message should be logged) mock_sub.return_value = madison_proc mock_sub.return_value = grep_proc mock_pkg_ver.return_value = '' self.assertEqual(None, self.bigtop.check_bigtop_repo_package('foo')) mock_hookenv.log.assert_called_once() mock_hookenv.reset_mock() # reset our grep args to simulate the repo pkg being found grep_attrs = {'communicate.return_value': (b'pkg|1|repo', 'stderr')} grep_proc.configure_mock(**grep_attrs) # test a missing installed pkg (no log message) mock_sub.return_value = madison_proc mock_sub.return_value = grep_proc mock_pkg_ver.return_value = '' self.assertEqual('1', self.bigtop.check_bigtop_repo_package('foo')) mock_hookenv.log.assert_not_called() mock_hookenv.reset_mock() # test repo and installed pkg versions are the same (no log message) mock_sub.return_value = madison_proc mock_sub.return_value = grep_proc mock_pkg_ver.return_value = '1' self.assertEqual(None, self.bigtop.check_bigtop_repo_package('foo')) mock_hookenv.log.assert_not_called() mock_hookenv.reset_mock() # test repo pkg is newer than installed pkg (no log message) mock_sub.return_value = madison_proc mock_sub.return_value = grep_proc mock_pkg_ver.return_value = '0' self.assertEqual('1', self.bigtop.check_bigtop_repo_package('foo')) mock_hookenv.log.assert_not_called() mock_hookenv.reset_mock() @mock.patch('charms.layer.apache_bigtop_base.socket') @mock.patch('charms.layer.apache_bigtop_base.subprocess') @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.hookenv') def test_check_reverse_dns(self, mock_hookenv, mock_utils, mock_sub, mock_socket): ''' Verify that we set the reverse_dns_ok state, and handle errors correctly. ''' # Test the case where things succeed. mock_sub.check_output.return_value = b'domain' self.bigtop.check_reverse_dns() self.assertTrue(unitdata.kv().get('reverse_dns_ok')) # Test the case where we get an exception. mock_sub.check_output.return_value = b'localdomain' self.bigtop.check_reverse_dns() self.assertFalse(unitdata.kv().get('reverse_dns_ok')) class MockHError(Exception): pass def raise_herror(*args, **kwargs): raise MockHError('test') mock_socket.herror = MockHError mock_socket.gethostbyaddr = raise_herror self.bigtop.check_reverse_dns() self.assertFalse(unitdata.kv().get('reverse_dns_ok')) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_version', new_callable=mock.PropertyMock) @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.Path') def test_fetch_bigtop_release(self, mock_path, mock_hookenv, mock_ver): '''Verify we raise an exception if an invalid release is specified.''' mock_hookenv.resource_get.return_value = False mock_ver.return_value = 'foo' self.assertRaises( BigtopError, self.bigtop.fetch_bigtop_release) @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.hookenv') def test_install_puppet_modules(self, mock_hookenv, mock_utils): '''Verify that we seem to install puppet modules correctly.''' mock_hookenv.charm_dir.return_value = '/tmp' def mock_run_as(user, *args): ''' Verify that we run puppet as root. ''' self.assertEqual(user, 'root') mock_utils.run_as.side_effect = mock_run_as self.bigtop.install_puppet_modules() @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.glob') @mock.patch('charms.layer.apache_bigtop_base.chdir') def test_apply_patches(self, mock_chdir, mock_glob, mock_utils, mock_hookenv): ''' Verify that we apply patches in the correct order. ''' mock_hookenv.charm_dir.return_value = '/tmp' reverse_sorted = ['foo', 'baz', 'bar'] mock_glob.return_value = ['foo', 'baz', 'bar'] def mock_run_as(*args): patch = args[-1] self.assertEqual(args[0], 'root') # Verify that we're running on a sorted list. self.assertTrue(patch.endswith(reverse_sorted.pop())) mock_utils.run_as.side_effect = mock_run_as self.bigtop.apply_patches() @mock.patch('charms.layer.apache_bigtop_base.yaml') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_base') @mock.patch('charms.layer.apache_bigtop_base.Path') def test_render_hiera_yaml(self, mock_path, mock_base, mock_yaml): ''' Verify that we attempt to add the values that we expect our hiera object, before writing it out to a (mocked) yaml file. ''' def mock_dump(hiera_yaml, *args, **kwargs): self.assertTrue(hiera_yaml.get(':yaml')) self.assertTrue(':datadir' in hiera_yaml[':yaml']) mock_yaml.dump.side_effect = mock_dump mock_dst = mock.Mock() mock_path.return_value = mock_dst mock_yaml.load.return_value = defaultdict(lambda: {}) mock_base.__div__.side_effect = lambda rel: mock_base mock_base.__truediv__.side_effect = lambda rel: mock_base self.bigtop.render_hiera_yaml() # Verify that we attempt to write yaml::datadir to hieradata. self.assertTrue(mock_dst.write_text.called) @mock.patch('charms.layer.apache_bigtop_base.utils.run_as') @mock.patch('charms.layer.apache_bigtop_base.yaml') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.site_yaml') @mock.patch('charms.layer.apache_bigtop_base.Path') def test_render_site_yaml(self, mock_path, mock_site, mock_yaml, mock_run): ''' Verify that we attempt to put together a plausible site yaml config, before writing it out to a (mocked) yaml file. ''' # Setup mock_yaml.load.return_value = defaultdict(lambda: {}) config = { 'roles': None, 'overrides': None, 'hosts': None } def verify_yaml(yaml, *args, **kwargs): ''' Verify that the dict we are trying to dump to yaml has the values that we expect. ''' self.assertTrue('bigtop::bigtop_repo_uri' in yaml) if config['roles'] is None: self.assertFalse('bigtop::roles_enabled' in yaml) else: self.assertTrue('bigtop::roles_enabled' in yaml) self.assertTrue('bigtop::roles' in yaml) self.assertEqual( yaml['bigtop::roles'], sorted(config['roles']) ) if config['overrides'] is not None: for key in config['overrides']: self.assertTrue(yaml.get(key) == config['overrides'][key]) mock_yaml.dump.side_effect = verify_yaml # Test various permutations of arguments passed in. for config_set in [ {'roles': ['foo', 'bar', 'baz']}, # Test roles {'overrides': {'foo': 'bar'}}]: # Test override config.update(config_set) # Test self.bigtop.render_site_yaml( roles=config['roles'], overrides=config['overrides'], hosts=config['hosts']) # Reset mock_yaml.load.return_value = defaultdict(lambda: {}) config['roles'] = None config['overrides'] = None config['hosts'] = None def test_queue_puppet(self): '''Verify that we set the expected 'puppet queued' state.''' self.bigtop.queue_puppet() self.assertTrue(is_state('apache-bigtop-base.puppet_queued')) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.trigger_puppet') @mock.patch('charms.layer.apache_bigtop_base.hookenv') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.bigtop_version', new_callable=mock.PropertyMock) def test_handle_queued_puppet(self, mock_ver, mock_hookenv, mock_trigger): ''' Verify that we attempt to call puppet when it has been queued, and then clear the queued state. ''' set_state('apache-bigtop-base.puppet_queued') mock_ver.return_value = '1.2.0' Bigtop._handle_queued_puppet() self.assertTrue(mock_trigger.called) self.assertFalse(is_state('apache-bigtop-base.puppet_queued')) @mock.patch('charms.layer.apache_bigtop_base.utils') @mock.patch('charms.layer.apache_bigtop_base.chdir') @mock.patch('charms.layer.apache_bigtop_base.unitdata') def test_trigger_puppet(self, mock_unit, mock_chdir, mock_utils): ''' Test to verify that we attempt to trigger puppet correctly. ''' def verify_utils_call(user, puppet, *args): self.assertEqual(user, 'root') self.assertEqual(puppet, 'puppet') mock_kv = mock.Mock() mock_unit.kv.return_value = mock_kv mock_kv.get.return_value = 'foo' mock_utils.run_as.side_effect = verify_utils_call self.bigtop.trigger_puppet() self.assertTrue(mock_utils.run_as.called) # TODO: verify the Java 1.7 logic. @mock.patch('charms.layer.apache_bigtop_base.subprocess') @mock.patch('charms.layer.apache_bigtop_base.utils.run_as') def test_check_hdfs_setup(self, mock_run, mock_sub): ''' Verify that our hdfs setup check works as expected, and handles errors as expected. ''' class MockException(Exception): pass mock_sub.CalledProcessError = MockException def mock_raise(*args, **kwargs): raise MockException('foo!') for s in ['ubuntu', ' ubuntu ', 'ubuntu ', ' ubuntu']: mock_run.return_value = s self.assertTrue(self.bigtop.check_hdfs_setup()) for s in ['foo', ' ', '', ' bar', 'notubuntu', 'ubuntu not ']: mock_run.return_value = s self.assertFalse(self.bigtop.check_hdfs_setup()) mock_run.side_effect = mock_raise self.assertFalse(self.bigtop.check_hdfs_setup()) @unittest.skip('noop') def test_spec(self): '''Nothing to test that the linter won't handle.''' @mock.patch('charms.layer.apache_bigtop_base.subprocess') @mock.patch('charms.layer.apache_bigtop_base.utils.run_as') @mock.patch('charms.layer.apache_bigtop_base.chdir') @mock.patch('charms.layer.apache_bigtop_base.chownr') @mock.patch('charms.layer.apache_bigtop_base.layer.options') def test_run_smoke_tests(self, mock_options, mock_ownr, mock_chdir, mock_run, mock_sub): ''' Verify that we attempt to run smoke tests correctly, and handle exceptions as expected. ''' mock_options.return_value = {} # Returns None if bigtop isn't available. remove_state('bigtop.available') self.assertEqual(None, self.bigtop.run_smoke_tests()) # Returns None if we don't pass in a 'smoke_components' arg set_state('bigtop.available') self.assertEqual(None, self.bigtop.run_smoke_tests()) # Should return 'success' if all went well. self.assertEqual( self.bigtop.run_smoke_tests(smoke_components=['foo', 'bar']), 'success' ) # Should return error message if subprocess raised an Exception. class MockException(Exception): pass MockException.output = "test output" mock_sub.CalledProcessError = MockException def mock_raise(*args, **kwargs): raise MockException('foo!') mock_run.side_effect = mock_raise self.assertEqual( self.bigtop.run_smoke_tests(smoke_components=['foo', 'bar']), "test output" ) @mock.patch('charms.layer.apache_bigtop_base.Bigtop.update_bigtop_repo') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.render_hiera_yaml') @mock.patch('charms.layer.apache_bigtop_base.Path') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.pin_bigtop_packages') @mock.patch('charms.layer.apache_bigtop_base.Bigtop.trigger_puppet') @mock.patch('charms.layer.apache_bigtop_base.subprocess') def test_reinstall_repo_packages(self, mock_sub, mock_trigger, mock_pin, mock_path, mock_hiera, mock_update): ''' Verify that we attempt to trigger puppet during a reinstall, and handle exceptions as expected. ''' class MockException(Exception): pass MockException.output = "test output" mock_sub.CalledProcessError = MockException def mock_raise(*args, **kwargs): raise MockException('foo!') # Should return error message if apt-get remove raised an Exception. mock_sub.check_call.side_effect = mock_raise self.assertEqual( self.bigtop.reinstall_repo_packages(remove_pkgs='foo bar-*'), "test output" ) # Should call pin twice if trigger puppet fails (once to raise prio, # once again to drop it back down) mock_trigger.side_effect = mock_raise self.assertEqual(self.bigtop.reinstall_repo_packages(), 'failed') self.assertEqual(mock_pin.call_count, 2) # Should return 'success' if all went well. mock_trigger.side_effect = None self.assertEqual(self.bigtop.reinstall_repo_packages(), 'success') def test_get_ip_for_interface(self): ''' Test to verify that our get_ip_for_interface method does sensible things. ''' ip = self.bigtop.get_ip_for_interface('lo') self.assertEqual(ip, '127.0.0.1') ip = self.bigtop.get_ip_for_interface('127.0.0.0/24') self.assertEqual(ip, '127.0.0.1') # If passed 0.0.0.0, or something similar, the function should # treat it as a special case, and return what it was passed. for i in ['0.0.0.0', '0.0.0.0/0', '0/0', '::']: ip = self.bigtop.get_ip_for_interface(i) self.assertEqual(ip, i) self.assertRaises( BigtopError, self.bigtop.get_ip_for_interface, '2.2.2.0/24') self.assertRaises( BigtopError, self.bigtop.get_ip_for_interface, 'foo')