Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / misc.py @ 5eacbcae

History | View | Annotate | Download (13.8 kB)

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
"""Miscellaneous logical units that don't fit into any category."""
23

    
24
import logging
25
import time
26

    
27
from ganeti import compat
28
from ganeti import constants
29
from ganeti import errors
30
from ganeti import locking
31
from ganeti import qlang
32
from ganeti import query
33
from ganeti import utils
34
from ganeti.cmdlib.base import NoHooksLU, QueryBase
35
from ganeti.cmdlib.common import GetWantedNodes, SupportsOob
36

    
37

    
38
class LUOobCommand(NoHooksLU):
39
  """Logical unit for OOB handling.
40

41
  """
42
  REQ_BGL = False
43
  _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
44

    
45
  def ExpandNames(self):
46
    """Gather locks we need.
47

48
    """
49
    if self.op.node_names:
50
      self.op.node_names = GetWantedNodes(self, self.op.node_names)
51
      lock_names = self.op.node_names
52
    else:
53
      lock_names = locking.ALL_SET
54

    
55
    self.needed_locks = {
56
      locking.LEVEL_NODE: lock_names,
57
      }
58

    
59
    self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
60

    
61
    if not self.op.node_names:
62
      # Acquire node allocation lock only if all nodes are affected
63
      self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
64

    
65
  def CheckPrereq(self):
66
    """Check prerequisites.
67

68
    This checks:
69
     - the node exists in the configuration
70
     - OOB is supported
71

72
    Any errors are signaled by raising errors.OpPrereqError.
73

74
    """
75
    self.nodes = []
76
    self.master_node = self.cfg.GetMasterNode()
77

    
78
    assert self.op.power_delay >= 0.0
79

    
80
    if self.op.node_names:
81
      if (self.op.command in self._SKIP_MASTER and
82
          self.master_node in self.op.node_names):
83
        master_node_obj = self.cfg.GetNodeInfo(self.master_node)
84
        master_oob_handler = SupportsOob(self.cfg, master_node_obj)
85

    
86
        if master_oob_handler:
87
          additional_text = ("run '%s %s %s' if you want to operate on the"
88
                             " master regardless") % (master_oob_handler,
89
                                                      self.op.command,
90
                                                      self.master_node)
91
        else:
92
          additional_text = "it does not support out-of-band operations"
93

    
94
        raise errors.OpPrereqError(("Operating on the master node %s is not"
95
                                    " allowed for %s; %s") %
96
                                   (self.master_node, self.op.command,
97
                                    additional_text), errors.ECODE_INVAL)
98
    else:
99
      self.op.node_names = self.cfg.GetNodeList()
100
      if self.op.command in self._SKIP_MASTER:
101
        self.op.node_names.remove(self.master_node)
102

    
103
    if self.op.command in self._SKIP_MASTER:
104
      assert self.master_node not in self.op.node_names
105

    
106
    for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
107
      if node is None:
108
        raise errors.OpPrereqError("Node %s not found" % node_name,
109
                                   errors.ECODE_NOENT)
110
      else:
111
        self.nodes.append(node)
