Browse Source

Merge "Restart mcollective on slave nodes after restore"

changes/20/386620/1
Jenkins 2 years ago
parent
commit
9e69e8d0d6

+ 2
- 0
octane/handlers/backup_restore/__init__.py View File

@@ -18,6 +18,7 @@ from octane.handlers.backup_restore import cobbler
18 18
 from octane.handlers.backup_restore import fuel_keys
19 19
 from octane.handlers.backup_restore import fuel_uuid
20 20
 from octane.handlers.backup_restore import logs
21
+from octane.handlers.backup_restore import mcollective
21 22
 from octane.handlers.backup_restore import mirrors
22 23
 from octane.handlers.backup_restore import nailgun_plugins
23 24
 from octane.handlers.backup_restore import postgres
@@ -48,6 +49,7 @@ ARCHIVATORS = [
48 49
     version.VersionArchivator,
49 50
     nailgun_plugins.NailgunPluginsArchivator,
50 51
     puppet.PuppetApplyTasks,
52
+    mcollective.McollectiveArchivator,
51 53
 ]
52 54
 
53 55
 REPO_ARCHIVATORS = [

+ 53
- 0
octane/handlers/backup_restore/mcollective.py View File

@@ -0,0 +1,53 @@
1
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+# not use this file except in compliance with the License. You may obtain
3
+# a copy of the License at
4
+#
5
+#      http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+# Unless required by applicable law or agreed to in writing, software
8
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+# License for the specific language governing permissions and limitations
11
+# under the License.
12
+
13
+import io
14
+import json
15
+import logging
16
+import tarfile
17
+
18
+from fuelclient import objects
19
+
20
+from octane.handlers.backup_restore import base
21
+from octane.util import fuel_client
22
+from octane.util import mcollective
23
+from octane.util import node as node_util
24
+
25
+LOG = logging.getLogger(__name__)
26
+
27
+
28
+class McollectiveArchivator(base.Base):
29
+    filename = "mco/ping.json"
30
+
31
+    def backup(self):
32
+        status = mcollective.get_mco_ping_status()
33
+        content = json.dumps(status)
34
+        info = tarfile.TarInfo(self.filename)
35
+        info.size = len(content)
36
+        fileobj = io.BytesIO(content)
37
+        self.archive.addfile(info, fileobj=fileobj)
38
+
39
+    def restore(self):
40
+        with fuel_client.set_auth_context(self.context):
41
+            nodes = objects.Node.get_all()
42
+            for node in nodes:
43
+                node_util.restart_mcollective(node)
44
+        content = self.archive.extractfile(self.filename)
45
+        if content is not None:
46
+            orig_status = json.load(content)
47
+            new_status = mcollective.get_mco_ping_status()
48
+            offline = mcollective.compair_mco_ping_statuses(orig_status,
49
+                                                            new_status)
50
+            if offline:
51
+                LOG.warning("Some nodes went offline after the upgrade of the "
52
+                            "master node (check them manually): %s",
53
+                            ", ".join(offline))

+ 16
- 0
octane/tests/test_archivators.py View File

@@ -20,6 +20,7 @@ from octane.handlers.backup_restore import base
20 20
 from octane.handlers.backup_restore import cobbler
21 21
 from octane.handlers.backup_restore import fuel_keys
22 22
 from octane.handlers.backup_restore import fuel_uuid
23
+from octane.handlers.backup_restore import mcollective
23 24
 from octane.handlers.backup_restore import mirrors
24 25
 from octane.handlers.backup_restore import nailgun_plugins
25 26
 from octane.handlers.backup_restore import postgres
@@ -271,3 +272,18 @@ def test_repos_backup(
271 272
 def test_archivator_name(mocker, name, expected_name):
272 273
 
273 274
     assert expected_name == type(name, (base.Base, ), {})(None).archivator_name
275
+
276
+
277
+def test_mcollective_backup(mocker):
278
+    archive = mock.Mock()
279
+    mocker.patch("octane.util.mcollective.get_mco_ping_status")
280
+    mock_json = mocker.patch("json.dumps")
281
+    mock_json.return_value = "{}"
282
+    mock_info = mocker.patch("tarfile.TarInfo")
283
+    mock_io = mocker.patch("io.BytesIO")
284
+    mcollective.McollectiveArchivator(archive).backup()
285
+    archive.addfile.assert_called_once_with(
286
+        mock_info.return_value, fileobj=mock_io.return_value)
287
+    mock_io.assert_called_once_with(mock_json.return_value)
288
+    mock_info.assert_called_once_with("mco/ping.json")
289
+    assert mock_info.return_value.size == len(mock_json.return_value)

+ 41
- 0
octane/tests/test_archivators_restore.py View File

@@ -24,6 +24,7 @@ from octane.handlers.backup_restore import cobbler
24 24
 from octane.handlers.backup_restore import fuel_keys
25 25
 from octane.handlers.backup_restore import fuel_uuid
26 26
 from octane.handlers.backup_restore import logs
27
+from octane.handlers.backup_restore import mcollective
27 28
 from octane.handlers.backup_restore import mirrors
28 29
 from octane.handlers.backup_restore import postgres
29 30
 from octane.handlers.backup_restore import puppet
@@ -677,3 +678,43 @@ def test_admin_network_restore(mocker, members, is_exist):
677 678
         mock_puppet.assert_called_once_with('dhcp-ranges')
678 679
     else:
679 680
         mock_puppet.assert_not_called()
681
+
682
+
683
+@pytest.mark.parametrize(("members", "check_status"), [
684
+    ([TestMember("mco/ping.json", True, False)], True),
685
+    ([], False),
686
+])
687
+def test_mcollective_restore(mocker, members, check_status):
688
+    nodes = [mock.Mock(), mock.Mock()]
689
+    mocker.patch("octane.util.fuel_client.set_auth_context")
690
+    mock_get = mocker.patch("fuelclient.objects.Node.get_all")
691
+    mock_get.return_value = nodes
692
+    mock_restart = mocker.patch("octane.util.node.restart_mcollective")
693
+    mock_json = mocker.patch("json.load")
694
+    mock_status = mocker.patch("octane.util.mcollective.get_mco_ping_status")
695
+    mock_cmp = mocker.patch(
696
+        "octane.util.mcollective.compair_mco_ping_statuses")
697
+    mock_cmp.return_value = set(["1"])
698
+    mock_log = mocker.patch(
699
+        "octane.handlers.backup_restore.mcollective.LOG.warning")
700
+
701
+    archive = TestArchive(members, mcollective.McollectiveArchivator)
702
+    mcollective.McollectiveArchivator(archive).restore()
703
+    assert mock_restart.call_args_list == [
704
+        mock.call(node) for node in nodes
705
+    ]
706
+    if check_status:
707
+        effective = [
708
+            member
709
+            for member in members if member.name == "mco/ping.json"
710
+        ][-1]
711
+        assert effective
712
+        mock_json.assert_called_once_with(effective)
713
+        mock_status.assert_called_once_with()
714
+        mock_cmp.assert_called_once_with(
715
+            mock_json.return_value, mock_status.return_value)
716
+        mock_log.assert_called_once_with(mock.ANY, "1")
717
+    else:
718
+        assert not mock_json.called
719
+        assert not mock_status.called
720
+        assert not mock_cmp.called

+ 36
- 0
octane/tests/test_util_mcollective.py View File

@@ -0,0 +1,36 @@
1
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+# not use this file except in compliance with the License. You may obtain
3
+# a copy of the License at
4
+#
5
+#      http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+# Unless required by applicable law or agreed to in writing, software
8
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+# License for the specific language governing permissions and limitations
11
+# under the License.
12
+
13
+import io
14
+import json
15
+
16
+import pytest
17
+
18
+from octane.util import mcollective
19
+
20
+
21
+@pytest.mark.parametrize("status", [
22
+    {"a": "b", "c": "d"},
23
+])
24
+def test_get_mco_ping_status(mocker, status):
25
+    stdout = io.BytesIO(json.dumps(status))
26
+    mock_popen = mocker.patch("octane.util.subprocess.popen")
27
+    mock_popen.return_value.__enter__.return_value.stdout = stdout
28
+    result = mcollective.get_mco_ping_status()
29
+    assert result == status
30
+
31
+
32
+@pytest.mark.parametrize(("orig", "new", "offline"), [
33
+    ([{"sender": 1}, {"sender": 2}], [{"sender": 1}], set([2])),
34
+])
35
+def test_compair_mco_ping_statuses(mocker, orig, new, offline):
36
+    assert mcollective.compair_mco_ping_statuses(orig, new) == offline

+ 13
- 0
octane/tests/test_util_node.py View File

@@ -242,3 +242,16 @@ def test_get_parameters(mocker, parameters, parameters_to_get, required,
242 242
     mock_get.assert_called_once_with(
243 243
         mock_sftp.return_value.open.return_value.__enter__.return_value,
244 244
         parameters_to_get)
245
+
246
+
247
+@pytest.mark.parametrize(("online", "result", "error"), [
248
+    (True, True, False),
249
+    (False, None, False),
250
+    (True, False, True),
251
+])
252
+def test_restart_mcollective(mocker, online, result, error):
253
+    node = mock.Mock(data={"online": online, "id": 123})
254
+    mock_ssh = mocker.patch("octane.util.ssh.call")
255
+    if error:
256
+        mock_ssh.side_effect = Exception()
257
+    assert node_util.restart_mcollective(node) == result

+ 31
- 0
octane/util/mcollective.py View File

@@ -0,0 +1,31 @@
1
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+# not use this file except in compliance with the License. You may obtain
3
+# a copy of the License at
4
+#
5
+#      http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+# Unless required by applicable law or agreed to in writing, software
8
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+# License for the specific language governing permissions and limitations
11
+# under the License.
12
+
13
+import json
14
+
15
+from octane.util import subprocess
16
+
17
+
18
+def get_mco_ping_status(node_id=None):
19
+    cmd = ["mco", "rpc", "rpcutil", "ping", "--json"]
20
+    if node_id is not None:
21
+        cmd.extend(["-I", str(node_id)])
22
+    with subprocess.popen(cmd, stdout=subprocess.PIPE) as proc:
23
+        return json.load(proc.stdout)
24
+
25
+
26
+def compair_mco_ping_statuses(orig_status, new_status):
27
+    # NOTE(akcram): Statuses are present only for alive nodes.
28
+    orig_ids = {resp["sender"] for resp in orig_status}
29
+    new_ids = {resp["sender"] for resp in new_status}
30
+    offline = orig_ids - new_ids
31
+    return offline

+ 18
- 0
octane/util/node.py View File

@@ -212,3 +212,21 @@ def get_parameters(node, filename, parameters_to_get, ensure=True):
212 212
             raise AbsentParametersError(
213 213
                 node.data["id"], filename, flat_parameters)
214 214
     return parameters
215
+
216
+
217
+def restart_mcollective(node):
218
+    node_id = node.data["id"]
219
+    if not node.data["online"]:
220
+        LOG.warning("Not possible to restart mcollective on the offline "
221
+                    "node %s", node_id)
222
+        return None
223
+    try:
224
+        ssh.call(["service", "mcollective", "restart"], node=node)
225
+    except Exception as exc:
226
+        LOG.warning("Failed to restart mcollective on the node %s: %s",
227
+                    node_id, exc)
228
+        return False
229
+    else:
230
+        LOG.info("The mcollective service was successfully restarted on "
231
+                 "the node %s", node_id)
232
+        return True

Loading…
Cancel
Save