Improve error message for replace-disks
[ganeti-local] / lib / cmdlib / test.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 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 """Test logical units."""
23
24 import logging
25 import shutil
26 import socket
27 import tempfile
28
29 from ganeti import compat
30 from ganeti import constants
31 from ganeti import errors
32 from ganeti import locking
33 from ganeti import utils
34 from ganeti.masterd import iallocator
35 from ganeti.cmdlib.base import NoHooksLU
36 from ganeti.cmdlib.common import ExpandInstanceName, GetWantedNodes, \
37   GetWantedInstances
38
39
40 class LUTestDelay(NoHooksLU):
41   """Sleep for a specified amount of time.
42
43   This LU sleeps on the master and/or nodes for a specified amount of
44   time.
45
46   """
47   REQ_BGL = False
48
49   def ExpandNames(self):
50     """Expand names and set required locks.
51
52     This expands the node list, if any.
53
54     """
55     self.needed_locks = {}
56     if self.op.on_nodes:
57       # _GetWantedNodes can be used here, but is not always appropriate to use
58       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
59       # more information.
60       self.op.on_nodes = GetWantedNodes(self, self.op.on_nodes)
61       self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
62
63   def _TestDelay(self):
64     """Do the actual sleep.
65
66     """
67     if self.op.on_master:
68       if not utils.TestDelay(self.op.duration):
69         raise errors.OpExecError("Error during master delay test")
70     if self.op.on_nodes:
71       result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
72       for node, node_result in result.items():
73         node_result.Raise("Failure during rpc call to node %s" % node)
74
75   def Exec(self, feedback_fn):
76     """Execute the test delay opcode, with the wanted repetitions.
77
78     """
79     if self.op.repeat == 0:
80       self._TestDelay()
81     else:
82       top_value = self.op.repeat - 1
83       for i in range(self.op.repeat):
84         self.LogInfo("Test delay iteration %d/%d", i, top_value)
85         self._TestDelay()
86
87
88 class LUTestJqueue(NoHooksLU):
89   """Utility LU to test some aspects of the job queue.
90
91   """
92   REQ_BGL = False
93
94   # Must be lower than default timeout for WaitForJobChange to see whether it
95   # notices changed jobs
96   _CLIENT_CONNECT_TIMEOUT = 20.0
97   _CLIENT_CONFIRM_TIMEOUT = 60.0
98
99   @classmethod
100   def _NotifyUsingSocket(cls, cb, errcls):
101     """Opens a Unix socket and waits for another program to connect.
102
103     @type cb: callable
104     @param cb: Callback to send socket name to client
105     @type errcls: class
106     @param errcls: Exception class to use for errors
107
108     """
109     # Using a temporary directory as there's no easy way to create temporary
110     # sockets without writing a custom loop around tempfile.mktemp and
111     # socket.bind
112     tmpdir = tempfile.mkdtemp()
113     try:
114       tmpsock = utils.PathJoin(tmpdir, "sock")
115
116       logging.debug("Creating temporary socket at %s", tmpsock)
117       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
118       try:
119         sock.bind(tmpsock)
120         sock.listen(1)
121
122         # Send details to client
123         cb(tmpsock)
124
125         # Wait for client to connect before continuing
126         sock.settimeout(cls._CLIENT_CONNECT_TIMEOUT)
127         try:
128           (conn, _) = sock.accept()
129         except socket.error, err:
130           raise errcls("Client didn't connect in time (%s)" % err)
131       finally:
132         sock.close()
133     finally:
134       # Remove as soon as client is connected
135       shutil.rmtree(tmpdir)
136
137     # Wait for client to close
138     try:
139       try:
140         # pylint: disable=E1101
141         # Instance of '_socketobject' has no ... member
142         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
143         conn.recv(1)
144       except socket.error, err:
145         raise errcls("Client failed to confirm notification (%s)" % err)
146     finally:
147       conn.close()
148
149   def _SendNotification(self, test, arg, sockname):
150     """Sends a notification to the client.
151
152     @type test: string
153     @param test: Test name
154     @param arg: Test argument (depends on test)
155     @type sockname: string
156     @param sockname: Socket path
157
158     """
159     self.Log(constants.ELOG_JQUEUE_TEST, (sockname, test, arg))
160
161   def _Notify(self, prereq, test, arg):
162     """Notifies the client of a test.
163
164     @type prereq: bool
165     @param prereq: Whether this is a prereq-phase test
166     @type test: string
167     @param test: Test name
168     @param arg: Test argument (depends on test)
169
170     """
171     if prereq:
172       errcls = errors.OpPrereqError
173     else:
174       errcls = errors.OpExecError
175
176     return self._NotifyUsingSocket(compat.partial(self._SendNotification,
177                                                   test, arg),
178                                    errcls)
179
180   def CheckArguments(self):
181     self.checkargs_calls = getattr(self, "checkargs_calls", 0) + 1
182     self.expandnames_calls = 0
183
184   def ExpandNames(self):
185     checkargs_calls = getattr(self, "checkargs_calls", 0)
186     if checkargs_calls < 1:
187       raise errors.ProgrammerError("CheckArguments was not called")
188
189     self.expandnames_calls += 1
190
191     if self.op.notify_waitlock:
192       self._Notify(True, constants.JQT_EXPANDNAMES, None)
193
194     self.LogInfo("Expanding names")
195
196     # Get lock on master node (just to get a lock, not for a particular reason)
197     self.needed_locks = {
198       locking.LEVEL_NODE: self.cfg.GetMasterNode(),
199       }
200
201   def Exec(self, feedback_fn):
202     if self.expandnames_calls < 1:
203       raise errors.ProgrammerError("ExpandNames was not called")
204
205     if self.op.notify_exec:
206       self._Notify(False, constants.JQT_EXEC, None)
207
208     self.LogInfo("Executing")
209
210     if self.op.log_messages:
211       self._Notify(False, constants.JQT_STARTMSG, len(self.op.log_messages))
212       for idx, msg in enumerate(self.op.log_messages):
213         self.LogInfo("Sending log message %s", idx + 1)
214         feedback_fn(constants.JQT_MSGPREFIX + msg)
215         # Report how many test messages have been sent
216         self._Notify(False, constants.JQT_LOGMSG, idx + 1)
217
218     if self.op.fail:
219       raise errors.OpExecError("Opcode failure was requested")
220
221     return True
222
223
224 class LUTestAllocator(NoHooksLU):
225   """Run allocator tests.
226
227   This LU runs the allocator tests
228
229   """
230   def CheckPrereq(self):
231     """Check prerequisites.
232
233     This checks the opcode parameters depending on the director and mode test.
234
235     """
236     if self.op.mode in (constants.IALLOCATOR_MODE_ALLOC,
237                         constants.IALLOCATOR_MODE_MULTI_ALLOC):
238       for attr in ["memory", "disks", "disk_template",
239                    "os", "tags", "nics", "vcpus"]:
240         if not hasattr(self.op, attr):
241           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
242                                      attr, errors.ECODE_INVAL)
243       iname = self.cfg.ExpandInstanceName(self.op.name)
244       if iname is not None:
245         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
246                                    iname, errors.ECODE_EXISTS)
247       if not isinstance(self.op.nics, list):
248         raise errors.OpPrereqError("Invalid parameter 'nics'",
249                                    errors.ECODE_INVAL)
250       if not isinstance(self.op.disks, list):
251         raise errors.OpPrereqError("Invalid parameter 'disks'",
252                                    errors.ECODE_INVAL)
253       for row in self.op.disks:
254         if (not isinstance(row, dict) or
255             constants.IDISK_SIZE not in row or
256             not isinstance(row[constants.IDISK_SIZE], int) or
257             constants.IDISK_MODE not in row or
258             row[constants.IDISK_MODE] not in constants.DISK_ACCESS_SET):
259           raise errors.OpPrereqError("Invalid contents of the 'disks'"
260                                      " parameter", errors.ECODE_INVAL)
261       if self.op.hypervisor is None:
262         self.op.hypervisor = self.cfg.GetHypervisorType()
263     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
264       fname = ExpandInstanceName(self.cfg, self.op.name)
265       self.op.name = fname
266       self.relocate_from = \
267           list(self.cfg.GetInstanceInfo(fname).secondary_nodes)
268     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
269                           constants.IALLOCATOR_MODE_NODE_EVAC):
270       if not self.op.instances:
271         raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
272       self.op.instances = GetWantedInstances(self, self.op.instances)
273     else:
274       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
275                                  self.op.mode, errors.ECODE_INVAL)
276
277     if self.op.direction == constants.IALLOCATOR_DIR_OUT:
278       if self.op.iallocator is None:
279         raise errors.OpPrereqError("Missing allocator name",
280                                    errors.ECODE_INVAL)
281     elif self.op.direction != constants.IALLOCATOR_DIR_IN:
282       raise errors.OpPrereqError("Wrong allocator test '%s'" %
283                                  self.op.direction, errors.ECODE_INVAL)
284
285   def Exec(self, feedback_fn):
286     """Run the allocator test.
287
288     """
289     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
290       req = iallocator.IAReqInstanceAlloc(name=self.op.name,
291                                           memory=self.op.memory,
292                                           disks=self.op.disks,
293                                           disk_template=self.op.disk_template,
294                                           os=self.op.os,
295                                           tags=self.op.tags,
296                                           nics=self.op.nics,
297                                           vcpus=self.op.vcpus,
298                                           spindle_use=self.op.spindle_use,
299                                           hypervisor=self.op.hypervisor,
300                                           node_whitelist=None)
301     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
302       req = iallocator.IAReqRelocate(name=self.op.name,
303                                      relocate_from=list(self.relocate_from))
304     elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
305       req = iallocator.IAReqGroupChange(instances=self.op.instances,
306                                         target_groups=self.op.target_groups)
307     elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
308       req = iallocator.IAReqNodeEvac(instances=self.op.instances,
309                                      evac_mode=self.op.evac_mode)
310     elif self.op.mode == constants.IALLOCATOR_MODE_MULTI_ALLOC:
311       disk_template = self.op.disk_template
312       insts = [iallocator.IAReqInstanceAlloc(name="%s%s" % (self.op.name, idx),
313                                              memory=self.op.memory,
314                                              disks=self.op.disks,
315                                              disk_template=disk_template,
316                                              os=self.op.os,
317                                              tags=self.op.tags,
318                                              nics=self.op.nics,
319                                              vcpus=self.op.vcpus,
320                                              spindle_use=self.op.spindle_use,
321                                              hypervisor=self.op.hypervisor)
322                for idx in range(self.op.count)]
323       req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
324     else:
325       raise errors.ProgrammerError("Uncatched mode %s in"
326                                    " LUTestAllocator.Exec", self.op.mode)
327
328     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
329     if self.op.direction == constants.IALLOCATOR_DIR_IN:
330       result = ial.in_text
331     else:
332       ial.Run(self.op.iallocator, validate=False)
333       result = ial.out_text
334     return result