From f93a153298ebcbe5005a98637b083b5cdb601e4c Mon Sep 17 00:00:00 2001 From: Igor Degtiarov Date: Fri, 1 Jul 2016 19:00:47 +0300 Subject: [PATCH] Add HTTPS test for the toolchain Change-Id: Ib073584d5a38cd6b736f7ab48688c86e0528cbed --- stacklight_tests/base_test.py | 8 ++ stacklight_tests/elasticsearch_kibana/api.py | 20 +++- stacklight_tests/helpers/checkers.py | 14 ++- stacklight_tests/helpers/helpers.py | 31 +++++- stacklight_tests/influxdb_grafana/api.py | 19 +++- .../lma_infrastructure_alerting/api.py | 26 +++-- stacklight_tests/run_tests.py | 1 + .../toolchain/test_https_plugins.py | 98 +++++++++++++++++++ 8 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 stacklight_tests/toolchain/test_https_plugins.py diff --git a/stacklight_tests/base_test.py b/stacklight_tests/base_test.py index aa5176b..40dd24e 100644 --- a/stacklight_tests/base_test.py +++ b/stacklight_tests/base_test.py @@ -120,3 +120,11 @@ class PluginApi(object): self.helpers.power_off_node(target_node) self.helpers.wait_for_vip_migration( target_node, self.settings.role_name, vip_name) + + def get_http_protocol(self, tls_parameter='tls_enabled'): + """Return the HTTP protocol configured for the plugin (http or https). + """ + protocol = 'http' + if self.helpers.get_plugin_setting(self.settings.name, tls_parameter): + protocol = 'https' + return protocol diff --git a/stacklight_tests/elasticsearch_kibana/api.py b/stacklight_tests/elasticsearch_kibana/api.py index d49f295..0dbec8d 100644 --- a/stacklight_tests/elasticsearch_kibana/api.py +++ b/stacklight_tests/elasticsearch_kibana/api.py @@ -24,6 +24,8 @@ class ElasticsearchPluginApi(base_test.PluginApi): def __init__(self): super(ElasticsearchPluginApi, self).__init__() self._es_client = None + self._kibana_port = None + self._kibana_protocol = None @property def es(self): @@ -32,6 +34,21 @@ class ElasticsearchPluginApi(base_test.PluginApi): [{'host': self.get_plugin_vip(), 'port': 9200}]) return self._es_client + @property + def kibana_port(self): + if self._kibana_port is None: + if self.kibana_protocol == 'http': + self._kibana_port = 80 + else: + self._kibana_port = 443 + return self._kibana_port + + @property + def kibana_protocol(self): + if self._kibana_protocol is None: + self._kibana_protocol = self.get_http_protocol() + return self._kibana_protocol + def get_plugin_settings(self): return plugin_settings @@ -51,7 +68,8 @@ class ElasticsearchPluginApi(base_test.PluginApi): return "http://{}:9200/{}".format(self.get_plugin_vip(), path) def get_kibana_url(self): - return "http://{}:80/".format(self.get_plugin_vip()) + return "{0}://{1}:{2}/".format( + self.kibana_protocol, self.get_plugin_vip(), self.kibana_port) def check_plugin_online(self): elasticsearch_url = self.get_elasticsearch_url() diff --git a/stacklight_tests/helpers/checkers.py b/stacklight_tests/helpers/checkers.py index 71adc57..efa36d6 100644 --- a/stacklight_tests/helpers/checkers.py +++ b/stacklight_tests/helpers/checkers.py @@ -17,10 +17,19 @@ import socket from proboscis import asserts import requests +from requests.packages.urllib3 import poolmanager +from stacklight_tests.helpers import helpers from stacklight_tests.helpers import remote_ops +class TestHTTPAdapter(requests.adapters.HTTPAdapter): + """Custom transport adapter to disable host checking in https requests.""" + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = poolmanager.PoolManager(assert_hostname=False) + + def check_http_get_response(url, expected_code=200, msg=None, **kwargs): """Perform a HTTP GET request and assert that the HTTP server replies with the expected code. @@ -34,8 +43,11 @@ def check_http_get_response(url, expected_code=200, msg=None, **kwargs): :returns: HTTP response object :rtype: requests.Response """ + s = requests.Session() + s.mount("https://", TestHTTPAdapter()) + cert = helpers.get_fixture("https/rootCA.pem") msg = msg or "%s responded with {0}, expected {1}" % url - r = requests.get(url, **kwargs) + r = s.get(url, verify=cert, **kwargs) asserts.assert_equal( r.status_code, expected_code, msg.format(r.status_code, expected_code)) return r diff --git a/stacklight_tests/helpers/helpers.py b/stacklight_tests/helpers/helpers.py index 245ebf1..8a726fa 100644 --- a/stacklight_tests/helpers/helpers.py +++ b/stacklight_tests/helpers/helpers.py @@ -111,6 +111,35 @@ class PluginHelper(object): self.env.admin_actions.install_plugin( plugin_file_name=os.path.basename(plugin_path)) + def get_plugin_setting(self, plugin, parameter): + """Return the given parameter's value for the plugin. + + :param plugin: name of the plugin. + :type plugin: str + :param parameter: name of the parameter. + :type name: str + :returns: parameter's value + """ + asserts.assert_true( + self.fuel_web.check_plugin_exists(self.cluster_id, plugin), + "Plugin {0} isn't found.".format(plugin)) + + attributes = self.nailgun_client.get_cluster_attributes( + self.cluster_id) + attributes = attributes['editable'][plugin] + + value = None + for item in attributes['metadata']['versions']: + if (parameter in item and + item['metadata']['plugin_id'] == + attributes['metadata']['chosen_id']): + value = item[parameter]['value'] + break + asserts.assert_is_not_none( + value, "Could not find parameter {0} for plugin {1}".format( + parameter, plugin)) + return value + def activate_plugin(self, name, version, options=None, strict=False): """Enable and configure a plugin for the cluster. @@ -122,7 +151,7 @@ class PluginHelper(object): :type options: dict :param strict: whether or not to fail when setting an unknown option (default: False). - :type options: boolean + :type strict: boolean :returns: None """ if options is None: diff --git a/stacklight_tests/influxdb_grafana/api.py b/stacklight_tests/influxdb_grafana/api.py index e320042..2aa709b 100644 --- a/stacklight_tests/influxdb_grafana/api.py +++ b/stacklight_tests/influxdb_grafana/api.py @@ -26,15 +26,27 @@ class InfluxdbPluginApi(base_test.PluginApi): def __init__(self): super(InfluxdbPluginApi, self).__init__() self._grafana_port = None + self._grafana_protocol = None @property def grafana_port(self): if self._grafana_port is None: - self._grafana_port = 80 + if self.grafana_protocol == 'http': + self._grafana_port = 80 + else: + self._grafana_port = 443 + # TODO(pasquier-s): remove this code once all plugins use the + # standard ports if self.checkers.check_port(self.get_plugin_vip(), 8000): self._grafana_port = 8000 return self._grafana_port + @property + def grafana_protocol(self): + if self._grafana_protocol is None: + self._grafana_protocol = self.get_http_protocol() + return self._grafana_protocol + def get_plugin_settings(self): return plugin_settings @@ -51,8 +63,9 @@ class InfluxdbPluginApi(base_test.PluginApi): return self.helpers.get_plugin_vip(self.settings.vip_name) def get_grafana_url(self, path=''): - return "http://{0}:{1}/{2}".format(self.get_plugin_vip(), - self.grafana_port, path) + return "{0}://{1}:{2}/{3}".format(self.grafana_protocol, + self.get_plugin_vip(), + self.grafana_port, path) def get_influxdb_url(self, path=''): return "http://{0}:8086/{1}".format(self.get_plugin_vip(), path) diff --git a/stacklight_tests/lma_infrastructure_alerting/api.py b/stacklight_tests/lma_infrastructure_alerting/api.py index 45f735b..9092db9 100644 --- a/stacklight_tests/lma_infrastructure_alerting/api.py +++ b/stacklight_tests/lma_infrastructure_alerting/api.py @@ -28,15 +28,27 @@ class InfraAlertingPluginApi(base_test.PluginApi): def __init__(self): super(InfraAlertingPluginApi, self).__init__() self._nagios_port = None + self._nagios_protocol = None @property def nagios_port(self): if self._nagios_port is None: - self._nagios_port = 80 + if self.nagios_protocol == 'http': + self._nagios_port = 80 + else: + self._nagios_port = 443 + # TODO(pasquier-s): remove this code once all plugins use the + # standard ports if self.checkers.check_port(self.get_plugin_vip(), 8001): self._nagios_port = 8001 return self._nagios_port + @property + def nagios_protocol(self): + if self._nagios_protocol is None: + self._nagios_protocol = self.get_http_protocol() + return self._nagios_protocol + def get_plugin_settings(self): return infra_alerting_plugin_settings @@ -72,13 +84,15 @@ class InfraAlertingPluginApi(base_test.PluginApi): ) def get_authenticated_nagios_url(self): - return "http://{0}:{1}@{2}:{3}".format(self.settings.nagios_user, - self.settings.nagios_password, - self.get_plugin_vip(), - self.nagios_port) + return "{0}://{1}:{2}@{3}:{4}".format(self.nagios_protocol, + self.settings.nagios_user, + self.settings.nagios_password, + self.get_plugin_vip(), + self.nagios_port) def get_nagios_url(self): - return "http://{0}:{1}".format(self.get_plugin_vip(), self.nagios_port) + return "{0}://{1}:{2}".format(self.nagios_protocol, + self.get_plugin_vip(), self.nagios_port) def open_nagios_page(self, link_text, anchor): driver = self.ui_tester.get_driver(self.get_authenticated_nagios_url(), diff --git a/stacklight_tests/run_tests.py b/stacklight_tests/run_tests.py index 85a2b81..bedcd88 100644 --- a/stacklight_tests/run_tests.py +++ b/stacklight_tests/run_tests.py @@ -54,6 +54,7 @@ def import_tests(): from stacklight_tests.toolchain import test_dedicated_environment # noqa from stacklight_tests.toolchain import test_detached_plugins # noqa from stacklight_tests.toolchain import test_functional # noqa + from stacklight_tests.toolchain import test_https_plugins # noqa from stacklight_tests.toolchain import test_network_templates # noqa from stacklight_tests.toolchain import test_post_install # noqa from stacklight_tests.toolchain import test_reduced_footprint # noqa diff --git a/stacklight_tests/toolchain/test_https_plugins.py b/stacklight_tests/toolchain/test_https_plugins.py new file mode 100644 index 0000000..318f3cc --- /dev/null +++ b/stacklight_tests/toolchain/test_https_plugins.py @@ -0,0 +1,98 @@ +# Copyright 2016 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from fuelweb_test.helpers.decorators import log_snapshot_after_test +from fuelweb_test import logger +from proboscis import test + +from stacklight_tests.helpers import helpers +from stacklight_tests.toolchain import api + + +@test(groups=["https"]) +class TestToolchainHTTPs(api.ToolchainApi): + """Class for smoke testing the LMA Toolchain plugins.""" + + @test(depends_on_groups=['prepare_slaves_3'], + groups=["https", "deploy_toolchain_with_https", "toolchain", + "deploy"]) + @log_snapshot_after_test + def deploy_toolchain_with_https(self): + """Install the LMA Toolchain plugins and check it exists + + Scenario: + 1. Upload the LMA Toolchain plugins to the master node + 2. Install the plugins + 3. Create the cluster + 4. Uplaod script for ssl certificate creation + 5. Create ssl certificate for influxdb_grafana plugin + 6. Create ssl certificate for elasticsearch_kibana plugin + 7. Create ssl certificate for lma_infrastructure_alerting plugin + 8. Enable and configure TLS option in plugins + 9. Deploy the cluster + 10. Check that LMA Toolchain plugins are running + 11. Run OSTF + + Duration 120m + """ + self.env.revert_snapshot("ready_with_3_slaves") + + self.prepare_plugins() + + self.helpers.create_cluster(name=self.__class__.__name__) + + self.activate_plugins() + + plugins_ssl = { + "kibana": self.ELASTICSEARCH_KIBANA, + "grafana": self.INFLUXDB_GRAFANA, + "nagios": self.LMA_INFRASTRUCTURE_ALERTING, + } + + with self.env.d_env.get_admin_remote() as remote: + remote.upload( + helpers.get_fixture("https/create_certificate.sh"), + "/tmp") + for name, plugin in plugins_ssl.items(): + self._activate_ssl_plugin(name, plugin, remote) + + self.helpers.deploy_cluster(self.settings.base_nodes) + + self.check_plugins_online() + + self.helpers.run_ostf() + + self.env.make_snapshot("https_plugins", is_make=True) + + @staticmethod + def _activate_ssl_plugin(name, plugin, remote): + """Creates certificate and activate ssl option in plugin.""" + logger.info( + "Create certificate and configure tls in plugin {}".format( + plugin.get_plugin_settings().name)) + + ssl_cert = {} + ssl_cert["name"] = "{}.pem".format(name) + + remote.execute( + "cd /tmp && bash -x create_certificate.sh {}.fuel.local". + format(name), verbose=True + ) + + with remote.open("/tmp/{}.pem".format(name)) as f: + ssl_cert["content"] = f.read() + + plugin.activate_plugin(options={ + "tls_enabled/value": True, + "{}_ssl_cert/value".format(name): ssl_cert + })