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 |
| 21 | from tcp_tests.managers import envmanager_devops |
| 22 | from tcp_tests.managers import envmanager_empty |
| 23 | from tcp_tests.managers import underlay_ssh_manager |
| 24 | |
| 25 | LOG = logger.logger |
| 26 | |
| 27 | |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 28 | @pytest.fixture(scope="session") |
| 29 | def hardware(request, config): |
| 30 | """Fixture for manage the hardware layer. |
| 31 | |
| 32 | - start/stop/reboot libvirt/IPMI(/MaaS?) nodes |
| 33 | - snapshot/revert libvirt nodes (fuel-devops only) |
| 34 | - block/unblock libvirt networks/interfaces (fuel-devops only) |
| 35 | |
| 36 | This fixture should get a hardware configuration from |
| 37 | 'config' object or create a virtual/baremetal underlay |
| 38 | using EnvironmentManager. |
| 39 | |
| 40 | Creates a snapshot 'hardware' with ready-to-use virtual environment |
| 41 | (Only for config.hardware.manager='devops'): |
| 42 | - just created virtual nodes in power-on state |
| 43 | - node volumes filled with necessary content |
| 44 | - node network interfaces connected to necessary devices |
| 45 | |
| 46 | config.hardware.manager: one of ('devops', 'maas', None) |
| 47 | config.hardware.config: path to the config file for the manager |
| 48 | config.hardware.current_snapshot = Latest created or reverted snapshot |
| 49 | |
| 50 | :rtype EnvironmentModel: if config.hardware.manager == 'devops' |
| 51 | :rtype EnvironmentManagerEmpty: if config.hardware.manager == 'empty' |
| 52 | """ |
| 53 | env = None |
| 54 | |
| 55 | manager = config.hardware.manager |
| 56 | |
| 57 | if manager == 'empty': |
| 58 | # No environment manager is used. |
| 59 | # 'config' should contain config.underlay.ssh settings |
| 60 | # 'config' should contain config.underlay.current_snapshot setting |
| 61 | env = envmanager_empty.EnvironmentManagerEmpty(config=config) |
| 62 | |
| 63 | elif manager == 'devops': |
| 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 |
| 67 | env = envmanager_devops.EnvironmentManager(config=config) |
| 68 | else: |
| 69 | raise Exception("Unknown hardware manager: '{}'".format(manager)) |
| 70 | |
| 71 | # for devops manager: power on nodes and wait for SSH |
| 72 | # for empty manager: do nothing |
| 73 | # for maas manager: provision nodes and wait for SSH |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 74 | if not env.has_snapshot(ext.SNAPSHOT.hardware): |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 75 | env.create_snapshot(ext.SNAPSHOT.hardware) |
| 76 | |
| 77 | def fin(): |
| 78 | if settings.SHUTDOWN_ENV_ON_TEARDOWN: |
| 79 | LOG.info("Shutdown environment...") |
| 80 | env.stop() |
| 81 | |
| 82 | request.addfinalizer(fin) |
| 83 | return env |
| 84 | |
| 85 | |
| 86 | @pytest.fixture(scope='function') |
| 87 | def revert_snapshot(request, hardware): |
| 88 | """Revert snapshot for the test case |
| 89 | |
| 90 | Usage: |
| 91 | @pytest.mark.revert_snapshot(name='<required_snapshot_name>') |
| 92 | |
| 93 | If the mark 'revert_snapshot' is absend, or <required_snapshot_name> |
| 94 | not found, then an initial 'hardware' snapshot will be reverted. |
| 95 | |
| 96 | :rtype string: name of the reverted snapshot or None |
| 97 | """ |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 98 | top_fixtures_snapshots = utils.get_top_fixtures_marks( |
| 99 | request, 'revert_snapshot') |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 100 | |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 101 | # Try to revert the best matches snapshot for the test |
| 102 | for snapshot_name in top_fixtures_snapshots: |
| 103 | if hardware.has_snapshot(snapshot_name) and \ |
| 104 | hardware.has_snapshot_config(snapshot_name): |
| 105 | hardware.revert_snapshot(snapshot_name) |
| 106 | return snapshot_name |
| 107 | |
| 108 | # Fallback to the basic snapshot |
| 109 | hardware.revert_snapshot(ext.SNAPSHOT.hardware) |
| 110 | return None |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 111 | |
| 112 | |
| 113 | @pytest.fixture(scope='function', autouse=True) |
| 114 | def snapshot(request, hardware): |
| 115 | """Fixture for creating snapshot at the end of test if it's needed |
| 116 | |
| 117 | Marks: |
| 118 | snapshot_needed(name=None) - make snapshot if test is passed. If |
| 119 | name argument provided, it will be used for creating snapshot, |
| 120 | otherwise, test function name will be used |
| 121 | |
| 122 | fail_snapshot - make snapshot if test failed |
| 123 | |
| 124 | :param request: pytest.python.FixtureRequest |
| 125 | :param env: envmanager.EnvironmentManager |
| 126 | """ |
| 127 | snapshot_needed = request.keywords.get('snapshot_needed', None) |
| 128 | fail_snapshot = request.keywords.get('fail_snapshot', None) |
| 129 | |
| 130 | def test_fin(): |
| 131 | default_snapshot_name = getattr(request.node.function, |
| 132 | '_snapshot_name', |
| 133 | request.node.function.__name__) |
| 134 | if hasattr(request.node, 'rep_call') and request.node.rep_call.passed \ |
| 135 | and snapshot_needed: |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 136 | snapshot_name = utils.extract_name_from_mark(snapshot_needed) or \ |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 137 | "{}_passed".format(default_snapshot_name) |
Dennis Dmitriev | 411dd10 | 2017-09-15 16:04:47 +0300 | [diff] [blame] | 138 | hardware.create_snapshot(snapshot_name, force=True) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 139 | |
| 140 | elif hasattr(request.node, 'rep_setup') and \ |
| 141 | request.node.rep_setup.failed and fail_snapshot: |
| 142 | snapshot_name = "{0}_prep_failed".format(default_snapshot_name) |
Dennis Dmitriev | 411dd10 | 2017-09-15 16:04:47 +0300 | [diff] [blame] | 143 | hardware.create_snapshot(snapshot_name, force=True) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 144 | |
| 145 | elif hasattr(request.node, 'rep_call') and \ |
| 146 | request.node.rep_call.failed and fail_snapshot: |
| 147 | snapshot_name = "{0}_failed".format(default_snapshot_name) |
Dennis Dmitriev | 411dd10 | 2017-09-15 16:04:47 +0300 | [diff] [blame] | 148 | hardware.create_snapshot(snapshot_name, force=True) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 149 | |
| 150 | request.addfinalizer(test_fin) |
| 151 | |
| 152 | |
Dennis Dmitriev | 535869c | 2016-11-16 22:38:06 +0200 | [diff] [blame] | 153 | @pytest.mark.revert_snapshot(ext.SNAPSHOT.underlay) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 154 | @pytest.fixture(scope="function") |
| 155 | def underlay(revert_snapshot, config, hardware): |
| 156 | """Fixture that should provide SSH access to underlay objects. |
| 157 | |
| 158 | - Starts the 'hardware' environment and creates 'underlay' with required |
| 159 | configuration. |
| 160 | - Fills the following object using the 'hardware' fixture: |
| 161 | config.underlay.ssh = JSONList of SSH access credentials for nodes. |
| 162 | This list will be used for initialization the |
| 163 | model UnderlaySSHManager, see it for details. |
| 164 | |
| 165 | :rtype UnderlaySSHManager: Object that encapsulate SSH credentials; |
| 166 | - provide list of underlay nodes; |
| 167 | - provide SSH access to underlay nodes using |
| 168 | node names or node IPs. |
| 169 | """ |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 170 | # Create Underlay |
| 171 | if not config.underlay.ssh: |
| 172 | # If config.underlay.ssh wasn't provided from external config, then |
| 173 | # try to get necessary data from hardware manager (fuel-devops) |
Dennis Dmitriev | 7b9538f | 2017-05-15 17:01:34 +0300 | [diff] [blame] | 174 | |
| 175 | # for devops manager: power on nodes and wait for SSH |
| 176 | # for empty manager: do nothing |
| 177 | # for maas manager: provision nodes and wait for SSH |
| 178 | hardware.start(underlay_node_roles=config.underlay.roles, |
| 179 | timeout=config.underlay.bootstrap_timeout) |
| 180 | |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 181 | config.underlay.ssh = hardware.get_ssh_data( |
| 182 | roles=config.underlay.roles) |
| 183 | |
Dennis Dmitriev | 2a13a13 | 2016-11-04 00:56:23 +0200 | [diff] [blame] | 184 | underlay = underlay_ssh_manager.UnderlaySSHManager(config) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 185 | |
| 186 | if not config.underlay.lvm: |
| 187 | underlay.enable_lvm(hardware.lvm_storages()) |
| 188 | config.underlay.lvm = underlay.config_lvm |
| 189 | |
| 190 | hardware.create_snapshot(ext.SNAPSHOT.underlay) |
| 191 | |
| 192 | else: |
| 193 | # 1. hardware environment created and powered on |
| 194 | # 2. config.underlay.ssh contains SSH access to provisioned nodes |
| 195 | # (can be passed from external config with TESTS_CONFIGS variable) |
Dennis Dmitriev | 2a13a13 | 2016-11-04 00:56:23 +0200 | [diff] [blame] | 196 | underlay = underlay_ssh_manager.UnderlaySSHManager(config) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 197 | |
| 198 | return underlay |
Tatyana Leontovich | ab47e16 | 2017-10-06 16:53:30 +0300 | [diff] [blame^] | 199 | |
| 200 | |
| 201 | @pytest.fixture(scope='function', autouse=True) |
| 202 | def grab_versions(request, underlay): |
| 203 | """Fixture for grab package versions at the end of test |
| 204 | |
| 205 | Marks: |
| 206 | grab_versions(name=None) - make snapshot if test is passed. If |
| 207 | name argument provided, it will be used for creating data, |
| 208 | otherwise, test function name will be used |
| 209 | |
| 210 | """ |
| 211 | grab_version = request.keywords.get('grab_versions', None) |
| 212 | |
| 213 | def test_fin(): |
| 214 | default_name = getattr(request.node.function, '_name', |
| 215 | request.node.function.__name__) |
| 216 | if hasattr(request.node, 'rep_call') and request.node.rep_call.passed \ |
| 217 | and grab_version: |
| 218 | artifact_name = utils.extract_name_from_mark(grab_version) or \ |
| 219 | "{}".format(default_name) |
| 220 | underlay.get_logs(artifact_name) |
| 221 | request.addfinalizer(test_fin) |