112

    
113
      if (not self.op.ignore_status and
114
          (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
115
        raise errors.OpPrereqError(("Cannot power off node %s because it is"
116
                                    " not marked offline") % node_name,
117
                                   errors.ECODE_STATE)
118

    
119
  def Exec(self, feedback_fn):
120
    """Execute OOB and return result if we expect any.
121

122
    """
123
    master_node = self.master_node
124
    ret = []
125

    
126
    for idx, node in enumerate(utils.NiceSort(self.nodes,
127
                                              key=lambda node: node.name)):
128
      node_entry = [(constants.RS_NORMAL, node.name)]
129
      ret.append(node_entry)
130

    
131
      oob_program = SupportsOob(self.cfg, node)
132

    
133
      if not oob_program:
134
        node_entry.append((constants.RS_UNAVAIL, None))
135
        continue
136

    
137
      logging.info("Executing out-of-band command '%s' using '%s' on %s",
138
                   self.op.command, oob_program, node.name)
139
      result = self.rpc.call_run_oob(master_node, oob_program,
140
                                     self.op.command, node.name,
141
                                     self.op.timeout)
142

    
143
      if result.fail_msg:
144
        self.LogWarning("Out-of-band RPC failed on node '%s': %s",
145
                        node.name, result.fail_msg)
146
        node_entry.append((constants.RS_NODATA, None))
147
      else:
148
        try:
149
          self._CheckPayload(result)
150
        except errors.OpExecError, err:
151
          self.LogWarning("Payload returned by node '%s' is not valid: %s",
152
                          node.name, err)
153
          node_entry.append((constants.RS_NODATA, None))
154
        else:
155
          if self.op.command == constants.OOB_HEALTH:
156
            # For health we should log important events
157
            for item, status in result.payload:
158
              if status in [constants.OOB_STATUS_WARNING,
159
                            constants.OOB_STATUS_CRITICAL]:
160
                self.LogWarning("Item '%s' on node '%s' has status '%s'",
161
                                item, node.name, status)
162

    
163
          if self.op.command == constants.OOB_POWER_ON:
164
            node.powered = True
165
          elif self.op.command == constants.OOB_POWER_OFF:
166
            node.powered = False
167
          elif self.op.command == constants.OOB_POWER_STATUS:
168
            powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
169
            if powered != node.powered:
170
              logging.warning(("Recorded power state (%s) of node '%s' does not"
171
                               " match actual power state (%s)"), node.powered,
172
                              node.name, powered)
173

    
174
          # For configuration changing commands we should update the node
175
          if self.op.command in (constants.OOB_POWER_ON,
176
                                 constants.OOB_POWER_OFF):
177
            self.cfg.Update(node, feedback_fn)
178

    
179
          node_entry.append((constants.RS_NORMAL, result.payload))
180

    
181
          if (self.op.command == constants.OOB_POWER_ON and
182
              idx < len(self.nodes) - 1):
183
            time.sleep(self.op.power_delay)
184

    
185
    return ret
186

    
187
  def _CheckPayload(self, result):
188
    """Checks if the payload is valid.
189

190
    @param result: RPC result
191
    @raises errors.OpExecError: If payload is not valid
192

193
    """
194
    errs = []
195
    if self.op.command == constants.OOB_HEALTH:
196
      if not isinstance(result.payload, list):
197
        errs.append("command 'health' is expected to return a list but got %s" %
198
                    type(result.payload))
199
      else:
200
        for item, status in result.payload:
201
          if status not in constants.OOB_STATUSES:
202
            errs.append("health item '%s' has invalid status '%s'" %
203
                        (item, status))
204

    
205
    if self.op.command == constants.OOB_POWER_STATUS:
206
      if not isinstance(result.payload, dict):
207
        errs.append("power-status is expected to return a dict but got %s" %
208
                    type(result.payload))
209

    
210
    if self.op.command in [
211
      constants.OOB_POWER_ON,
212
      constants.OOB_POWER_OFF,
213
      constants.OOB_POWER_CYCLE,
214
      ]:
215
      if result.payload is not None:
216
        errs.append("%s is expected to not return payload but got '%s'" %
217
                    (self.op.command, result.payload))
218

    
219
    if errs:
220
      raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
221
                               utils.CommaJoin(errs))
222

    
223

    
224
class ExtStorageQuery(QueryBase):
225
  FIELDS = query.EXTSTORAGE_FIELDS
226

    
227
  def ExpandNames(self, lu):
228
    # Lock all nodes in shared mode
229
    # Temporary removal of locks, should be reverted later
230
    # TODO: reintroduce locks when they are lighter-weight
231
    lu.needed_locks = {}
232
    #self.share_locks[locking.LEVEL_NODE] = 1
233
    #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
234

    
235
    # The following variables interact with _QueryBase._GetNames
236
    if self.names:
237
      self.wanted = self.names
238
    else:
239
      self.wanted = locking.ALL_SET
240

    
241
    self.do_locking = self.use_locking
242

    
243
  def DeclareLocks(self, lu, level):
244
    pass
245

    
246
  @staticmethod
247
  def _DiagnoseByProvider(rlist):
248
    """Remaps a per-node return list into an a per-provider per-node dictionary
249

250
    @param rlist: a map with node names as keys and ExtStorage objects as values
251

252
    @rtype: dict
253
    @return: a dictionary with extstorage providers as keys and as
254
        value another map, with nodes as keys and tuples of
255
        (path, status, diagnose, parameters) as values, eg::
256

257
          {"provider1": {"node1": [(/usr/lib/..., True, "", [])]
258
                         "node2": [(/srv/..., False, "missing file")]
259
                         "node3": [(/srv/..., True, "", [])]
260
          }
261

262
    """
263
    all_es = {}
264
    # we build here the list of nodes that didn't fail the RPC (at RPC
265
    # level), so that nodes with a non-responding node daemon don't
266
    # make all OSes invalid
267
    good_nodes = [node_name for node_name in rlist
268
                  if not rlist[node_name].fail_msg]
269
    for node_name, nr in rlist.items():
270
      if nr.fail_msg or not nr.payload:
271
        continue
272
      for (name, path, status, diagnose, params) in nr.payload:
273
        if name not in all_es:
274
          # build a list of nodes for this os containing empty lists
275
          # for each node in node_list
276
          all_es[name] = {}
277
          for nname in good_nodes:
278
            all_es[name][nname] = []
279
        # convert params from [name, help] to (name, help)
280
        params = [tuple(v) for v in params]
281
        all_es[name][node_name].append((path, status, diagnose, params))
282
    return all_es
283

    
284
  def _GetQueryData(self, lu):
285
    """Computes the list of nodes and their attributes.
286

287
    """
288
    # Locking is not used
289
    assert not (compat.any(lu.glm.is_owned(level)
290
                           for level in locking.LEVELS
291
                           if level != locking.LEVEL_CLUSTER) or
292
                self.do_locking or self.use_locking)
293

    
294
    valid_nodes = [node.name
295
                   for node in lu.cfg.GetAllNodesInfo().values()
296
                   if not node.offline and node.vm_capable]
297
    pol = self._DiagnoseByProvider(lu.rpc.call_extstorage_diagnose(valid_nodes))
298

    
299
    data = {}
300

    
301
    nodegroup_list = lu.cfg.GetNodeGroupList()
302

    
303
    for (es_name, es_data) in pol.items():
304
      # For every provider compute the nodegroup validity.
305
      # To do this we need to check the validity of each node in es_data
306
      # and then construct the corresponding nodegroup dict:
307
      #      { nodegroup1: status
308
      #        nodegroup2: status
309
      #      }
310
      ndgrp_data = {}
311
      for nodegroup in nodegroup_list:
312
        ndgrp = lu.cfg.GetNodeGroup(nodegroup)
313

    
314
        nodegroup_nodes = ndgrp.members
315
        nodegroup_name = ndgrp.name
316
        node_statuses = []
317

    
318
        for node in nodegroup_nodes:
319
          if node in valid_nodes:
320
            if es_data[node] != []:
321
              node_status = es_data[node][0][1]
322
              node_statuses.append(node_status)
323
            else:
324
              node_statuses.append(False)
325

    
326
        if False in node_statuses:
327
          ndgrp_data[nodegroup_name] = False
328
        else:
329
          ndgrp_data[nodegroup_name] = True
330

    
331
      # Compute the provider's parameters
332
      parameters = set()
333
      for idx, esl in enumerate(es_data.values()):
334
        valid = bool(esl and esl[0][1])
335
        if not valid:
336
          break
337

    
338
        node_params = esl[0][3]
339
        if idx == 0:
340
          # First entry
341
          parameters.update(node_params)
342
        else:
343
          # Filter out inconsistent values
344
          parameters.intersection_update(node_params)
345

    
346
      params = list(parameters)
347

    
348
      # Now fill all the info for this provider
349
      info = query.ExtStorageInfo(name=es_name, node_status=es_data,
350
                                  nodegroup_status=ndgrp_data,
351
                                  parameters=params)
352

    
353
      data[es_name] = info
354

    
355
    # Prepare data in requested order
356
    return [data[name] for name in self._GetNames(lu, pol.keys(), None)
357
            if name in data]
358

    
359

    
360
class LUExtStorageDiagnose(NoHooksLU):
361
  """Logical unit for ExtStorage diagnose/query.
362

363
  """
364
  REQ_BGL = False
365

    
366
  def CheckArguments(self):
367
    self.eq = ExtStorageQuery(qlang.MakeSimpleFilter("name", self.op.names),
368
                               self.op.output_fields, False)
369

    
370
  def ExpandNames(self):
371
    self.eq.ExpandNames(self)
372

    
373
  def Exec(self, feedback_fn):
374
    return self.eq.OldStyleQuery(self)
375

    
376

    
377
class LURestrictedCommand(NoHooksLU):
378
  """Logical unit for executing restricted commands.
379

380
  """
381
  REQ_BGL = False
382

    
383
  def ExpandNames(self):
384
    if self.op.nodes:
385
      self.op.nodes = GetWantedNodes(self, self.op.nodes)
386

    
387
    self.needed_locks = {
388
      locking.LEVEL_NODE: self.op.nodes,
389
      }
390
    self.share_locks = {
391
      locking.LEVEL_NODE: not self.op.use_locking,
392
      }
393

    
394
  def CheckPrereq(self):
395
    """Check prerequisites.
396

397
    """
398

    
399
  def Exec(self, feedback_fn):
400
    """Execute restricted command and return output.
401

402
    """
403
    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
404

    
405
    # Check if correct locks are held
406
    assert set(self.op.nodes).issubset(owned_nodes)
407

    
408
    rpcres = self.rpc.call_restricted_command(self.op.nodes, self.op.command)
409

    
410
    result = []
411

    
412
    for node_name in self.op.nodes:
413
      nres = rpcres[node_name]
414
      if nres.fail_msg:
415
        msg = ("Command '%s' on node '%s' failed: %s" %
416
               (self.op.command, node_name, nres.fail_msg))
417
        result.append((False, msg))
418
      else:
419
        result.append((True, nres.payload))
420

    
421
    return result