Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / misc.py @ 87ed6b79

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 constants
28
from ganeti import errors
29
from ganeti import locking
30
from ganeti import qlang
31
from ganeti import query
32
from ganeti import utils
33
from ganeti.cmdlib.base import NoHooksLU, QueryBase
34
from ganeti.cmdlib.common import GetWantedNodes, SupportsOob
35

    
36

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

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

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

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

    
55
    self.needed_locks = {
56
      locking.LEVEL_NODE: lock_node_uuids,
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_uuid = self.cfg.GetMasterNode()
77
    master_node_obj = self.cfg.GetNodeInfo(self.master_node_uuid)
78

    
79
    assert self.op.power_delay >= 0.0
80

    
81
    if self.op.node_uuids:
82
      if (self.op.command in self._SKIP_MASTER and
83
          master_node_obj.uuid in self.op.node_uuids):
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
                                                      master_node_obj.name)
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
                                   (master_node_obj.name, self.op.command,
97
                                    additional_text), errors.ECODE_INVAL)
98
    else:
99
      self.op.node_uuids = self.cfg.GetNodeList()
100
      if self.op.command in self._SKIP_MASTER:
101
        self.op.node_uuids.remove(master_node_obj.uuid)
102

    
103
    if self.op.command in self._SKIP_MASTER:
104
      assert master_node_obj.uuid not in self.op.node_uuids
105

    
106
    for node_uuid in self.op.node_uuids:
107
      node = self.cfg.GetNodeInfo(node_uuid)
108
      if node is None:
109
        raise errors.OpPrereqError("Node %s not found" % node_uuid,
110
                                   errors.ECODE_NOENT)
111

    
112
      self.nodes.append(node)
113

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

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

123
    """
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(self.master_node_uuid, 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 = [lu.cfg.GetNodeInfoByName(name).uuid for name in 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 uuids 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 node uuids as keys and tuples of
255
        (path, status, diagnose, parameters) as values, eg::
256

257
          {"provider1": {"node_uuid1": [(/usr/lib/..., True, "", [])]
258
                         "node_uuid2": [(/srv/..., False, "missing file")]
259
                         "node_uuid3": [(/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_uuid for node_uuid in rlist
268
                  if not rlist[node_uuid].fail_msg]
269
    for node_uuid, 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 nuuid in good_nodes:
278
            all_es[name][nuuid] = []
279
        # convert params from [name, help] to (name, help)
280
        params = [tuple(v) for v in params]
281
        all_es[name][node_uuid].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
    valid_nodes = [node.uuid
289
                   for node in lu.cfg.GetAllNodesInfo().values()
290
                   if not node.offline and node.vm_capable]
291
    pol = self._DiagnoseByProvider(lu.rpc.call_extstorage_diagnose(valid_nodes))
292

    
293
    data = {}
294

    
295
    nodegroup_list = lu.cfg.GetNodeGroupList()
296

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

    
308
        nodegroup_nodes = ndgrp.members
309
        nodegroup_name = ndgrp.name
310
        node_statuses = []
311

    
312
        for node in nodegroup_nodes:
313
          if node in valid_nodes:
314
            if es_data[node] != []:
315
              node_status = es_data[node][0][1]
316
              node_statuses.append(node_status)
317
            else:
318
              node_statuses.append(False)
319

    
320
        if False in node_statuses:
321
          ndgrp_data[nodegroup_name] = False
322
        else:
323
          ndgrp_data[nodegroup_name] = True
324

    
325
      # Compute the provider's parameters
326
      parameters = set()
327
      for idx, esl in enumerate(es_data.values()):
328
        valid = bool(esl and esl[0][1])
329
        if not valid:
330
          break
331

    
332
        node_params = esl[0][3]
333
        if idx == 0:
334
          # First entry
335
          parameters.update(node_params)
336
        else:
337
          # Filter out inconsistent values
338
          parameters.intersection_update(node_params)
339

    
340
      params = list(parameters)
341

    
342
      # Now fill all the info for this provider
343
      info = query.ExtStorageInfo(name=es_name, node_status=es_data,
344
                                  nodegroup_status=ndgrp_data,
345
                                  parameters=params)
346

    
347
      data[es_name] = info
348

    
349
    # Prepare data in requested order
350
    return [data[name] for name in self._GetNames(lu, pol.keys(), None)
351
            if name in data]
352

    
353

    
354
class LUExtStorageDiagnose(NoHooksLU):
355
  """Logical unit for ExtStorage diagnose/query.
356

357
  """
358
  REQ_BGL = False
359

    
360
  def CheckArguments(self):
361
    self.eq = ExtStorageQuery(qlang.MakeSimpleFilter("name", self.op.names),
362
                               self.op.output_fields, False)
363

    
364
  def ExpandNames(self):
365
    self.eq.ExpandNames(self)
366

    
367
  def Exec(self, feedback_fn):
368
    return self.eq.OldStyleQuery(self)
369

    
370

    
371
class LURestrictedCommand(NoHooksLU):
372
  """Logical unit for executing restricted commands.
373

374
  """
375
  REQ_BGL = False
376

    
377
  def ExpandNames(self):
378
    if self.op.nodes:
379
      (self.op.node_uuids, self.op.nodes) = GetWantedNodes(self, self.op.nodes)
380

    
381
    self.needed_locks = {
382
      locking.LEVEL_NODE: self.op.node_uuids,
383
      }
384
    self.share_locks = {
385
      locking.LEVEL_NODE: not self.op.use_locking,
386
      }
387

    
388
  def CheckPrereq(self):
389
    """Check prerequisites.
390

391
    """
392

    
393
  def Exec(self, feedback_fn):
394
    """Execute restricted command and return output.
395

396
    """
397
    owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
398

    
399
    # Check if correct locks are held
400
    assert set(self.op.node_uuids).issubset(owned_nodes)
401

    
402
    rpcres = self.rpc.call_restricted_command(self.op.node_uuids,
403
                                              self.op.command)
404

    
405
    result = []
406

    
407
    for node_uuid in self.op.node_uuids:
408
      nres = rpcres[node_uuid]
409
      if nres.fail_msg:
410
        msg = ("Command '%s' on node '%s' failed: %s" %
411
               (self.op.command, self.cfg.GetNodeName(node_uuid),
412
                nres.fail_msg))
413
        result.append((False, msg))
414
      else:
415
        result.append((True, nres.payload))
416

    
417
    return result