I did not notice this before, but your description has been wrapped at 80 chars / line when it should have been wrapped at 72 / line.
I will fix that, but otherwise LGTM, thanks! On Tue, Nov 12, 2013 at 3:41 PM, Petr Pudlak <[email protected]> wrote: > This patch adds the `ssh-port` option. If set to a non-standard port, the > QA > script sets up the default node group with this port, and before running > test > it adds `iptable` rules to all nodes so that the nodes see each other's SSH > servers as running on this port. Their SSH configuration is _not_ changed > and > other machines see the nodes on 22 as before. > > The `iptable` rules are reset on each QA run, trying to preserve any > existing > rules (not created by the script) that might be present. > > Signed-off-by: Petr Pudlak <[email protected]> > --- > qa/ganeti-qa.py | 2 + > qa/qa-sample.json | 4 +- > qa/qa_group.py | 48 ++++++++++++++++++++++++ > qa/qa_iptables.py | 107 > ++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 160 insertions(+), 1 deletion(-) > create mode 100644 qa/qa_iptables.py > > diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py > index bf26cc5..b62fe9d 100755 > --- a/qa/ganeti-qa.py > +++ b/qa/ganeti-qa.py > @@ -181,6 +181,8 @@ def SetupCluster(rapi_user): > # To support RAPI on an existing cluster we have to find out the > secret > rapi_secret = _LookupRapiSecret(rapi_user) > > + qa_group.ConfigureGroups() > + > # Test on empty cluster > RunTestIf("node-list", qa_node.TestNodeList) > RunTestIf("instance-list", qa_instance.TestInstanceList) > diff --git a/qa/qa-sample.json b/qa/qa-sample.json > index fd28f38..f16d9ab 100644 > --- a/qa/qa-sample.json > +++ b/qa/qa-sample.json > @@ -263,7 +263,9 @@ > "burnin-rename": "xen-test-rename", > "burnin-reboot": true, > "reboot-types": ["soft", "hard", "full"], > - "use-iallocators": false > + "use-iallocators": false, > + "# Uncomment if you want to run the whole cluster on a different SSH > port": null, > + "# ssh-port": 222 > }, > > "# vim: set syntax=javascript :": null > diff --git a/qa/qa_group.py b/qa/qa_group.py > index f11e512..da88602 100644 > --- a/qa/qa_group.py > +++ b/qa/qa_group.py > @@ -24,9 +24,11 @@ > """ > > from ganeti import constants > +from ganeti import netutils > from ganeti import query > from ganeti import utils > > +import qa_iptables > import qa_config > import qa_utils > > @@ -41,6 +43,52 @@ def GetDefaultGroup(): > return groups.get("group-with-nodes", constants.INITIAL_NODE_GROUP_NAME) > > > +def ConfigureGroups(): > + """Configures groups and nodes for tests such as custom SSH ports. > + > + """ > + > + defgroup = GetDefaultGroup() > + nodes = qa_config.get("nodes") > + options = qa_config.get("options", {}) > + > + # Clear any old configuration > + qa_iptables.CleanRules(nodes) > + > + # Custom SSH ports: > + ssh_port = options.get("ssh-port") > + default_ssh_port = netutils.GetDaemonPort(constants.SSH) > + if (ssh_port is not None) and (ssh_port != default_ssh_port): > + ModifyGroupSshPort(qa_iptables.GLOBAL_RULES, defgroup, nodes, > ssh_port) > + > + > +def ModifyGroupSshPort(ipt_rules, group, nodes, ssh_port): > + """Modifies the node group settings and sets up iptable rules. > + > + For each pair of nodes add two rules that affect SSH connections from > one > + to the other one. > + The first one redirects port 22 to some unused port so that connecting > + through 22 fails. The second redirects port `ssh_port` to port 22. > + Together this results in master seeing the SSH daemons on the nodes on > + `ssh_port` instead of 22. > + """ > + default_ssh_port = netutils.GetDaemonPort(constants.SSH) > + all_nodes = qa_config.get("nodes") > + AssertCommand(["gnt-group", "modify", > + "--node-parameters=ssh_port=" + str(ssh_port), > + group]) > + for node in nodes: > + ipt_rules.RedirectPort(node.primary, "localhost", > + default_ssh_port, 65535) > + ipt_rules.RedirectPort(node.primary, "localhost", > + ssh_port, default_ssh_port) > + for node2 in all_nodes: > + ipt_rules.RedirectPort(node2.primary, node.primary, > + default_ssh_port, 65535) > + ipt_rules.RedirectPort(node2.primary, node.primary, > + ssh_port, default_ssh_port) > + > + > def TestGroupAddRemoveRename(): > """gnt-group add/remove/rename""" > existing_group_with_nodes = GetDefaultGroup() > diff --git a/qa/qa_iptables.py b/qa/qa_iptables.py > new file mode 100644 > index 0000000..a561039 > --- /dev/null > +++ b/qa/qa_iptables.py > @@ -0,0 +1,107 @@ > +#!/usr/bin/python -u > +# > + > +# Copyright (C) 2013 Google Inc. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 2 of the License, or > +# (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, but > +# WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +# General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > +# 02110-1301, USA. > + > + > +"""Manipulates nodes using `iptables` to simulate non-standard network > +conditions. > + > +""" > + > +import uuid > + > +import qa_config > +import qa_utils > + > +from qa_utils import AssertCommand > + > +# String used as a comment for produced `iptables` results > +IPTABLES_COMMENT_MARKER = "ganeti_qa_script" > + > + > +class RulesContext(object): > + def __init__(self, nodes): > + self._nodes = set() > + > + def __enter__(self): > + self._marker = IPTABLES_COMMENT_MARKER + "_" + str(uuid.uuid4()) > + return Rules(self) > + > + def __exit__(self, ext_type, exc_val, exc_tb): > + CleanRules(self._nodes, self._marker) > + > + def _AddNode(self, node): > + self._nodes.add(node) > + > + > +class Rules(object): > + """Allows to introduce iptable rules and dispose them at the end of a > block. > + > + Don't instantiate this class directly. Use `with RulesContext() as r` > instead. > + """ > + > + def __init__(self, ctx=None): > + self._ctx = ctx > + if self._ctx is not None: > + self._marker = self._ctx._marker > + else: > + self._marker = IPTABLES_COMMENT_MARKER > + > + def _AddNode(self, node): > + if self._ctx is not None: > + self._ctx._AddNode(node) > + > + def AppendRule(self, node, chain, rule, table="filter"): > + """Appends an `iptables` rule to a given node > + """ > + AssertCommand(["iptables", "-t", table, "-A", chain] + > + rule + > + ["-m", "comment", > + "--comment", self._marker], > + node=node) > + self._AddNode(node) > + > + def RedirectPort(self, node, host, port, new_port): > + """Adds a rule to a master node that makes a destination host+port > visible > + under a different port number. > + > + """ > + self.AppendRule(node, "OUTPUT", > + ["--protocol", "tcp", > + "--destination", host, "--dport", str(port), > + "--jump", "DNAT", > + "--to-destination", ":" + str(new_port)], > + table="nat") > + > + > +GLOBAL_RULES = Rules() > + > + > +def CleanRules(nodes, marker=IPTABLES_COMMENT_MARKER): > + """Removes all QA `iptables` rules matching a given marker from a given > node. > + > + If no marker is given, the global default is used, which clean all > custom > + markers. > + """ > + if not hasattr(nodes, '__iter__'): > + nodes = [nodes] > + for node in nodes: > + AssertCommand(("iptables-save | grep -v '%s' | iptables-restore" % > + (marker, )), > + node=node) > -- > 1.8.4.1 > > Hrvoje Ribicic Ganeti Engineering Google Germany GmbH Dienerstr. 12, 80331, München Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg Geschäftsführer: Graham Law, Christine Elizabeth Flores Steuernummer: 48/725/00206 Umsatzsteueridentifikationsnummer: DE813741370
