Extend qa to test hotplug support
[ganeti-local] / qa / qa_instance_utils.py
1 #
2 #
3
4 # Copyright (C) 2013 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 utility functions for managing instances
23
24 """
25
26 import operator
27
28 from ganeti import utils
29 from ganeti import constants
30 from ganeti import pathutils
31
32 import qa_config
33 import qa_error
34 import qa_utils
35
36 from qa_utils import AssertIn, AssertCommand
37
38
39 def RemoveInstance(instance):
40   AssertCommand(["gnt-instance", "remove", "-f", instance.name])
41
42
43 def GetGenericAddParameters(inst, disk_template, force_mac=None):
44   params = ["-B"]
45   params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
46                                  qa_config.get(constants.BE_MINMEM),
47                                  constants.BE_MAXMEM,
48                                  qa_config.get(constants.BE_MAXMEM)))
49
50   if disk_template != constants.DT_DISKLESS:
51     for idx, disk in enumerate(qa_config.GetDiskOptions()):
52       size = disk.get("size")
53       name = disk.get("name")
54       diskparams = "%s:size=%s" % (idx, size)
55       if name:
56         diskparams += ",name=%s" % name
57       if qa_config.AreSpindlesSupported():
58         spindles = disk.get("spindles")
59         if spindles is None:
60           raise qa_error.Error("'spindles' is a required parameter for disks"
61                                " when you enable exclusive storage tests")
62         diskparams += ",spindles=%s" % spindles
63       params.extend(["--disk", diskparams])
64
65   # Set static MAC address if configured
66   if force_mac:
67     nic0_mac = force_mac
68   else:
69     nic0_mac = inst.GetNicMacAddr(0, None)
70
71   if nic0_mac:
72     params.extend(["--net", "0:mac=%s" % nic0_mac])
73
74   return params
75
76
77 def _CreateInstanceByDiskTemplateRaw(nodes_spec, disk_template, fail=False):
78   """Creates an instance with the given disk template on the given nodes(s).
79      Note that this function does not check if enough nodes are given for
80      the respective disk template.
81
82   @type nodes_spec: string
83   @param nodes_spec: string specification of one node (by node name) or several
84                      nodes according to the requirements of the disk template
85   @type disk_template: string
86   @param disk_template: the disk template to be used by the instance
87   @return: the created instance
88
89   """
90   instance = qa_config.AcquireInstance()
91   try:
92     cmd = (["gnt-instance", "add",
93             "--os-type=%s" % qa_config.get("os"),
94             "--disk-template=%s" % disk_template,
95             "--node=%s" % nodes_spec] +
96            GetGenericAddParameters(instance, disk_template))
97     cmd.append(instance.name)
98
99     AssertCommand(cmd, fail=fail)
100
101     if not fail:
102       CheckSsconfInstanceList(instance.name)
103       instance.SetDiskTemplate(disk_template)
104
105       return instance
106   except:
107     instance.Release()
108     raise
109
110   # Handle the case where creation is expected to fail
111   assert fail
112   instance.Release()
113   return None
114
115
116 def CreateInstanceDrbd8(nodes, fail=False):
117   """Creates an instance using disk template 'drbd' on the given nodes.
118
119   @type nodes: list of nodes
120   @param nodes: nodes to be used by the instance
121   @return: the created instance
122
123   """
124   assert len(nodes) > 1
125   return _CreateInstanceByDiskTemplateRaw(
126     ":".join(map(operator.attrgetter("primary"), nodes)),
127     constants.DT_DRBD8, fail=fail)
128
129
130 def CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=False):
131   """Creates an instance using the given disk template for disk templates
132      for which one given node is sufficient. These templates are for example:
133      plain, diskless, file, sharedfile, blockdev, rados.
134
135   @type nodes: list of nodes
136   @param nodes: a list of nodes, whose first element is used to create the
137                 instance
138   @type disk_template: string
139   @param disk_template: the disk template to be used by the instance
140   @return: the created instance
141
142   """
143   assert len(nodes) > 0
144   return _CreateInstanceByDiskTemplateRaw(nodes[0].primary, disk_template,
145                                           fail=fail)
146
147
148 def CreateInstanceByDiskTemplate(nodes, disk_template, fail=False):
149   """Given a disk template, this function creates an instance using
150      the template. It uses the required number of nodes depending on
151      the disk template. This function is intended to be used by tests
152      that don't care about the specifics of the instance other than
153      that it uses the given disk template.
154
155      Note: If you use this function, make sure to call
156      'TestInstanceRemove' at the end of your tests to avoid orphaned
157      instances hanging around and interfering with the following tests.
158
159   @type nodes: list of nodes
160   @param nodes: the list of the nodes on which the instance will be placed;
161                 it needs to have sufficiently many elements for the given
162                 disk template
163   @type disk_template: string
164   @param disk_template: the disk template to be used by the instance
165   @return: the created instance
166
167   """
168   if disk_template == constants.DT_DRBD8:
169     return CreateInstanceDrbd8(nodes, fail=fail)
170   elif disk_template in [constants.DT_DISKLESS, constants.DT_PLAIN,
171                          constants.DT_FILE]:
172     return CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
173   else:
174     # FIXME: This assumes that for all other disk templates, we only need one
175     # node and no disk template specific parameters. This else-branch is
176     # currently only used in cases where we expect failure. Extend it when
177     # QA needs for these templates change.
178     return CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
179
180
181 def _ReadSsconfInstanceList():
182   """Reads ssconf_instance_list from the master node.
183
184   """
185   master = qa_config.GetMasterNode()
186
187   ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
188                                "ssconf_%s" % constants.SS_INSTANCE_LIST)
189
190   cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
191
192   return qa_utils.GetCommandOutput(master.primary,
193                                    utils.ShellQuoteArgs(cmd)).splitlines()
194
195
196 def CheckSsconfInstanceList(instance):
197   """Checks if a certain instance is in the ssconf instance list.
198
199   @type instance: string
200   @param instance: Instance name
201
202   """
203   AssertIn(qa_utils.ResolveInstanceName(instance),
204            _ReadSsconfInstanceList())