Dmitriy Kruglov | d8c76bc | 2019-09-26 09:30:38 +0200 | [diff] [blame^] | 1 | # Copyright 2019 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 | import json |
| 15 | import pytest |
| 16 | |
| 17 | from devops.helpers import helpers |
| 18 | |
| 19 | from tcp_tests import logger |
| 20 | from tcp_tests import settings |
| 21 | |
| 22 | LOG = logger.logger |
| 23 | |
| 24 | |
| 25 | class TestUbuntuSecurityUpdates(object): |
| 26 | """Test class for verification of obtaining Ubuntu security updates""" |
| 27 | |
| 28 | ENV_NAME = settings.ENV_NAME |
| 29 | UPGRADE_CMD = ( |
| 30 | 'export DEBIAN_FRONTEND=noninteractive && ' |
| 31 | 'apt-get update && ' |
| 32 | 'apt-get -y upgrade && ' |
| 33 | 'apt-get -y -o Dpkg::Options::="--force-confdef" ' |
| 34 | ' -o Dpkg::Options::="--force-confnew" dist-upgrade' |
| 35 | ) |
| 36 | INST_LINUX_HEADERS_CMD = ( |
| 37 | "export DEBIAN_FRONTEND=noninteractive && " |
| 38 | "apt-get -y install linux-headers-generic" |
| 39 | ) |
| 40 | |
| 41 | UPDATE_JOB_NAME = "deploy-update-package" |
| 42 | UPDATE_JOB_PARAMETERS = { |
| 43 | "ASK_CONFIRMATION": False, |
| 44 | "TARGET_SERVERS": '' |
| 45 | } |
| 46 | |
| 47 | SANITY_JOB_NAME = 'cvp-sanity' |
| 48 | SANITY_JOB_PARAMETERS = { |
| 49 | 'EXTRA_PARAMS': { |
| 50 | 'envs': ["tests_set=-k 'not test_ceph_health'"] |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | JENKINS_START_TIMEOUT = 60 |
| 55 | |
| 56 | def get_available_pkg_updates(self, nodes, salt): |
| 57 | """Collect available package updates for given nodes |
| 58 | |
| 59 | :param nodes: list, nodes to collect available updates for |
| 60 | :param salt: SaltManager, tcp-qa Salt manager instance |
| 61 | :return: dict, update candidates for nodes |
| 62 | """ |
| 63 | updates = {} |
| 64 | for node in nodes: |
| 65 | updates[node] = salt.local( |
| 66 | node, "pkg.list_upgrades")['return'][0][node] |
| 67 | return updates |
| 68 | |
| 69 | def run_cvp_sanity(self, dt): |
| 70 | """A wrapper for executing cvp-sanity pipeline |
| 71 | |
| 72 | :param dt: DrivetrainManager, tcp-qa Drivetrain manager instance |
| 73 | :return: str, build execution status of cvp-sanity pipeline |
| 74 | """ |
| 75 | return dt.start_job_on_cid_jenkins( |
| 76 | job_name=self.SANITY_JOB_NAME, |
| 77 | job_parameters=self.SANITY_JOB_PARAMETERS, |
| 78 | start_timeout=self.JENKINS_START_TIMEOUT, |
| 79 | build_timeout=60 * 15 |
| 80 | ) |
| 81 | |
| 82 | def reboot_hw_node(self, ssh, salt, node): |
| 83 | """Reboot the given node and wait for it to start back |
| 84 | |
| 85 | :param ssh: UnderlaySSHManager, tcp-qa SSH manager instance |
| 86 | :param salt: SaltManager, tcp-qa Salt manager instance |
| 87 | :param node: str, name of the node to reboot |
| 88 | """ |
| 89 | LOG.info("Sending reboot command to '{}' node.".format(node)) |
| 90 | remote = ssh.remote(node_name=node) |
| 91 | remote.execute_async("/sbin/shutdown -r now") |
| 92 | |
| 93 | # Wait for restarted node to boot and become accessible |
| 94 | helpers.wait_pass( |
| 95 | lambda: salt.local(node, "test.ping", timeout=5), |
| 96 | timeout=60 * 10, interval=5) |
| 97 | |
| 98 | # TODO: finish the test once ASK_CONFIRMATION option is added to |
| 99 | # 'deploy-update-package' pipeline |
| 100 | @pytest.mark.grab_versions |
| 101 | @pytest.mark.ubuntu_security_updates_pipeline |
| 102 | def _test_obtaining_ubuntu_security_updates_via_pipeline( |
| 103 | self, salt_actions, drivetrain_actions, show_step): |
| 104 | """Test obtaining Ubuntu security updates using Jenkins |
| 105 | |
| 106 | Scenario: |
| 107 | 1. Collect available package upgrades for nodes of the given server |
| 108 | role |
| 109 | 2. Execute deploy-update-package pipeline for the given server role |
| 110 | 3. Collect available package upgrades for server role nodes again |
| 111 | 4. Check that there is no candidates for upgrade |
| 112 | 5. Run cvp-sanity tests |
| 113 | |
| 114 | Duration: ~ min |
| 115 | """ |
| 116 | salt = salt_actions |
| 117 | dt = drivetrain_actions |
| 118 | |
| 119 | role = "mon*" |
| 120 | nodes = salt.local(role, "test.ping")['return'][0].keys() |
| 121 | |
| 122 | # Collect available package upgrades for nodes |
| 123 | show_step(1) |
| 124 | updates = self.get_available_pkg_updates(nodes, salt) |
| 125 | LOG.info("Packages to be updated on nodes:\n{}".format( |
| 126 | json.dumps(updates, indent=4))) |
| 127 | |
| 128 | # Execute 'deploy-update-package' pipeline to upgrade packages on nodes |
| 129 | show_step(2) |
| 130 | self.UPDATE_JOB_PARAMETERS["TARGET_SERVERS"] = role |
| 131 | status = dt.start_job_on_cid_jenkins( |
| 132 | job_name=self.UPDATE_JOB_NAME, |
| 133 | job_parameters=self.UPDATE_JOB_PARAMETERS, |
| 134 | start_timeout=self.JENKINS_START_TIMEOUT, |
| 135 | build_timeout=60 * 15 |
| 136 | ) |
| 137 | assert status == 'SUCCESS', ( |
| 138 | "'{}' job run status is {} after upgrading packages on {} nodes. " |
| 139 | "Please check the build and executed stages.".format( |
| 140 | self.UPDATE_JOB_NAME, status, role) |
| 141 | ) |
| 142 | |
| 143 | # Collect available package upgrades for nodes again |
| 144 | show_step(3) |
| 145 | post_upgrade = self.get_available_pkg_updates(nodes, salt) |
| 146 | |
| 147 | # Check that there is no available package upgrades |
| 148 | show_step(4) |
| 149 | for node in nodes: |
| 150 | assert not post_upgrade[node], ( |
| 151 | "{} node still has upgrade candidates. Please check the " |
| 152 | "following packages and the reason why they are not " |
| 153 | "updated:\n{}".format(node, post_upgrade[node]) |
| 154 | ) |
| 155 | |
| 156 | # Execute cvp-sanity tests |
| 157 | show_step(5) |
| 158 | status = self.run_cvp_sanity(dt) |
| 159 | assert status == 'SUCCESS', ( |
| 160 | "'{0}' job run status is {1} after executing CVP-Sanity " |
| 161 | "tests".format( |
| 162 | self.SANITY_JOB_NAME, status) |
| 163 | ) |
| 164 | |
| 165 | @pytest.mark.grab_versions |
| 166 | @pytest.mark.ubuntu_security_updates_manual_infra_vms |
| 167 | def test_obtaining_ubuntu_security_updates_manual_infra_vms( |
| 168 | self, salt_actions, drivetrain_actions, show_step): |
| 169 | """Test obtaining Ubuntu security updates on virtual infra nodes. |
| 170 | Repeat the scenario for 01, 02 and 03 indexes of nodes. |
| 171 | |
| 172 | Scenario: |
| 173 | 1. Select set of virtual nodes for upgrade |
| 174 | 2. Collect available package upgrades for the nodes |
| 175 | 3. Upgrade the nodes |
| 176 | 4. Collect available package upgrades for the nodes again |
| 177 | 5. Check that there is no candidates for upgrade on the nodes |
| 178 | 6. Run cvp-sanity tests |
| 179 | |
| 180 | Duration: ~ 100 min |
| 181 | """ |
| 182 | salt = salt_actions |
| 183 | dt = drivetrain_actions |
| 184 | |
| 185 | for index in ('01', '02', '03'): |
| 186 | msg = ("# Executing scenario for '{i}' index of nodes #".format( |
| 187 | i=index)) |
| 188 | LOG.info( |
| 189 | "\n\n{pad}\n{msg}\n{pad}".format(pad="#" * len(msg), msg=msg)) |
| 190 | |
| 191 | # Select set of nodes for current iteration of updates |
| 192 | show_step(1) |
| 193 | tgt = "*{}* and E@^(?!kvm|cfg|cmp|osd).*$".format(index) |
| 194 | nodes = salt.local(tgt, "test.ping")['return'][0].keys() |
| 195 | LOG.info("Nodes to be upgraded:\n{}".format( |
| 196 | json.dumps(nodes, indent=4))) |
| 197 | |
| 198 | # Collect available package upgrades for the nodes |
| 199 | show_step(2) |
| 200 | updates = self.get_available_pkg_updates(nodes, salt) |
| 201 | |
| 202 | # Upgrade the selected nodes |
| 203 | show_step(3) |
| 204 | for node in nodes: |
| 205 | LOG.info( |
| 206 | "Starting upgrade of '{}' node.\nThe following packages " |
| 207 | "will be updated:\n{}".format( |
| 208 | node, json.dumps(updates[node], indent=4)) |
| 209 | ) |
| 210 | salt.cmd_run(node, self.UPGRADE_CMD) |
| 211 | |
| 212 | # Collect available package upgrades for the nodes again |
| 213 | show_step(4) |
| 214 | post_upgrade = self.get_available_pkg_updates(nodes, salt) |
| 215 | |
| 216 | # Check that there is no package upgrades candidates on the nodes |
| 217 | show_step(5) |
| 218 | missed_upd = { |
| 219 | node: pkgs for (node, pkgs) in post_upgrade.items() if pkgs} |
| 220 | assert not missed_upd, ( |
| 221 | "{} nodes still have upgrade candidates. Please check the " |
| 222 | "nodes and reason why the listed packages are not " |
| 223 | "updated:\n{}".format( |
| 224 | missed_upd.keys(), json.dumps(missed_upd, indent=4)) |
| 225 | ) |
| 226 | |
| 227 | # Execute cvp-sanity tests |
| 228 | show_step(6) |
| 229 | status = self.run_cvp_sanity(dt) |
| 230 | assert status == 'SUCCESS', ( |
| 231 | "'{0}' job run status is {1} after executing CVP-Sanity smoke " |
| 232 | "tests".format(self.SANITY_JOB_NAME, status)) |
| 233 | |
| 234 | @pytest.mark.grab_versions |
| 235 | @pytest.mark.ubuntu_security_updates_manual_hw_nodes |
| 236 | def test_obtaining_ubuntu_security_updates_manual_hw_nodes( |
| 237 | self, |
| 238 | salt_actions, |
| 239 | underlay_actions, |
| 240 | drivetrain_actions, |
| 241 | show_step): |
| 242 | """Test obtaining Ubuntu security updates on HW nodes. |
| 243 | Repeat the scenario for 01, 02 and 03 indexes of nodes. |
| 244 | |
| 245 | Scenario: |
| 246 | 1. Select set HW nodes for upgrade |
| 247 | 2. Collect available package upgrades for the nodes |
| 248 | 3. Upgrade the nodes |
| 249 | 4. Collect available package upgrades for the nodes again |
| 250 | 5. Check that there is no candidates for upgrade on the nodes |
| 251 | 6. Run cvp-sanity tests |
| 252 | |
| 253 | Duration: ~ 70 min |
| 254 | """ |
| 255 | salt = salt_actions |
| 256 | ssh = underlay_actions |
| 257 | dt = drivetrain_actions |
| 258 | |
| 259 | for index in ('01', '02', '03'): |
| 260 | msg = ("# Executing scenario for '{i}' index of nodes #".format( |
| 261 | i=index)) |
| 262 | LOG.info( |
| 263 | "\n\n{pad}\n{msg}\n{pad}".format(pad="#" * len(msg), msg=msg)) |
| 264 | |
| 265 | # Select set of nodes for current iteration of updates |
| 266 | show_step(1) |
| 267 | tgt = "E@^(kvm|cmp).?{}.*$".format(index) |
| 268 | nodes = salt.local(tgt, "test.ping")['return'][0].keys() |
| 269 | LOG.info("Nodes to be upgraded:\n{}".format( |
| 270 | json.dumps(nodes, indent=4))) |
| 271 | |
| 272 | # Collect available package upgrades for the nodes |
| 273 | show_step(2) |
| 274 | updates = self.get_available_pkg_updates(nodes, salt) |
| 275 | |
| 276 | # Upgrade the selected nodes |
| 277 | show_step(3) |
| 278 | for node in nodes: |
| 279 | LOG.info( |
| 280 | "Starting upgrade of '{}' node.\nThe following packages " |
| 281 | "will be updated:\n{}".format( |
| 282 | node, json.dumps(updates[node], indent=4)) |
| 283 | ) |
| 284 | salt.cmd_run(node, self.UPGRADE_CMD) |
| 285 | # Update Linux headers on compute nodes |
| 286 | if "cmp" in node: |
| 287 | LOG.info( |
| 288 | "Updating linux headers on '{}' node.".format(node)) |
| 289 | salt.cmd_run(node, self.INST_LINUX_HEADERS_CMD) |
| 290 | |
| 291 | # Reboot the node after upgrade |
| 292 | LOG.info("Starting reboot of '{}' node.".format(node)) |
| 293 | self.reboot_hw_node(ssh, salt, node) |
| 294 | LOG.info("'{}' node is back after reboot.".format(node)) |
| 295 | |
| 296 | # Collect available package upgrades for the nodes again |
| 297 | show_step(4) |
| 298 | post_upgrade = self.get_available_pkg_updates(nodes, salt) |
| 299 | |
| 300 | # Check that there is no package upgrades candidates on the nodes |
| 301 | show_step(5) |
| 302 | missed_upd = { |
| 303 | node: pkgs for (node, pkgs) in post_upgrade.items() if pkgs} |
| 304 | assert not missed_upd, ( |
| 305 | "{} nodes still have upgrade candidates. Please check the " |
| 306 | "nodes and reason why the listed packages are not " |
| 307 | "updated:\n{}".format( |
| 308 | missed_upd.keys(), json.dumps(missed_upd, indent=4)) |
| 309 | ) |
| 310 | |
| 311 | # Execute cvp-sanity tests |
| 312 | show_step(6) |
| 313 | status = self.run_cvp_sanity(dt) |
| 314 | assert status == 'SUCCESS', ( |
| 315 | "'{0}' job run status is {1} after executing CVP-Sanity " |
| 316 | "tests".format(self.SANITY_JOB_NAME, status)) |