Commit ec7b6d63 authored by Petr Pudlak's avatar Petr Pudlak Committed by Petr Pudlak

Update QA to test custom SSH ports

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 tests 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: default avatarPetr Pudlak <>
Reviewed-by: default avatarHrvoje Ribicic <>
parent 651ce6a3
......@@ -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)
# Test on empty cluster
RunTestIf("node-list", qa_node.TestNodeList)
RunTestIf("instance-list", qa_instance.TestInstanceList)
......@@ -264,7 +264,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
......@@ -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
# 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),
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()
#!/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
# 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
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):
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
def _AddNode(self, node):
if self._ctx is not None:
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],
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)],
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
if not hasattr(nodes, '__iter__'):
nodes = [nodes]
for node in nodes:
AssertCommand(("iptables-save | grep -v '%s' | iptables-restore" %
(marker, )),
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment