def test_404_connection(self): """Verify the apache status module gets disabled when hardening apache. Ported from amulet tests. """ logging.info('Checking apache mod_status gets disabled.') unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') keystone_unit = zaza_model.get_lead_unit_name('keystone') dashboard_relation = openstack_juju.get_relation_from_unit( keystone_unit, unit_name, 'identity-service') dashboard_ip = dashboard_relation['private-address'] logging.debug("... dashboard_ip is:{}".format(dashboard_ip)) logging.debug('Maybe enabling hardening for apache...') _app_config = zaza_model.get_application_config(self.application_name) logging.info(_app_config['harden']) with self.config_change( {'harden': _app_config['harden'].get('value', '')}, {'harden': 'apache'}): try: urllib.request.urlopen( 'http://{}/server-status'.format(dashboard_ip)) except urllib.request.HTTPError as e: if e.code == 404: return # test failed if it didn't return 404 msg = "Apache mod_status check failed." assert False, msg
def _get_dashboard_ip(): """Get the IP of the dashboard. :returns: The IP of the dashboard :rtype: str """ unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') keystone_unit = zaza_model.get_lead_unit_name('keystone') dashboard_relation = openstack_juju.get_relation_from_unit( keystone_unit, unit_name, 'identity-service') dashboard_ip = dashboard_relation['private-address'] logging.debug("dashboard_ip is: {}".format(dashboard_ip)) return dashboard_ip
def test_get_lead_unit_name(self): self.patch_object(model, 'get_juju_model', return_value='mname') self.patch_object(model, 'get_units') self.get_units.return_value = self.units self.patch_object(model, 'Model') self.Model.return_value = self.Model_mock self.assertEqual(model.get_lead_unit_name('app', 'model'), 'app/4')
def get_client_and_attempt_operation(self, ip): """Attempt to list users on the openstack-dashboard service. This is slightly complicated in that the client is actually a web-site. Thus, the test has to login first and then attempt the operation. This makes the test a little more complicated. :param ip: the IP address to get the session against. :type ip: str :raises: PolicydOperationFailedException if operation fails. """ unit = zaza_model.get_unit_from_name( zaza_model.get_lead_unit_name(self.application_name)) logging.info("Dashboard is at {}".format(unit.public_address)) overcloud_auth = openstack_utils.get_overcloud_auth() password = overcloud_auth['OS_PASSWORD'], logging.info("admin password is {}".format(password)) # try to get the url which will either pass or fail with a 403 overcloud_auth = openstack_utils.get_overcloud_auth() domain = 'admin_domain', username = '******', password = overcloud_auth['OS_PASSWORD'], client, response = _login(self.get_horizon_url(), domain, username, password) # now attempt to get the domains page _url = self.url.format(unit.public_address) result = client.get(_url) if result.status_code == 403: raise policyd.PolicydOperationFailedException("Not authenticated")
def test_404_connection(self): """Verify the apache status module gets disabled when hardening apache. Ported from amulet tests. """ logging.info('Checking apache mod_status gets disabled.') unit = zaza_model.get_unit_from_name( zaza_model.get_lead_unit_name(self.application_name)) logging.debug("... dashboard_ip is: {}".format(unit.public_address)) logging.debug('Maybe enabling hardening for apache...') _app_config = zaza_model.get_application_config(self.application_name) logging.info(_app_config['harden']) request = urllib.request.Request('http://{}/server-status'.format( unit.public_address)) with self.config_change( {'harden': _app_config['harden'].get('value', '')}, {'harden': 'apache'}): try: _do_request(request) except urllib.request.HTTPError as e: # test failed if it didn't return 404 msg = "Apache mod_status check failed." self.assertEqual(e.code, 404, msg) logging.info('OK')
def setUpClass(cls, application_name=None, model_alias=None): """Run setup for test class to create common resources.""" cls.model_aliases = model.get_juju_model_aliases() if model_alias: cls.model_name = cls.model_aliases[model_alias] else: cls.model_name = model.get_juju_model() cls.test_config = lifecycle_utils.get_charm_config(fatal=False) if application_name: cls.application_name = application_name else: charm_under_test_name = cls.test_config['charm_name'] deployed_app_names = model.sync_deployed(model_name=cls.model_name) if charm_under_test_name in deployed_app_names: # There is an application named like the charm under test. # Let's consider it the application under test: cls.application_name = charm_under_test_name else: # Let's search for any application whose name starts with the # name of the charm under test and assume it's the application # under test: for app_name in deployed_app_names: if app_name.startswith(charm_under_test_name): cls.application_name = app_name break else: logging.warning('Could not find application under test') return cls.lead_unit = model.get_lead_unit_name(cls.application_name, model_name=cls.model_name) logging.debug('Leader unit is {}'.format(cls.lead_unit))
def setUpClass(cls): """Run setup for test class to create common resourcea.""" cls.keystone_session = openstack_utils.get_overcloud_keystone_session() cls.model_name = model.get_juju_model() cls.test_config = lifecycle_utils.get_charm_config() cls.application_name = cls.test_config['charm_name'] cls.lead_unit = model.get_lead_unit_name(cls.application_name, model_name=cls.model_name) logging.debug('Leader unit is {}'.format(cls.lead_unit))
def setUpClass(cls): """Run setup for tests.""" cls.model_name = model.get_juju_model() cls.application_name = "nrpe" cls.lead_unit_name = model.get_lead_unit_name( cls.application_name, model_name=cls.model_name) cls.units = model.get_units(cls.application_name, model_name=cls.model_name) cls.nrpe_ip = model.get_app_ips(cls.application_name)[0]
def setUpClass(cls): super(Test, cls).setUpClass() cls.model_name = model.get_juju_model() cls.application_name = "lldpd" cls.lead_unit_name = model.get_lead_unit_name( cls.application_name, model_name=cls.model_name) cls.units = model.get_units(cls.application_name, model_name=cls.model_name) logging.debug("Leader unit is {}".format(cls.lead_unit_name))
def setUpClass(cls, application_name=None, model_alias=None): """Run setup for test class to create common resources. Note: the derived class may not use the application_name; if it's set to None then this setUpClass() method will attempt to extract the application name from the charm_config (essentially the test.yaml) using the key 'charm_name' in the test_config. If that isn't present, then there will be no application_name set, and this is considered a generic scenario of a whole model rather than a particular charm under test. :param application_name: the name of the applications that the derived class is testing. If None, then it's a generic test not connected to any single charm. :type application_name: Optional[str] :param model_alias: the alias to use if needed. :type model_alias: Optional[str] """ cls.model_aliases = model.get_juju_model_aliases() if model_alias: cls.model_name = cls.model_aliases[model_alias] else: cls.model_name = model.get_juju_model() cls.test_config = lifecycle_utils.get_charm_config(fatal=False) if application_name: cls.application_name = application_name else: try: charm_under_test_name = cls.test_config['charm_name'] except KeyError: logging.warning("No application_name and no charm config so " "not setting the application_name. Likely a " "scenario test.") return deployed_app_names = model.sync_deployed(model_name=cls.model_name) if charm_under_test_name in deployed_app_names: # There is an application named like the charm under test. # Let's consider it the application under test: cls.application_name = charm_under_test_name else: # Let's search for any application whose name starts with the # name of the charm under test and assume it's the application # under test: for app_name in deployed_app_names: if app_name.startswith(charm_under_test_name): cls.application_name = app_name break else: logging.warning('Could not find application under test') return cls.lead_unit = model.get_lead_unit_name( cls.application_name, model_name=cls.model_name) logging.debug('Leader unit is {}'.format(cls.lead_unit))
def test_501_security_checklist_action(self): """Verify expected result on a default install. Ported from amulet tests. """ logging.info("Testing security-checklist") unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') action = zaza_model.run_action(unit_name, 'security-checklist') assert action.data.get(u"status") == "failed", \ "Security check is expected to not pass by default"
def test_prometheus_metrics(self): """Validate that Prometheus has Ceph metrics.""" try: zaza_model.get_application('prometheus2') except KeyError: raise unittest.SkipTest('Prometheus not present, skipping test') unit = zaza_model.get_unit_from_name( zaza_model.get_lead_unit_name('prometheus2')) self.assertEqual('3', _get_mon_count_from_prometheus(unit.public_address))
def test_404_connection(self): """Verify the apache status module gets disabled when hardening apache. Ported from amulet tests. """ logging.info('Checking apache mod_status gets disabled.') unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') keystone_unit = zaza_model.get_lead_unit_name('keystone') dashboard_relation = openstack_juju.get_relation_from_unit( keystone_unit, unit_name, 'identity-service') dashboard_ip = dashboard_relation['private-address'] logging.debug("... dashboard_ip is:{}".format(dashboard_ip)) logging.debug('Maybe enabling hardening for apache...') _app_config = zaza_model.get_application_config(self.application_name) logging.info(_app_config['harden']) # NOTE(ajkavanagh): it seems that apache2 doesn't start quickly enough # for the test, and so it gets reset errors; repeat until either that # stops or there is a failure @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, min=5, max=10), retry=tenacity.retry_if_exception_type( http.client.RemoteDisconnected), reraise=True) def _do_request(): return urllib.request.urlopen( 'http://{}/server-status'.format(dashboard_ip)) with self.config_change( {'harden': _app_config['harden'].get('value', '')}, {'harden': 'apache'}): try: _do_request() except urllib.request.HTTPError as e: # test failed if it didn't return 404 msg = "Apache mod_status check failed." self.assertEqual(e.code, 404, msg) logging.info('OK')
def test_400_connection(self): """Test that dashboard responds to http request. Ported from amulet tests. """ logging.info('Checking dashboard http response...') unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') keystone_unit = zaza_model.get_lead_unit_name('keystone') dashboard_relation = openstack_juju.get_relation_from_unit( keystone_unit, unit_name, 'identity-service') dashboard_ip = dashboard_relation['private-address'] logging.debug("... dashboard_ip is:{}".format(dashboard_ip)) # NOTE(fnordahl) there is a eluding issue that currently makes the # first request to the OpenStack Dashboard error out # with 500 Internal Server Error in CI. Temporarilly # add retry logic to unwedge the gate. This issue # should be revisited and root caused properly when time # allows. @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, min=5, max=10), reraise=True) def do_request(): logging.info("... trying to fetch the page") try: response = urllib.request.urlopen( 'http://{}/horizon'.format(dashboard_ip)) logging.info("... fetched page") except Exception as e: logging.info("... exception raised was {}".format(str(e))) raise return response.read().decode('utf-8') html = do_request() self.assertIn('OpenStack Dashboard', html, "Dashboard frontpage check failed")
def setUpClass(cls, application_name=None, model_alias=None): """Run setup for test class to create common resources.""" cls.model_aliases = model.get_juju_model_aliases() if model_alias: cls.model_name = cls.model_aliases[model_alias] else: cls.model_name = model.get_juju_model() cls.test_config = lifecycle_utils.get_charm_config(fatal=False) if application_name: cls.application_name = application_name else: cls.application_name = cls.test_config['charm_name'] cls.lead_unit = model.get_lead_unit_name(cls.application_name, model_name=cls.model_name) logging.debug('Leader unit is {}'.format(cls.lead_unit))
def test_100_services(self): """Verify the expected services are running. Ported from amulet tests. """ logging.info('Checking openstack-dashboard services...') unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') openstack_services = ['apache2'] services = {} services[unit_name] = openstack_services for unit_name, unit_services in services.items(): zaza_model.block_until_service_status(unit_name=unit_name, services=unit_services, target_status='running')
def test_401_authenticate(self): """Validate that authentication succeeds for client log in.""" logging.info('Checking authentication through dashboard...') unit = zaza_model.get_unit_from_name( zaza_model.get_lead_unit_name(self.application_name)) overcloud_auth = openstack_utils.get_overcloud_auth() password = overcloud_auth['OS_PASSWORD'], logging.info("admin password is {}".format(password)) # try to get the url which will either pass or fail with a 403 overcloud_auth = openstack_utils.get_overcloud_auth() domain = 'admin_domain', username = '******', password = overcloud_auth['OS_PASSWORD'], _login(unit.public_address, domain, username, password) logging.info('OK')
def test_200_haproxy_stats_config(self): """Verify that the HAProxy stats are properly setup.""" logging.info('Checking dashboard HAProxy settings...') unit = zaza_model.get_unit_from_name( zaza_model.get_lead_unit_name(self.application_name)) logging.debug("... dashboard_ip is:{}".format( zaza_model.get_unit_public_address(unit))) conf = '/etc/haproxy/haproxy.cfg' port = '8888' set_alternate = { 'haproxy-expose-stats': 'True', } request = urllib.request.Request('http://{}:{}'.format( zaza_model.get_unit_public_address(unit), port)) output = str(generic_utils.get_file_contents(unit, conf)) password = None for line in output.split('\n'): if "stats auth" in line: password = line.split(':')[1] break else: raise ValueError("'stats auth' not found in output'") base64string = base64.b64encode( bytes('{}:{}'.format('admin', password), 'ascii')) request.add_header("Authorization", "Basic {}".format(base64string.decode('utf-8'))) # Expect default config to not be available externally. expected = 'bind 127.0.0.1:{}'.format(port) self.assertIn(expected, output) with self.assertRaises(urllib.error.URLError): _do_request(request) zaza_model.set_application_config(self.application_name, set_alternate) zaza_model.block_until_all_units_idle(model_name=self.model_name) # Once exposed, expect HAProxy stats to be available externally output = str(generic_utils.get_file_contents(unit, conf)) expected = 'bind 0.0.0.0:{}'.format(port) html = _do_request(request).read().decode(encoding='utf-8') self.assertIn(expected, output) self.assertIn('Statistics Report for HAProxy', html, "HAProxy stats check failed")
def test_302_router_settings(self): """Verify that the horizon router settings are correct. Ported from amulet tests. """ # note this test is only valid after trusty-icehouse; however, all of # the zaza tests are after trusty-icehouse logging.info('Checking dashboard router settings...') unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') conf = ('/usr/share/openstack-dashboard/openstack_dashboard/' 'enabled/_40_router.py') cmd = 'cat {}'.format(conf) output = zaza_model.run_on_unit(unit_name, cmd) expected = { 'DISABLED': "True", } mismatches = self.crude_py_parse(output['Stdout'], expected) assert not mismatches, ("mismatched keys on {} were:\n{}".format( conf, ", ".join(mismatches)))
def get_base_url(self): """Return the base url for http(s) requests. :returns: URL :rtype: str """ vip = (zaza_model.get_application_config( self.application_name).get("vip").get("value")) if vip: ip = vip else: unit = zaza_model.get_unit_from_name( zaza_model.get_lead_unit_name(self.application_name)) ip = unit.public_address logging.debug("Dashboard ip is:{}".format(ip)) scheme = 'http' if self.use_https: scheme = 'https' url = '{}://{}'.format(scheme, ip) return url
def test_400_connection(self): """Test that dashboard responds to http request. Ported from amulet tests. """ logging.info('Checking dashboard http response...') unit = zaza_model.get_unit_from_name( zaza_model.get_lead_unit_name(self.application_name)) logging.debug("... dashboard_ip is:{}".format(unit.public_address)) request = urllib.request.Request('http://{}/horizon'.format( unit.public_address)) try: logging.info("... trying to fetch the page") html = _do_request(request) logging.info("... fetched page") except Exception as e: logging.info("... exception raised was {}".format(str(e))) raise return html.read().decode('utf-8') self.assertIn('OpenStack Dashboard', html, "Dashboard frontpage check failed")
def test_050_local_settings_permissions_regression_check_lp1755027(self): """Assert regression check lp1755027. Assert the intended file permissions on openstack-dashboard's configuration file. Regression coverage for https://bugs.launchpad.net/bugs/1755027. Ported from amulet tests. """ file_path = '/etc/openstack-dashboard/local_settings.py' expected_perms = '640' unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') logging.info('Checking {} permissions...'.format(file_path)) # NOTE(beisner): This could be a new test helper, but it needs # to be a clean backport to stable with high prio, so maybe later. cmd = 'stat -c %a {}'.format(file_path) output = zaza_model.run_on_unit(unit_name, cmd) perms = output['Stdout'].strip() assert perms == expected_perms, \ ('{} perms of {} not expected ones of {}' .format(file_path, perms, expected_perms))
def test_03_user_monitor(self): """Verify user monitors are applied.""" user_monitors = { "version": "0.3", "monitors": { "local": { "procrunning": { "rsync": { "max": 1, "executable": "rsync", "name": "RSYNc Running", "min": 1, }, "jujud": { "max": 1, "executable": "jujud", "name": "Juju Running", "min": 1, }, } }, "remote": { "tcp": { "ssh": { "warning": 2, "critical": 10, "name": "SSH Running", "timeout": 12, "port": 22, "string": "SSH.*", "expect": None, } } }, }, } model.set_application_config(self.application_name, {"monitors": yaml.dump(user_monitors)}) model.block_until_all_units_idle() local_nrpe_checks = { "check_proc_jujud_user.cfg": "command[check_proc_jujud_user]=/usr/lib/nagios/plugins/" "check_procs -w 1 -c 1 -C jujud", "check_proc_rsync_user.cfg": "command[check_proc_rsync_user]=/usr/lib/nagios/plugins/" "check_procs -w 1 -c 1 -C rsync", } for nrpe_check in local_nrpe_checks: logging.info( "Checking content of '{}' nrpe check".format(nrpe_check)) cmd = "cat /etc/nagios/nrpe.d/" + nrpe_check result = model.run_on_unit(self.lead_unit_name, cmd) code = result.get("Code") if code != "0": logging.warning( "Unable to find nrpe check {} at /etc/nagios/nrpe.d/". format(nrpe_check)) raise model.CommandRunFailed(cmd, result) content = result.get("Stdout") self.assertTrue(local_nrpe_checks[nrpe_check] in content) remote_nrpe_checks = { "check_tcp_H_HOSTADDRESS__E_p22_s_SSH____eNone_w2_c10_t12_t10.cfg": "/usr/lib/nagios/plugins/check_tcp -H $HOSTADDRESS$ " "-E -p 22 -s 'SSH.*' -e None -w 2 -c 10 -t 12 -t 10" } for nrpe_check in remote_nrpe_checks: logging.info( "Checking content of '{}' nrpe command in nagios unit".format( nrpe_check)) cmd = "cat /etc/nagios3/conf.d/commands/" + nrpe_check nagios_lead_unit_name = model.get_lead_unit_name( "nagios", model_name=self.model_name) result = model.run_on_unit(nagios_lead_unit_name, cmd) code = result.get("Code") if code != "0": logging.warning( "Unable to find nrpe command {} at " "/etc/nagios3/conf.d/commands/ in nagios unit".format( nrpe_check)) raise model.CommandRunFailed(cmd, result) content = result.get("Stdout") self.assertTrue(remote_nrpe_checks[nrpe_check] in content)
def test_401_authenticate(self): """Validate that authentication succeeds for client log in. Ported from amulet tests. """ logging.info('Checking authentication through dashboard...') unit_name = zaza_model.get_lead_unit_name('openstack-dashboard') keystone_unit = zaza_model.get_lead_unit_name('keystone') dashboard_relation = openstack_juju.get_relation_from_unit( keystone_unit, unit_name, 'identity-service') dashboard_ip = dashboard_relation['private-address'] logging.debug("... dashboard_ip is:{}".format(dashboard_ip)) url = 'http://{}/horizon/auth/login/'.format(dashboard_ip) overcloud_auth = openstack_utils.get_overcloud_auth() if overcloud_auth['OS_AUTH_URL'].endswith("v2.0"): api_version = 2 else: api_version = 3 keystone_client = openstack_utils.get_keystone_client(overcloud_auth) catalog = keystone_client.service_catalog.get_endpoints() logging.info(catalog) if api_version == 2: region = catalog['identity'][0]['publicURL'] else: region = [ i['url'] for i in catalog['identity'] if i['interface'] == 'public' ][0] # NOTE(ajkavanagh) there used to be a trusty/icehouse test in the # amulet test, but as the zaza tests only test from trusty/mitaka # onwards, the test has been dropped if (openstack_utils.get_os_release() >= openstack_utils.get_os_release('bionic_stein')): expect = "Sign Out" # update the in dashboard seems to require region to be default in # this test configuration region = 'default' else: expect = 'Projects - OpenStack Dashboard' # NOTE(thedac) Similar to the connection test above we get occasional # intermittent authentication fails. Wrap in a retry loop. @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, min=5, max=10), retry=tenacity.retry_unless_exception_type( self.AuthExceptions), reraise=True) def _do_auth_check(expect): # start session, get csrftoken client = requests.session() client.get(url) if 'csrftoken' in client.cookies: csrftoken = client.cookies['csrftoken'] else: raise Exception("Missing csrftoken") # build and send post request auth = { 'domain': 'admin_domain', 'username': '******', 'password': overcloud_auth['OS_PASSWORD'], 'csrfmiddlewaretoken': csrftoken, 'next': '/horizon/', 'region': region, } # In the minimal test deployment /horizon/project/ is unauthorized, # this does not occur in a full deployment and is probably due to # services/information missing that horizon wants to display data # for. # Redirect to /horizon/identity/ instead. if (openstack_utils.get_os_release() >= openstack_utils.get_os_release('xenial_queens')): auth['next'] = '/horizon/identity/' if (openstack_utils.get_os_release() >= openstack_utils.get_os_release('bionic_stein')): auth['region'] = 'default' if api_version == 2: del auth['domain'] logging.info('POST data: "{}"'.format(auth)) response = client.post(url, data=auth, headers={'Referer': url}) if expect not in response.text: msg = 'FAILURE code={} text="{}"'.format( response, response.text) # NOTE(thedac) amulet.raise_status exits on exception. # Raise a custom exception. logging.info("Yeah, wen't wrong: {}".format(msg)) raise self.FailedAuth(msg) raise self.PassedAuth() try: _do_auth_check(expect) except self.FailedAuth as e: assert False, str(e) except self.PassedAuth: pass