Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 1 | # Copyright 2016 Mirantis, Inc. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | # not use this file except in compliance with the License. You may obtain |
| 5 | # a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 12 | # License for the specific language governing permissions and limitations |
| 13 | # under the License. |
| 14 | |
| 15 | import pytest |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 16 | |
| 17 | from tcp_tests.helpers import ext |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 18 | from tcp_tests.helpers import utils |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 19 | from tcp_tests import logger |
| 20 | from tcp_tests import settings |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 21 | from tcp_tests.managers import underlay_ssh_manager |
| 22 | |
| 23 | LOG = logger.logger |
| 24 | |
| 25 | |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 26 | @pytest.fixture(scope="session") |
| 27 | def hardware(request, config): |
| 28 | """Fixture for manage the hardware layer. |
| 29 | |
| 30 | - start/stop/reboot libvirt/IPMI(/MaaS?) nodes |
| 31 | - snapshot/revert libvirt nodes (fuel-devops only) |
| 32 | - block/unblock libvirt networks/interfaces (fuel-devops only) |
| 33 | |
| 34 | This fixture should get a hardware configuration from |
| 35 | 'config' object or create a virtual/baremetal underlay |
| 36 | using EnvironmentManager. |
| 37 | |
| 38 | Creates a snapshot 'hardware' with ready-to-use virtual environment |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 39 | (Only for config.hardware.env_manager='devops'): |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 40 | - just created virtual nodes in power-on state |
| 41 | - node volumes filled with necessary content |
| 42 | - node network interfaces connected to necessary devices |
| 43 | |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 44 | config.hardware.env_manager: one of ('devops', 'maas', None) |
| 45 | config.hardware.config: path to the config file for the env_manager |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 46 | config.hardware.current_snapshot = Latest created or reverted snapshot |
| 47 | |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 48 | :rtype EnvironmentModel: if config.hardware.env_manager == 'devops' |
| 49 | :rtype EnvironmentManagerEmpty: |
| 50 | if config.hardware.env_manager == 'empty' |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 51 | """ |
| 52 | env = None |
| 53 | |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 54 | env_manager = config.hardware.env_manager |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 55 | |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 56 | if env_manager == 'empty': |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 57 | # No environment manager is used. |
| 58 | # 'config' should contain config.underlay.ssh settings |
| 59 | # 'config' should contain config.underlay.current_snapshot setting |
Dennis Dmitriev | c902ad8 | 2019-04-12 13:41:30 +0300 | [diff] [blame] | 60 | from tcp_tests.managers import envmanager_empty |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 61 | env = envmanager_empty.EnvironmentManagerEmpty(config=config) |
| 62 | |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 63 | elif env_manager == 'devops': |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 64 | # fuel-devops environment manager is used. |
| 65 | # config.underlay.ssh settings can be empty or witn SSH to existing env |
| 66 | # config.underlay.current_snapshot |
Dennis Dmitriev | c902ad8 | 2019-04-12 13:41:30 +0300 | [diff] [blame] | 67 | from tcp_tests.managers import envmanager_devops |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 68 | env = envmanager_devops.EnvironmentManager(config=config) |
Dennis Dmitriev | f5f2e60 | 2017-11-03 15:36:19 +0200 | [diff] [blame] | 69 | |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 70 | elif env_manager == 'heat': |
Dennis Dmitriev | f5f2e60 | 2017-11-03 15:36:19 +0200 | [diff] [blame] | 71 | # heat environment manager is used. |
| 72 | # config.underlay.ssh settings can be empty or witn SSH to existing env |
| 73 | # config.underlay.current_snapshot |
Dennis Dmitriev | c902ad8 | 2019-04-12 13:41:30 +0300 | [diff] [blame] | 74 | from tcp_tests.managers import envmanager_heat |
Dennis Dmitriev | f5f2e60 | 2017-11-03 15:36:19 +0200 | [diff] [blame] | 75 | env = envmanager_heat.EnvironmentManagerHeat(config=config) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 76 | else: |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 77 | raise Exception("Unknown hardware manager: '{}'".format(env_manager)) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 78 | |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 79 | # for devops env_manager: power on nodes and wait for SSH |
| 80 | # for empty env_manager: do nothing |
| 81 | # for maas env_manager: provision nodes and wait for SSH |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 82 | if not env.has_snapshot(ext.SNAPSHOT.hardware): |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 83 | env.create_snapshot(ext.SNAPSHOT.hardware) |
| 84 | |
| 85 | def fin(): |
| 86 | if settings.SHUTDOWN_ENV_ON_TEARDOWN: |
| 87 | LOG.info("Shutdown environment...") |
| 88 | env.stop() |
| 89 | |
| 90 | request.addfinalizer(fin) |
| 91 | return env |
| 92 | |
| 93 | |
| 94 | @pytest.fixture(scope='function') |
| 95 | def revert_snapshot(request, hardware): |
| 96 | """Revert snapshot for the test case |
| 97 | |
| 98 | Usage: |
| 99 | @pytest.mark.revert_snapshot(name='<required_snapshot_name>') |
| 100 | |
| 101 | If the mark 'revert_snapshot' is absend, or <required_snapshot_name> |
| 102 | not found, then an initial 'hardware' snapshot will be reverted. |
| 103 | |
| 104 | :rtype string: name of the reverted snapshot or None |
| 105 | """ |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 106 | top_fixtures_snapshots = utils.get_top_fixtures_marks( |
| 107 | request, 'revert_snapshot') |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 108 | |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 109 | # Try to revert the best matches snapshot for the test |
| 110 | for snapshot_name in top_fixtures_snapshots: |
| 111 | if hardware.has_snapshot(snapshot_name) and \ |
| 112 | hardware.has_snapshot_config(snapshot_name): |
| 113 | hardware.revert_snapshot(snapshot_name) |
| 114 | return snapshot_name |
| 115 | |
| 116 | # Fallback to the basic snapshot |
| 117 | hardware.revert_snapshot(ext.SNAPSHOT.hardware) |
| 118 | return None |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 119 | |
| 120 | |
Dennis Dmitriev | 986326a | 2018-02-12 17:24:26 +0200 | [diff] [blame] | 121 | @pytest.fixture(scope='function') |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 122 | def snapshot(request, hardware): |
| 123 | """Fixture for creating snapshot at the end of test if it's needed |
| 124 | |
| 125 | Marks: |
| 126 | snapshot_needed(name=None) - make snapshot if test is passed. If |
| 127 | name argument provided, it will be used for creating snapshot, |
| 128 | otherwise, test function name will be used |
| 129 | |
| 130 | fail_snapshot - make snapshot if test failed |
| 131 | |
| 132 | :param request: pytest.python.FixtureRequest |
| 133 | :param env: envmanager.EnvironmentManager |
| 134 | """ |
| 135 | snapshot_needed = request.keywords.get('snapshot_needed', None) |
| 136 | fail_snapshot = request.keywords.get('fail_snapshot', None) |
| 137 | |
| 138 | def test_fin(): |
| 139 | default_snapshot_name = getattr(request.node.function, |
| 140 | '_snapshot_name', |
| 141 | request.node.function.__name__) |
| 142 | if hasattr(request.node, 'rep_call') and request.node.rep_call.passed \ |
| 143 | and snapshot_needed: |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 144 | snapshot_name = utils.extract_name_from_mark(snapshot_needed) or \ |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 145 | "{}_passed".format(default_snapshot_name) |
Dennis Dmitriev | 411dd10 | 2017-09-15 16:04:47 +0300 | [diff] [blame] | 146 | hardware.create_snapshot(snapshot_name, force=True) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 147 | |
| 148 | elif hasattr(request.node, 'rep_setup') and \ |
| 149 | request.node.rep_setup.failed and fail_snapshot: |
| 150 | snapshot_name = "{0}_prep_failed".format(default_snapshot_name) |
Dennis Dmitriev | 411dd10 | 2017-09-15 16:04:47 +0300 | [diff] [blame] | 151 | hardware.create_snapshot(snapshot_name, force=True) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 152 | |
| 153 | elif hasattr(request.node, 'rep_call') and \ |
| 154 | request.node.rep_call.failed and fail_snapshot: |
| 155 | snapshot_name = "{0}_failed".format(default_snapshot_name) |
Dennis Dmitriev | 411dd10 | 2017-09-15 16:04:47 +0300 | [diff] [blame] | 156 | hardware.create_snapshot(snapshot_name, force=True) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 157 | |
| 158 | request.addfinalizer(test_fin) |
| 159 | |
| 160 | |
Dennis Dmitriev | b3b3749 | 2018-07-08 21:23:00 +0300 | [diff] [blame] | 161 | @pytest.fixture(scope="function") |
| 162 | def underlay_actions(config): |
| 163 | """Fixture that provides SSH access to underlay objects. |
| 164 | |
| 165 | :param config: oslo_config object that keeps various parameters |
| 166 | across the fixtures, tests and test runs. |
| 167 | All SSH data is taken from the provided config. |
| 168 | :rtype UnderlaySSHManager: Object that encapsulate SSH credentials; |
| 169 | - provide list of underlay nodes; |
| 170 | - provide SSH access to underlay nodes using |
| 171 | node names or node IPs. |
| 172 | """ |
| 173 | return underlay_ssh_manager.UnderlaySSHManager(config) |
| 174 | |
| 175 | |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 176 | @pytest.mark.revert_snapshot(ext.SNAPSHOT.underlay) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 177 | @pytest.fixture(scope="function") |
Dennis Dmitriev | ea21463 | 2018-09-19 16:30:52 +0300 | [diff] [blame] | 178 | def underlay(request, revert_snapshot, config, hardware, underlay_actions): |
Dennis Dmitriev | b3b3749 | 2018-07-08 21:23:00 +0300 | [diff] [blame] | 179 | """Fixture that bootstraps the environment underlay. |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 180 | |
| 181 | - Starts the 'hardware' environment and creates 'underlay' with required |
| 182 | configuration. |
| 183 | - Fills the following object using the 'hardware' fixture: |
| 184 | config.underlay.ssh = JSONList of SSH access credentials for nodes. |
| 185 | This list will be used for initialization the |
| 186 | model UnderlaySSHManager, see it for details. |
| 187 | |
| 188 | :rtype UnderlaySSHManager: Object that encapsulate SSH credentials; |
| 189 | - provide list of underlay nodes; |
| 190 | - provide SSH access to underlay nodes using |
| 191 | node names or node IPs. |
| 192 | """ |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 193 | # Create Underlay |
Dmitry Tyzhnenko | 35413c0 | 2018-03-05 14:12:37 +0200 | [diff] [blame] | 194 | |
| 195 | def basic_underlay(): |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 196 | # If config.underlay.ssh wasn't provided from external config, then |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 197 | # try to get necessary data from hardware env_manager (fuel-devops) |
Dennis Dmitriev | 7b9538f | 2017-05-15 17:01:34 +0300 | [diff] [blame] | 198 | |
Dennis Dmitriev | 552454c | 2019-03-21 23:15:51 +0200 | [diff] [blame] | 199 | # for devops env_manager: power on nodes and wait for SSH |
| 200 | # for empty env_manager: do nothing |
| 201 | # for maas env_manager: provision nodes and wait for SSH |
Dennis Dmitriev | 7b9538f | 2017-05-15 17:01:34 +0300 | [diff] [blame] | 202 | hardware.start(underlay_node_roles=config.underlay.roles, |
| 203 | timeout=config.underlay.bootstrap_timeout) |
| 204 | |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 205 | config.underlay.ssh = hardware.get_ssh_data( |
| 206 | roles=config.underlay.roles) |
| 207 | |
Dmitry Tyzhnenko | 34595f8 | 2018-06-12 19:03:12 +0300 | [diff] [blame] | 208 | LOG.info("Config - {}".format(config)) |
Dennis Dmitriev | ea21463 | 2018-09-19 16:30:52 +0300 | [diff] [blame] | 209 | underlay_actions.add_config_ssh(config.underlay.ssh) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 210 | |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 211 | hardware.create_snapshot(ext.SNAPSHOT.underlay) |
| 212 | |
Dennis Dmitriev | ea21463 | 2018-09-19 16:30:52 +0300 | [diff] [blame] | 213 | return underlay_actions |
Dmitry Tyzhnenko | 35413c0 | 2018-03-05 14:12:37 +0200 | [diff] [blame] | 214 | |
| 215 | def day1_underlay(): |
| 216 | hardware.start( |
| 217 | underlay_node_roles=['salt_master'], |
| 218 | timeout=config.underlay.bootstrap_timeout) |
| 219 | |
| 220 | config.underlay.ssh = hardware.get_ssh_data( |
| 221 | roles=config.underlay.roles) |
Dennis Dmitriev | ea21463 | 2018-09-19 16:30:52 +0300 | [diff] [blame] | 222 | underlay_actions.add_config_ssh(config.underlay.ssh) |
Dmitry Tyzhnenko | 35413c0 | 2018-03-05 14:12:37 +0200 | [diff] [blame] | 223 | |
| 224 | LOG.info("Generate MACs for MaaS") |
| 225 | macs = { |
| 226 | n.name.split('.')[0]: { |
| 227 | "interface": { |
| 228 | "mac": n.get_interface_by_network_name('admin').mac_address}, # noqa |
| 229 | "power_parameters": { |
| 230 | "power_address": "{}:{}".format( |
| 231 | n.get_interface_by_network_name('admin').l2_network_device.address_pool.get_ip('l2_network_device'), # noqa |
| 232 | n.bmc_port |
| 233 | )}} for n in hardware.slave_nodes} |
| 234 | |
| 235 | config.day1_cfg_config.maas_machines_macs = { |
| 236 | "parameters": { |
| 237 | "maas": { |
| 238 | "region": { |
| 239 | "machines": macs}}}} |
| 240 | |
Dmitry Tyzhnenko | 35413c0 | 2018-03-05 14:12:37 +0200 | [diff] [blame] | 241 | for node in hardware.slave_nodes: |
| 242 | # For correct comissioning by MaaS nodes should be powered off |
| 243 | node.destroy() |
| 244 | |
| 245 | hardware.create_snapshot(ext.SNAPSHOT.underlay) |
| 246 | |
Dennis Dmitriev | ea21463 | 2018-09-19 16:30:52 +0300 | [diff] [blame] | 247 | return underlay_actions |
Dmitry Tyzhnenko | 35413c0 | 2018-03-05 14:12:37 +0200 | [diff] [blame] | 248 | |
| 249 | if not config.underlay.ssh: |
| 250 | if request.node.get_marker('day1_underlay'): |
Dennis Dmitriev | ea21463 | 2018-09-19 16:30:52 +0300 | [diff] [blame] | 251 | return day1_underlay() |
Dmitry Tyzhnenko | 35413c0 | 2018-03-05 14:12:37 +0200 | [diff] [blame] | 252 | else: |
Dennis Dmitriev | ea21463 | 2018-09-19 16:30:52 +0300 | [diff] [blame] | 253 | return basic_underlay() |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 254 | else: |
| 255 | # 1. hardware environment created and powered on |
| 256 | # 2. config.underlay.ssh contains SSH access to provisioned nodes |
| 257 | # (can be passed from external config with TESTS_CONFIGS variable) |
Dennis Dmitriev | ea21463 | 2018-09-19 16:30:52 +0300 | [diff] [blame] | 258 | return underlay_actions |
Tatyana Leontovich | ab47e16 | 2017-10-06 16:53:30 +0300 | [diff] [blame] | 259 | |
| 260 | |
Dennis Dmitriev | 986326a | 2018-02-12 17:24:26 +0200 | [diff] [blame] | 261 | @pytest.fixture(scope='function') |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame] | 262 | def grab_versions(request, func_name, underlay): |
Tatyana Leontovich | ab47e16 | 2017-10-06 16:53:30 +0300 | [diff] [blame] | 263 | """Fixture for grab package versions at the end of test |
| 264 | |
| 265 | Marks: |
| 266 | grab_versions(name=None) - make snapshot if test is passed. If |
| 267 | name argument provided, it will be used for creating data, |
| 268 | otherwise, test function name will be used |
| 269 | |
| 270 | """ |
| 271 | grab_version = request.keywords.get('grab_versions', None) |
| 272 | |
| 273 | def test_fin(): |
Dennis Dmitriev | f0b2afe | 2018-02-28 13:25:02 +0200 | [diff] [blame] | 274 | fixture_failed = (hasattr(request.node, 'rep_setup') and |
| 275 | request.node.rep_setup.failed) |
| 276 | test_passed = (hasattr(request.node, 'rep_call') and |
| 277 | request.node.rep_call.passed) |
| 278 | test_failed = (hasattr(request.node, 'rep_call') and |
| 279 | request.node.rep_call.failed) |
| 280 | |
| 281 | if fixture_failed or test_passed or test_failed: |
Tatyana Leontovich | ab47e16 | 2017-10-06 16:53:30 +0300 | [diff] [blame] | 282 | artifact_name = utils.extract_name_from_mark(grab_version) or \ |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame] | 283 | "{}".format(func_name) |
Tatyana Leontovich | ab47e16 | 2017-10-06 16:53:30 +0300 | [diff] [blame] | 284 | underlay.get_logs(artifact_name) |
Dennis Dmitriev | f0b2afe | 2018-02-28 13:25:02 +0200 | [diff] [blame] | 285 | |
| 286 | if grab_version: |
| 287 | request.addfinalizer(test_fin) |