The actions of the maintainance daemon are tested for the cases of live-repair, evacuate, and evacuate-failover.
Signed-off-by: Bhimanavajjula Aditya <[email protected]> --- Makefile.am | 1 + qa/ganeti-qa.py | 8 +++ qa/qa-sample.json | 3 +- qa/qa_maintd.py | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 qa/qa_maintd.py diff --git a/Makefile.am b/Makefile.am index 5df7d2c..4086c51 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1347,6 +1347,7 @@ qa_scripts = \ qa/qa_job.py \ qa/qa_job_utils.py \ qa/qa_logging.py \ + qa/qa_maintd.py \ qa/qa_monitoring.py \ qa/qa_network.py \ qa/qa_node.py \ diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index 8e7fada..5b94faf 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -50,6 +50,7 @@ import qa_filters import qa_group import qa_instance import qa_iptables +import qa_maintd import qa_monitoring import qa_network import qa_node @@ -888,6 +889,12 @@ def RunMonitoringTests(): RunTestIf("mon-collector", qa_monitoring.TestInstStatusCollector) +def RunMaintdTests(): + RunTestIf("maintd", qa_maintd.TestEvacuate) + RunTestIf("maintd", qa_maintd.TestEvacuateFailover) + RunTestIf("maintd", qa_maintd.TestLiveRepair) + + PARALLEL_TEST_DICT = { "parallel-failover": qa_performance.TestParallelInstanceFailover, "parallel-migration": qa_performance.TestParallelInstanceMigration, @@ -1078,6 +1085,7 @@ def RunQa(): qa_cluster.AssertClusterVerify() RunTestBlock(RunMonitoringTests) + RunTestBlock(RunMaintdTests) RunPerformanceTests() diff --git a/qa/qa-sample.json b/qa/qa-sample.json index 0a60708..ac10a05 100644 --- a/qa/qa-sample.json +++ b/qa/qa-sample.json @@ -275,7 +275,8 @@ "default-instance-tests": true, "exclusive-storage-instance-tests": false, - "mon-collector": true + "mon-collector": true, + "maintd": true }, "options": { diff --git a/qa/qa_maintd.py b/qa/qa_maintd.py new file mode 100644 index 0000000..474e683 --- /dev/null +++ b/qa/qa_maintd.py @@ -0,0 +1,196 @@ +# +# + +# Copyright (C) 2015 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"""Maintainance daemon tests. + +""" + +import random +import os.path + +from ganeti import serializer +from ganeti.utils import retry + +import qa_config +import qa_error + +from qa_utils import AssertCommand, \ + UploadData, \ + stdout_of +from qa_instance_utils import CreateInstanceDrbd8, \ + RemoveInstance + + +def _GetMaintTags(node): + tags = stdout_of(["gnt-node", + "list-tags", + node.primary + ]).split() + return set([t for t in tags if t.startswith('maintd:repairready:')]) + + +def _AssertRepairTagAddition(node, preexisting_tags): + def fn(): + tags = _GetMaintTags(node) + if not preexisting_tags < tags: + raise retry.RetryAgain() + retry.Retry(fn, 5.0, 500.0) + + +def _AssertNodeDrained(node): + def fn(): + out = stdout_of(["gnt-node", + "list", + "--output=name", + "--no-headers", + "--filter", + "drained" + ]) + if node.primary not in out: + raise retry.RetryAgain() + retry.Retry(fn, 5.0, 500.0) + + +def _AssertInstanceMove(inst, move_type): + def fn(): + out = stdout_of(["gnt-job", + "list", + "--output=status", + "--no-headers", + "--filter", + '"%s(%s)" in summary' % (move_type, inst.name) + ]) + if 'success' not in out: + raise retry.RetryAgain() + retry.Retry(fn, 5.0, 500.0) + + +def _AssertRepairCommand(): + def fn(): + out = stdout_of(["gnt-job", + "list", + "--output=status", + "--no-headers", + "--filter", + '"REPAIR_COMMAND" in summary' + ]) + if 'success' not in out: + raise retry.RetryAgain() + retry.Retry(fn, 5.0, 500.0) + + +def _TestEvac(filepath, filecontent, inst_move_type): + node1, node2 = qa_config.AcquireManyNodes( + 2, + exclude=qa_config.GetMasterNode()) + inst = CreateInstanceDrbd8([node1, node2]) + UploadData(node1.primary, filecontent, 0755, filepath) + pt = _GetMaintTags(node1) + AssertCommand(["gnt-cluster", + "modify", + "--diagnose-data-collector-filename", + os.path.basename(filepath) + ]) + _AssertNodeDrained(node1) + _AssertInstanceMove(inst, inst_move_type) + _AssertRepairTagAddition(node1, pt) + AssertCommand(["gnt-node", + "modify", + "--drained=no", + node1.primary + ]) + RemoveInstance(inst) + inst.Release() + node1.Release() + node2.Release() + + +def TestEvacuate(): + """Test node evacuate upon diagnosis. + + """ + AssertCommand(["gnt-cluster", + "modify", + "--maintenance-interval=3" + ]) + n = random.randint(10000, 99999) + _TestEvac('/etc/ganeti/node-diagnose-commands/evacuate', + 'echo \'' + serializer.DumpJson({ + "status": "evacuate", + "details": "qa evacuate test %d" % n}).strip() + '\'', + 'INSTANCE_MIGRATE') + + +def TestEvacuateFailover(): + """Test node evacuate failover upon diagnosis. + + """ + n = random.randint(10000, 99999) + _TestEvac('/etc/ganeti/node-diagnose-commands/evacuate-failover', + 'echo \'' + serializer.DumpJson({ + "status": "evacuate-failover", + "details": "qa evacuate failover test %d" % n}).strip() + '\'', + 'INSTANCE_FAILOVER') + + +def TestLiveRepair(): + """Test node evacuate failover upon diagnosis. + + """ + n = random.randint(10000, 99999) + node = qa_config.AcquireNode(exclude=qa_config.GetMasterNode()) + pt = _GetMaintTags(node) + UploadData(node.primary, + 'echo \'' + serializer.DumpJson({ + "status": "live-repair", + "command": "repair", + "details": str(n)}).strip() + '\'', + 0755, + '/etc/ganeti/node-diagnose-commands/live-repair') + UploadData(node.primary, + """#!/usr/bin/python +import sys +import json + +n = json.loads(sys.stdin.read())['details'] +with open('/tmp/' + n, 'w') as f: + f.write(n) +""", + 0755, + '/etc/ganeti/node-repair-commands/repair') + AssertCommand(["gnt-cluster", + "modify", + "--diagnose-data-collector-filename", + "live-repair" + ]) + _AssertRepairCommand() + _AssertRepairTagAddition(node, pt) + if str(n) != AssertCommand(["cat", "/tmp/" + str(n)], node=node)[1]: + raise qa_error.Error('Repair command was unsuccessful') -- 2.6.0.rc2.230.g3dd15c0
