Statistics
| Branch: | Tag: | Revision:

root / qa / qa_group.py @ 67bd83ae

History | View | Annotate | Download (12.1 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""QA tests for node groups.
23

24
"""
25

    
26
from ganeti import constants
27
from ganeti import netutils
28
from ganeti import query
29
from ganeti import utils
30

    
31
import qa_iptables
32
import qa_config
33
import qa_utils
34

    
35
from qa_utils import AssertCommand, AssertEqual, GetCommandOutput
36

    
37

    
38
def GetDefaultGroup():
39
  """Returns the default node group.
40

41
  """
42
  groups = qa_config.get("groups", {})
43
  return groups.get("group-with-nodes", constants.INITIAL_NODE_GROUP_NAME)
44

    
45

    
46
def ConfigureGroups():
47
  """Configures groups and nodes for tests such as custom SSH ports.
48

49
  """
50

    
51
  defgroup = GetDefaultGroup()
52
  nodes = qa_config.get("nodes")
53
  options = qa_config.get("options", {})
54

    
55
  # Clear any old configuration
56
  qa_iptables.CleanRules(nodes)
57

    
58
  # Custom SSH ports:
59
  ssh_port = options.get("ssh-port")
60
  default_ssh_port = netutils.GetDaemonPort(constants.SSH)
61
  if (ssh_port is not None) and (ssh_port != default_ssh_port):
62
    ModifyGroupSshPort(qa_iptables.GLOBAL_RULES, defgroup, nodes, ssh_port)
63

    
64

    
65
def ModifyGroupSshPort(ipt_rules, group, nodes, ssh_port):
66
  """Modifies the node group settings and sets up iptable rules.
67

68
  For each pair of nodes add two rules that affect SSH connections from one
69
  to the other one.
70
  The first one redirects port 22 to some unused port so that connecting
71
  through 22 fails. The second redirects port `ssh_port` to port 22.
72
  Together this results in master seeing the SSH daemons on the nodes on
73
  `ssh_port` instead of 22.
74
  """
75
  default_ssh_port = netutils.GetDaemonPort(constants.SSH)
76
  all_nodes = qa_config.get("nodes")
77
  AssertCommand(["gnt-group", "modify",
78
                 "--node-parameters=ssh_port=" + str(ssh_port),
79
                 group])
80
  for node in nodes:
81
    ipt_rules.RedirectPort(node.primary, "localhost",
82
                           default_ssh_port, 65535)
83
    ipt_rules.RedirectPort(node.primary, "localhost",
84
                           ssh_port, default_ssh_port)
85
    for node2 in all_nodes:
86
      ipt_rules.RedirectPort(node2.primary, node.primary,
87
                             default_ssh_port, 65535)
88
      ipt_rules.RedirectPort(node2.primary, node.primary,
89
                             ssh_port, default_ssh_port)
90

    
91

    
92
def TestGroupAddRemoveRename():
93
  """gnt-group add/remove/rename"""
94
  existing_group_with_nodes = GetDefaultGroup()
95

    
96
  (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
97

    
98
  AssertCommand(["gnt-group", "add", group1])
99
  AssertCommand(["gnt-group", "add", group2])
100
  AssertCommand(["gnt-group", "add", group2], fail=True)
101
  AssertCommand(["gnt-group", "add", existing_group_with_nodes], fail=True)
102

    
103
  AssertCommand(["gnt-group", "rename", group1, group2], fail=True)
104
  AssertCommand(["gnt-group", "rename", group1, group3])
105

    
106
  try:
107
    AssertCommand(["gnt-group", "rename", existing_group_with_nodes, group1])
108

    
109
    AssertCommand(["gnt-group", "remove", group2])
110
    AssertCommand(["gnt-group", "remove", group3])
111
    AssertCommand(["gnt-group", "remove", group1], fail=True)
112
  finally:
113
    # Try to ensure idempotency re groups that already existed.
114
    AssertCommand(["gnt-group", "rename", group1, existing_group_with_nodes])
115

    
116

    
117
def TestGroupAddWithOptions():
118
  """gnt-group add with options"""
119
  (group1, ) = qa_utils.GetNonexistentGroups(1)
120

    
121
  AssertCommand(["gnt-group", "add", "--alloc-policy", "notvalid", group1],
122
                fail=True)
123

    
124
  AssertCommand(["gnt-group", "add", "--alloc-policy", "last_resort",
125
                 "--node-parameters", "oob_program=/bin/true", group1])
126

    
127
  AssertCommand(["gnt-group", "remove", group1])
128

    
129

    
130
class NewGroupCtx(object):
131
  """Creates a new group and disposes afterwards."""
132

    
133
  def __enter__(self):
134
    (self._group, ) = qa_utils.GetNonexistentGroups(1)
135
    AssertCommand(["gnt-group", "add", self._group])
136
    return self._group
137

    
138
  def __exit__(self, exc_type, exc_val, exc_tb):
139
    AssertCommand(["gnt-group", "remove", self._group])
140

    
141

    
142
def _GetGroupIPolicy(groupname):
143
  """Return the run-time values of the cluster-level instance policy.
144

145
  @type groupname: string
146
  @param groupname: node group name
147
  @rtype: tuple
148
  @return: (policy, specs), where:
149
      - policy is a dictionary of the policy values, instance specs excluded
150
      - specs is a dictionary containing only the specs, using the internal
151
        format (see L{constants.IPOLICY_DEFAULTS} for an example), but without
152
        the standard values
153

154
  """
155
  info = qa_utils.GetObjectInfo(["gnt-group", "info", groupname])
156
  assert len(info) == 1
157
  policy = info[0]["Instance policy"]
158

    
159
  (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
160

    
161
  # Sanity checks
162
  assert "minmax" in ret_specs
163
  assert len(ret_specs["minmax"]) > 0
164
  assert len(ret_policy) > 0
165
  return (ret_policy, ret_specs)
166

    
167

    
168
def _TestGroupSetISpecs(groupname, new_specs=None, diff_specs=None,
169
                        fail=False, old_values=None):
170
  """Change instance specs on a group.
171

172
  At most one of new_specs or diff_specs can be specified.
173

174
  @type groupname: string
175
  @param groupname: group name
176
  @type new_specs: dict
177
  @param new_specs: new complete specs, in the same format returned by
178
      L{_GetGroupIPolicy}
179
  @type diff_specs: dict
180
  @param diff_specs: partial specs, it can be an incomplete specifications, but
181
      if min/max specs are specified, their number must match the number of the
182
      existing specs
183
  @type fail: bool
184
  @param fail: if the change is expected to fail
185
  @type old_values: tuple
186
  @param old_values: (old_policy, old_specs), as returned by
187
      L{_GetGroupIPolicy}
188
  @return: same as L{_GetGroupIPolicy}
189

190
  """
191
  build_cmd = lambda opts: ["gnt-group", "modify"] + opts + [groupname]
192
  get_policy = lambda: _GetGroupIPolicy(groupname)
193
  return qa_utils.TestSetISpecs(
194
    new_specs=new_specs, diff_specs=diff_specs,
195
    get_policy_fn=get_policy, build_cmd_fn=build_cmd,
196
    fail=fail, old_values=old_values)
197

    
198

    
199
def _TestGroupModifyISpecs(groupname):
200
  # This test is built on the assumption that the default ipolicy holds for
201
  # the node group under test
202
  old_values = _GetGroupIPolicy(groupname)
203
  samevals = dict((p, 4) for p in constants.ISPECS_PARAMETERS)
204
  base_specs = {
205
    constants.ISPECS_MINMAX: [{
206
      constants.ISPECS_MIN: samevals,
207
      constants.ISPECS_MAX: samevals,
208
      }],
209
    }
210
  mod_values = _TestGroupSetISpecs(groupname, new_specs=base_specs,
211
                                   old_values=old_values)
212
  for par in constants.ISPECS_PARAMETERS:
213
    # First make sure that the test works with good values
214
    good_specs = {
215
      constants.ISPECS_MINMAX: [{
216
        constants.ISPECS_MIN: {par: 8},
217
        constants.ISPECS_MAX: {par: 8},
218
        }],
219
      }
220
    mod_values = _TestGroupSetISpecs(groupname, diff_specs=good_specs,
221
                                     old_values=mod_values)
222
    bad_specs = {
223
      constants.ISPECS_MINMAX: [{
224
        constants.ISPECS_MIN: {par: 8},
225
        constants.ISPECS_MAX: {par: 4},
226
        }],
227
      }
228
    _TestGroupSetISpecs(groupname, diff_specs=bad_specs, fail=True,
229
                        old_values=mod_values)
230
  AssertCommand(["gnt-group", "modify", "--ipolicy-bounds-specs", "default",
231
                 groupname])
232
  AssertEqual(_GetGroupIPolicy(groupname), old_values)
233

    
234
  # Get the ipolicy command (from the cluster config)
235
  mnode = qa_config.GetMasterNode()
236
  addcmd = GetCommandOutput(mnode.primary, utils.ShellQuoteArgs([
237
    "gnt-group", "show-ispecs-cmd", "--include-defaults", groupname,
238
    ]))
239
  modcmd = ["gnt-group", "modify"]
240
  opts = addcmd.split()
241
  assert opts[0:2] == ["gnt-group", "add"]
242
  for k in range(2, len(opts) - 1):
243
    if opts[k].startswith("--ipolicy-"):
244
      assert k + 2 <= len(opts)
245
      modcmd.extend(opts[k:k + 2])
246
  modcmd.append(groupname)
247
  # Apply the ipolicy to the group and verify the result
248
  AssertCommand(modcmd)
249
  new_addcmd = GetCommandOutput(mnode.primary, utils.ShellQuoteArgs([
250
    "gnt-group", "show-ispecs-cmd", groupname,
251
    ]))
252
  AssertEqual(addcmd, new_addcmd)
253

    
254

    
255
def _TestGroupModifyIPolicy(groupname):
256
  _TestGroupModifyISpecs(groupname)
257

    
258
  # We assume that the default ipolicy holds
259
  (old_policy, old_specs) = _GetGroupIPolicy(groupname)
260
  for (par, setval, iname, expval) in [
261
    ("vcpu-ratio", 1.5, None, 1.5),
262
    ("spindle-ratio", 1.5, None, 1.5),
263
    ("disk-templates", constants.DT_PLAIN,
264
     "allowed disk templates", constants.DT_PLAIN)
265
    ]:
266
    if not iname:
267
      iname = par
268
    build_cmdline = lambda val: ["gnt-group", "modify", "--ipolicy-" + par,
269
                                 str(val), groupname]
270

    
271
    AssertCommand(build_cmdline(setval))
272
    (new_policy, new_specs) = _GetGroupIPolicy(groupname)
273
    AssertEqual(new_specs, old_specs)
274
    for (p, val) in new_policy.items():
275
      if p == iname:
276
        AssertEqual(val, expval)
277
      else:
278
        AssertEqual(val, old_policy[p])
279

    
280
    AssertCommand(build_cmdline("default"))
281
    (new_policy, new_specs) = _GetGroupIPolicy(groupname)
282
    AssertEqual(new_specs, old_specs)
283
    AssertEqual(new_policy, old_policy)
284

    
285

    
286
def TestGroupModify():
287
  """gnt-group modify"""
288
  # This tests assumes LVM to be enabled, thus it should skip if
289
  # this is not the case
290
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
291
    return
292
  (group1, ) = qa_utils.GetNonexistentGroups(1)
293

    
294
  AssertCommand(["gnt-group", "add", group1])
295

    
296
  try:
297
    _TestGroupModifyIPolicy(group1)
298
    AssertCommand(["gnt-group", "modify", "--alloc-policy", "unallocable",
299
                   "--node-parameters", "oob_program=/bin/false", group1])
300
    AssertCommand(["gnt-group", "modify",
301
                   "--alloc-policy", "notvalid", group1], fail=True)
302
    AssertCommand(["gnt-group", "modify",
303
                   "--node-parameters", "spindle_count=10", group1])
304
    if qa_config.TestEnabled("htools"):
305
      AssertCommand(["hbal", "-L", "-G", group1])
306
    AssertCommand(["gnt-group", "modify",
307
                   "--node-parameters", "spindle_count=default", group1])
308
  finally:
309
    AssertCommand(["gnt-group", "remove", group1])
310

    
311

    
312
def TestGroupList():
313
  """gnt-group list"""
314
  qa_utils.GenericQueryTest("gnt-group", query.GROUP_FIELDS.keys())
315

    
316

    
317
def TestGroupListFields():
318
  """gnt-group list-fields"""
319
  qa_utils.GenericQueryFieldsTest("gnt-group", query.GROUP_FIELDS.keys())
320

    
321

    
322
def TestAssignNodesIncludingSplit(orig_group, node1, node2):
323
  """gnt-group assign-nodes --force
324

325
  Expects node1 and node2 to be primary and secondary for a common instance.
326

327
  """
328
  assert node1 != node2
329

    
330
  (other_group, ) = qa_utils.GetNonexistentGroups(1)
331

    
332
  master_node = qa_config.GetMasterNode().primary
333

    
334
  def AssertInGroup(group, nodes):
335
    real_output = GetCommandOutput(master_node,
336
                                   "gnt-node list --no-headers -o group " +
337
                                   utils.ShellQuoteArgs(nodes))
338
    AssertEqual(real_output.splitlines(), [group] * len(nodes))
339

    
340
  AssertInGroup(orig_group, [node1, node2])
341
  AssertCommand(["gnt-group", "add", other_group])
342

    
343
  try:
344
    AssertCommand(["gnt-group", "assign-nodes", other_group, node1, node2])
345
    AssertInGroup(other_group, [node1, node2])
346

    
347
    # This should fail because moving node1 to orig_group would leave their
348
    # common instance split between orig_group and other_group.
349
    AssertCommand(["gnt-group", "assign-nodes", orig_group, node1], fail=True)
350
    AssertInGroup(other_group, [node1, node2])
351

    
352
    AssertCommand(["gnt-group", "assign-nodes", "--force", orig_group, node1])
353
    AssertInGroup(orig_group, [node1])
354
    AssertInGroup(other_group, [node2])
355

    
356
    AssertCommand(["gnt-group", "assign-nodes", orig_group, node2])
357
    AssertInGroup(orig_group, [node1, node2])
358
  finally:
359
    AssertCommand(["gnt-group", "remove", other_group])