Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 26d3fd2f

History | View | Annotate | Download (53.8 kB)

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

24 319856a9 Michael Hanselmann
This module provides the interface to the Ganeti cluster configuration.
25 a8083063 Iustin Pop

26 319856a9 Michael Hanselmann
The configuration data is stored on every node but is updated on the master
27 319856a9 Michael Hanselmann
only. After each update, the master distributes the data to the other nodes.
28 a8083063 Iustin Pop

29 319856a9 Michael Hanselmann
Currently, the data storage format is JSON. YAML was slow and consuming too
30 319856a9 Michael Hanselmann
much memory.
31 a8083063 Iustin Pop

32 a8083063 Iustin Pop
"""
33 a8083063 Iustin Pop
34 d367b66c Manuel Franceschini
# pylint: disable-msg=R0904
35 d367b66c Manuel Franceschini
# R0904: Too many public methods
36 d367b66c Manuel Franceschini
37 a8083063 Iustin Pop
import os
38 a8083063 Iustin Pop
import random
39 d8470559 Michael Hanselmann
import logging
40 d693c864 Iustin Pop
import time
41 a8083063 Iustin Pop
42 a8083063 Iustin Pop
from ganeti import errors
43 f78ede4e Guido Trotter
from ganeti import locking
44 a8083063 Iustin Pop
from ganeti import utils
45 a8083063 Iustin Pop
from ganeti import constants
46 a8083063 Iustin Pop
from ganeti import rpc
47 a8083063 Iustin Pop
from ganeti import objects
48 8d14b30d Iustin Pop
from ganeti import serializer
49 0fbae49a Balazs Lecz
from ganeti import uidpool
50 a744b676 Manuel Franceschini
from ganeti import netutils
51 e60c73a1 René Nussbaumer
from ganeti import runtime
52 243cdbcc Michael Hanselmann
53 243cdbcc Michael Hanselmann
54 7f93570a Iustin Pop
_config_lock = locking.SharedLock("ConfigWriter")
55 f78ede4e Guido Trotter
56 4fae38c5 Guido Trotter
# job id used for resource management at config upgrade time
57 8d9c3bef Michael Hanselmann
_UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
58 4fae38c5 Guido Trotter
59 f78ede4e Guido Trotter
60 5b263ed7 Michael Hanselmann
def _ValidateConfig(data):
61 c41eea6e Iustin Pop
  """Verifies that a configuration objects looks valid.
62 c41eea6e Iustin Pop

63 c41eea6e Iustin Pop
  This only verifies the version of the configuration.
64 c41eea6e Iustin Pop

65 c41eea6e Iustin Pop
  @raise errors.ConfigurationError: if the version differs from what
66 c41eea6e Iustin Pop
      we expect
67 c41eea6e Iustin Pop

68 c41eea6e Iustin Pop
  """
69 5b263ed7 Michael Hanselmann
  if data.version != constants.CONFIG_VERSION:
70 243cdbcc Michael Hanselmann
    raise errors.ConfigurationError("Cluster configuration version"
71 243cdbcc Michael Hanselmann
                                    " mismatch, got %s instead of %s" %
72 5b263ed7 Michael Hanselmann
                                    (data.version,
73 243cdbcc Michael Hanselmann
                                     constants.CONFIG_VERSION))
74 a8083063 Iustin Pop
75 319856a9 Michael Hanselmann
76 013da361 Guido Trotter
class TemporaryReservationManager:
77 013da361 Guido Trotter
  """A temporary resource reservation manager.
78 013da361 Guido Trotter

79 013da361 Guido Trotter
  This is used to reserve resources in a job, before using them, making sure
80 013da361 Guido Trotter
  other jobs cannot get them in the meantime.
81 013da361 Guido Trotter

82 013da361 Guido Trotter
  """
83 013da361 Guido Trotter
  def __init__(self):
84 013da361 Guido Trotter
    self._ec_reserved = {}
85 013da361 Guido Trotter
86 013da361 Guido Trotter
  def Reserved(self, resource):
87 013da361 Guido Trotter
    for holder_reserved in self._ec_reserved.items():
88 013da361 Guido Trotter
      if resource in holder_reserved:
89 013da361 Guido Trotter
        return True
90 013da361 Guido Trotter
    return False
91 013da361 Guido Trotter
92 013da361 Guido Trotter
  def Reserve(self, ec_id, resource):
93 013da361 Guido Trotter
    if self.Reserved(resource):
94 013da361 Guido Trotter
      raise errors.ReservationError("Duplicate reservation for resource: %s." %
95 013da361 Guido Trotter
                                    (resource))
96 013da361 Guido Trotter
    if ec_id not in self._ec_reserved:
97 013da361 Guido Trotter
      self._ec_reserved[ec_id] = set([resource])
98 013da361 Guido Trotter
    else:
99 013da361 Guido Trotter
      self._ec_reserved[ec_id].add(resource)
100 013da361 Guido Trotter
101 013da361 Guido Trotter
  def DropECReservations(self, ec_id):
102 013da361 Guido Trotter
    if ec_id in self._ec_reserved:
103 013da361 Guido Trotter
      del self._ec_reserved[ec_id]
104 013da361 Guido Trotter
105 013da361 Guido Trotter
  def GetReserved(self):
106 013da361 Guido Trotter
    all_reserved = set()
107 013da361 Guido Trotter
    for holder_reserved in self._ec_reserved.values():
108 013da361 Guido Trotter
      all_reserved.update(holder_reserved)
109 013da361 Guido Trotter
    return all_reserved
110 013da361 Guido Trotter
111 013da361 Guido Trotter
  def Generate(self, existing, generate_one_fn, ec_id):
112 013da361 Guido Trotter
    """Generate a new resource of this type
113 013da361 Guido Trotter

114 013da361 Guido Trotter
    """
115 013da361 Guido Trotter
    assert callable(generate_one_fn)
116 013da361 Guido Trotter
117 013da361 Guido Trotter
    all_elems = self.GetReserved()
118 013da361 Guido Trotter
    all_elems.update(existing)
119 013da361 Guido Trotter
    retries = 64
120 013da361 Guido Trotter
    while retries > 0:
121 013da361 Guido Trotter
      new_resource = generate_one_fn()
122 013da361 Guido Trotter
      if new_resource is not None and new_resource not in all_elems:
123 013da361 Guido Trotter
        break
124 013da361 Guido Trotter
    else:
125 013da361 Guido Trotter
      raise errors.ConfigurationError("Not able generate new resource"
126 013da361 Guido Trotter
                                      " (last tried: %s)" % new_resource)
127 013da361 Guido Trotter
    self.Reserve(ec_id, new_resource)
128 013da361 Guido Trotter
    return new_resource
129 013da361 Guido Trotter
130 013da361 Guido Trotter
131 a8083063 Iustin Pop
class ConfigWriter:
132 098c0958 Michael Hanselmann
  """The interface to the cluster configuration.
133 a8083063 Iustin Pop

134 d8aee57e Iustin Pop
  @ivar _temporary_lvs: reservation manager for temporary LVs
135 d8aee57e Iustin Pop
  @ivar _all_rms: a list of all temporary reservation managers
136 d8aee57e Iustin Pop

137 098c0958 Michael Hanselmann
  """
138 e60c73a1 René Nussbaumer
  def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts):
139 14e15659 Iustin Pop
    self.write_count = 0
140 f78ede4e Guido Trotter
    self._lock = _config_lock
141 a8083063 Iustin Pop
    self._config_data = None
142 a8083063 Iustin Pop
    self._offline = offline
143 a8083063 Iustin Pop
    if cfg_file is None:
144 a8083063 Iustin Pop
      self._cfg_file = constants.CLUSTER_CONF_FILE
145 a8083063 Iustin Pop
    else:
146 a8083063 Iustin Pop
      self._cfg_file = cfg_file
147 e60c73a1 René Nussbaumer
    self._getents = _getents
148 4fae38c5 Guido Trotter
    self._temporary_ids = TemporaryReservationManager()
149 a81c53c9 Iustin Pop
    self._temporary_drbds = {}
150 36b66e6e Guido Trotter
    self._temporary_macs = TemporaryReservationManager()
151 afa1386e Guido Trotter
    self._temporary_secrets = TemporaryReservationManager()
152 d8aee57e Iustin Pop
    self._temporary_lvs = TemporaryReservationManager()
153 d8aee57e Iustin Pop
    self._all_rms = [self._temporary_ids, self._temporary_macs,
154 d8aee57e Iustin Pop
                     self._temporary_secrets, self._temporary_lvs]
155 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
156 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
157 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
158 89e1fc26 Iustin Pop
    # file than after it was modified
159 b705c7a6 Manuel Franceschini
    self._my_hostname = netutils.Hostname.GetSysName()
160 3c7f6c44 Iustin Pop
    self._last_cluster_serial = -1
161 3d3a04bc Iustin Pop
    self._OpenConfig()
162 a8083063 Iustin Pop
163 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
164 a8083063 Iustin Pop
  @staticmethod
165 a8083063 Iustin Pop
  def IsCluster():
166 a8083063 Iustin Pop
    """Check if the cluster is configured.
167 a8083063 Iustin Pop

168 a8083063 Iustin Pop
    """
169 a8083063 Iustin Pop
    return os.path.exists(constants.CLUSTER_CONF_FILE)
170 a8083063 Iustin Pop
171 36b66e6e Guido Trotter
  def _GenerateOneMAC(self):
172 36b66e6e Guido Trotter
    """Generate one mac address
173 36b66e6e Guido Trotter

174 36b66e6e Guido Trotter
    """
175 36b66e6e Guido Trotter
    prefix = self._config_data.cluster.mac_prefix
176 36b66e6e Guido Trotter
    byte1 = random.randrange(0, 256)
177 36b66e6e Guido Trotter
    byte2 = random.randrange(0, 256)
178 36b66e6e Guido Trotter
    byte3 = random.randrange(0, 256)
179 36b66e6e Guido Trotter
    mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
180 36b66e6e Guido Trotter
    return mac
181 36b66e6e Guido Trotter
182 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
183 36b66e6e Guido Trotter
  def GenerateMAC(self, ec_id):
184 a8083063 Iustin Pop
    """Generate a MAC for an instance.
185 a8083063 Iustin Pop

186 a8083063 Iustin Pop
    This should check the current instances for duplicates.
187 a8083063 Iustin Pop

188 a8083063 Iustin Pop
    """
189 36b66e6e Guido Trotter
    existing = self._AllMACs()
190 36b66e6e Guido Trotter
    return self._temporary_ids.Generate(existing, self._GenerateOneMAC, ec_id)
191 a8083063 Iustin Pop
192 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
193 36b66e6e Guido Trotter
  def ReserveMAC(self, mac, ec_id):
194 36b66e6e Guido Trotter
    """Reserve a MAC for an instance.
195 1862d460 Alexander Schreiber

196 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
197 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
198 1862d460 Alexander Schreiber

199 1862d460 Alexander Schreiber
    """
200 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
201 36b66e6e Guido Trotter
    if mac in all_macs:
202 36b66e6e Guido Trotter
      raise errors.ReservationError("mac already in use")
203 36b66e6e Guido Trotter
    else:
204 36b66e6e Guido Trotter
      self._temporary_macs.Reserve(mac, ec_id)
205 1862d460 Alexander Schreiber
206 f9518d38 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
207 d8aee57e Iustin Pop
  def ReserveLV(self, lv_name, ec_id):
208 d8aee57e Iustin Pop
    """Reserve an VG/LV pair for an instance.
209 d8aee57e Iustin Pop

210 d8aee57e Iustin Pop
    @type lv_name: string
211 d8aee57e Iustin Pop
    @param lv_name: the logical volume name to reserve
212 d8aee57e Iustin Pop

213 d8aee57e Iustin Pop
    """
214 d8aee57e Iustin Pop
    all_lvs = self._AllLVs()
215 d8aee57e Iustin Pop
    if lv_name in all_lvs:
216 d8aee57e Iustin Pop
      raise errors.ReservationError("LV already in use")
217 d8aee57e Iustin Pop
    else:
218 d8aee57e Iustin Pop
      self._temporary_lvs.Reserve(lv_name, ec_id)
219 d8aee57e Iustin Pop
220 d8aee57e Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
221 afa1386e Guido Trotter
  def GenerateDRBDSecret(self, ec_id):
222 f9518d38 Iustin Pop
    """Generate a DRBD secret.
223 f9518d38 Iustin Pop

224 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
225 f9518d38 Iustin Pop

226 f9518d38 Iustin Pop
    """
227 afa1386e Guido Trotter
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
228 afa1386e Guido Trotter
                                            utils.GenerateSecret,
229 afa1386e Guido Trotter
                                            ec_id)
230 8d9c3bef Michael Hanselmann
231 34e54ebc Iustin Pop
  def _AllLVs(self):
232 923b1523 Iustin Pop
    """Compute the list of all LVs.
233 923b1523 Iustin Pop

234 923b1523 Iustin Pop
    """
235 923b1523 Iustin Pop
    lvnames = set()
236 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
237 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
238 923b1523 Iustin Pop
      for lv_list in node_data.values():
239 923b1523 Iustin Pop
        lvnames.update(lv_list)
240 923b1523 Iustin Pop
    return lvnames
241 923b1523 Iustin Pop
242 34e54ebc Iustin Pop
  def _AllIDs(self, include_temporary):
243 34e54ebc Iustin Pop
    """Compute the list of all UUIDs and names we have.
244 34e54ebc Iustin Pop

245 34e54ebc Iustin Pop
    @type include_temporary: boolean
246 34e54ebc Iustin Pop
    @param include_temporary: whether to include the _temporary_ids set
247 34e54ebc Iustin Pop
    @rtype: set
248 34e54ebc Iustin Pop
    @return: a set of IDs
249 34e54ebc Iustin Pop

250 34e54ebc Iustin Pop
    """
251 34e54ebc Iustin Pop
    existing = set()
252 34e54ebc Iustin Pop
    if include_temporary:
253 4fae38c5 Guido Trotter
      existing.update(self._temporary_ids.GetReserved())
254 34e54ebc Iustin Pop
    existing.update(self._AllLVs())
255 34e54ebc Iustin Pop
    existing.update(self._config_data.instances.keys())
256 34e54ebc Iustin Pop
    existing.update(self._config_data.nodes.keys())
257 76d5d3a3 Iustin Pop
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
258 34e54ebc Iustin Pop
    return existing
259 34e54ebc Iustin Pop
260 4fae38c5 Guido Trotter
  def _GenerateUniqueID(self, ec_id):
261 430b923c Iustin Pop
    """Generate an unique UUID.
262 923b1523 Iustin Pop

263 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
264 923b1523 Iustin Pop
    duplicates.
265 923b1523 Iustin Pop

266 c41eea6e Iustin Pop
    @rtype: string
267 c41eea6e Iustin Pop
    @return: the unique id
268 923b1523 Iustin Pop

269 923b1523 Iustin Pop
    """
270 4fae38c5 Guido Trotter
    existing = self._AllIDs(include_temporary=False)
271 4fae38c5 Guido Trotter
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
272 923b1523 Iustin Pop
273 430b923c Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
274 4fae38c5 Guido Trotter
  def GenerateUniqueID(self, ec_id):
275 430b923c Iustin Pop
    """Generate an unique ID.
276 430b923c Iustin Pop

277 430b923c Iustin Pop
    This is just a wrapper over the unlocked version.
278 430b923c Iustin Pop

279 4fae38c5 Guido Trotter
    @type ec_id: string
280 4fae38c5 Guido Trotter
    @param ec_id: unique id for the job to reserve the id to
281 34d657ba Iustin Pop

282 34d657ba Iustin Pop
    """
283 4fae38c5 Guido Trotter
    return self._GenerateUniqueID(ec_id)
284 34d657ba Iustin Pop
285 a8083063 Iustin Pop
  def _AllMACs(self):
286 a8083063 Iustin Pop
    """Return all MACs present in the config.
287 a8083063 Iustin Pop

288 c41eea6e Iustin Pop
    @rtype: list
289 c41eea6e Iustin Pop
    @return: the list of all MACs
290 c41eea6e Iustin Pop

291 a8083063 Iustin Pop
    """
292 a8083063 Iustin Pop
    result = []
293 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
294 a8083063 Iustin Pop
      for nic in instance.nics:
295 a8083063 Iustin Pop
        result.append(nic.mac)
296 a8083063 Iustin Pop
297 a8083063 Iustin Pop
    return result
298 a8083063 Iustin Pop
299 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
300 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
301 f9518d38 Iustin Pop

302 c41eea6e Iustin Pop
    @rtype: list
303 c41eea6e Iustin Pop
    @return: the list of all DRBD secrets
304 c41eea6e Iustin Pop

305 f9518d38 Iustin Pop
    """
306 f9518d38 Iustin Pop
    def helper(disk, result):
307 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
308 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
309 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
310 f9518d38 Iustin Pop
      if disk.children:
311 f9518d38 Iustin Pop
        for child in disk.children:
312 f9518d38 Iustin Pop
          helper(child, result)
313 f9518d38 Iustin Pop
314 f9518d38 Iustin Pop
    result = []
315 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
316 f9518d38 Iustin Pop
      for disk in instance.disks:
317 f9518d38 Iustin Pop
        helper(disk, result)
318 f9518d38 Iustin Pop
319 f9518d38 Iustin Pop
    return result
320 f9518d38 Iustin Pop
321 4b98ac29 Iustin Pop
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
322 4b98ac29 Iustin Pop
    """Compute duplicate disk IDs
323 4b98ac29 Iustin Pop

324 4b98ac29 Iustin Pop
    @type disk: L{objects.Disk}
325 4b98ac29 Iustin Pop
    @param disk: the disk at which to start searching
326 4b98ac29 Iustin Pop
    @type l_ids: list
327 4b98ac29 Iustin Pop
    @param l_ids: list of current logical ids
328 4b98ac29 Iustin Pop
    @type p_ids: list
329 4b98ac29 Iustin Pop
    @param p_ids: list of current physical ids
330 4b98ac29 Iustin Pop
    @rtype: list
331 4b98ac29 Iustin Pop
    @return: a list of error messages
332 4b98ac29 Iustin Pop

333 4b98ac29 Iustin Pop
    """
334 4b98ac29 Iustin Pop
    result = []
335 25ae22e4 Iustin Pop
    if disk.logical_id is not None:
336 25ae22e4 Iustin Pop
      if disk.logical_id in l_ids:
337 25ae22e4 Iustin Pop
        result.append("duplicate logical id %s" % str(disk.logical_id))
338 25ae22e4 Iustin Pop
      else:
339 25ae22e4 Iustin Pop
        l_ids.append(disk.logical_id)
340 25ae22e4 Iustin Pop
    if disk.physical_id is not None:
341 25ae22e4 Iustin Pop
      if disk.physical_id in p_ids:
342 25ae22e4 Iustin Pop
        result.append("duplicate physical id %s" % str(disk.physical_id))
343 25ae22e4 Iustin Pop
      else:
344 25ae22e4 Iustin Pop
        p_ids.append(disk.physical_id)
345 4b98ac29 Iustin Pop
346 4b98ac29 Iustin Pop
    if disk.children:
347 4b98ac29 Iustin Pop
      for child in disk.children:
348 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
349 4b98ac29 Iustin Pop
    return result
350 4b98ac29 Iustin Pop
351 4a89c54a Iustin Pop
  def _UnlockedVerifyConfig(self):
352 a8efbb40 Iustin Pop
    """Verify function.
353 a8efbb40 Iustin Pop

354 4a89c54a Iustin Pop
    @rtype: list
355 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
356 4a89c54a Iustin Pop
        configuration errors
357 4a89c54a Iustin Pop

358 a8083063 Iustin Pop
    """
359 a8083063 Iustin Pop
    result = []
360 a8083063 Iustin Pop
    seen_macs = []
361 48ce9fd9 Iustin Pop
    ports = {}
362 a8083063 Iustin Pop
    data = self._config_data
363 4b98ac29 Iustin Pop
    seen_lids = []
364 4b98ac29 Iustin Pop
    seen_pids = []
365 9a5fba23 Guido Trotter
366 9a5fba23 Guido Trotter
    # global cluster checks
367 9a5fba23 Guido Trotter
    if not data.cluster.enabled_hypervisors:
368 9a5fba23 Guido Trotter
      result.append("enabled hypervisors list doesn't have any entries")
369 9a5fba23 Guido Trotter
    invalid_hvs = set(data.cluster.enabled_hypervisors) - constants.HYPER_TYPES
370 9a5fba23 Guido Trotter
    if invalid_hvs:
371 9a5fba23 Guido Trotter
      result.append("enabled hypervisors contains invalid entries: %s" %
372 9a5fba23 Guido Trotter
                    invalid_hvs)
373 9f3ac970 Iustin Pop
    missing_hvp = (set(data.cluster.enabled_hypervisors) -
374 9f3ac970 Iustin Pop
                   set(data.cluster.hvparams.keys()))
375 9f3ac970 Iustin Pop
    if missing_hvp:
376 9f3ac970 Iustin Pop
      result.append("hypervisor parameters missing for the enabled"
377 9f3ac970 Iustin Pop
                    " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
378 9a5fba23 Guido Trotter
379 9a5fba23 Guido Trotter
    if data.cluster.master_node not in data.nodes:
380 9a5fba23 Guido Trotter
      result.append("cluster has invalid primary node '%s'" %
381 9a5fba23 Guido Trotter
                    data.cluster.master_node)
382 9a5fba23 Guido Trotter
383 9a5fba23 Guido Trotter
    # per-instance checks
384 a8083063 Iustin Pop
    for instance_name in data.instances:
385 a8083063 Iustin Pop
      instance = data.instances[instance_name]
386 81196341 Iustin Pop
      if instance.name != instance_name:
387 81196341 Iustin Pop
        result.append("instance '%s' is indexed by wrong name '%s'" %
388 81196341 Iustin Pop
                      (instance.name, instance_name))
389 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
390 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
391 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
392 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
393 a8083063 Iustin Pop
        if snode not in data.nodes:
394 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
395 a8083063 Iustin Pop
                        (instance_name, snode))
396 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
397 a8083063 Iustin Pop
        if nic.mac in seen_macs:
398 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
399 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
400 a8083063 Iustin Pop
        else:
401 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
402 48ce9fd9 Iustin Pop
403 48ce9fd9 Iustin Pop
      # gather the drbd ports for duplicate checks
404 48ce9fd9 Iustin Pop
      for dsk in instance.disks:
405 48ce9fd9 Iustin Pop
        if dsk.dev_type in constants.LDS_DRBD:
406 48ce9fd9 Iustin Pop
          tcp_port = dsk.logical_id[2]
407 48ce9fd9 Iustin Pop
          if tcp_port not in ports:
408 48ce9fd9 Iustin Pop
            ports[tcp_port] = []
409 48ce9fd9 Iustin Pop
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
410 48ce9fd9 Iustin Pop
      # gather network port reservation
411 48ce9fd9 Iustin Pop
      net_port = getattr(instance, "network_port", None)
412 48ce9fd9 Iustin Pop
      if net_port is not None:
413 48ce9fd9 Iustin Pop
        if net_port not in ports:
414 48ce9fd9 Iustin Pop
          ports[net_port] = []
415 48ce9fd9 Iustin Pop
        ports[net_port].append((instance.name, "network port"))
416 48ce9fd9 Iustin Pop
417 332d0e37 Iustin Pop
      # instance disk verify
418 332d0e37 Iustin Pop
      for idx, disk in enumerate(instance.disks):
419 332d0e37 Iustin Pop
        result.extend(["instance '%s' disk %d error: %s" %
420 332d0e37 Iustin Pop
                       (instance.name, idx, msg) for msg in disk.Verify()])
421 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
422 332d0e37 Iustin Pop
423 48ce9fd9 Iustin Pop
    # cluster-wide pool of free ports
424 a8efbb40 Iustin Pop
    for free_port in data.cluster.tcpudp_port_pool:
425 48ce9fd9 Iustin Pop
      if free_port not in ports:
426 48ce9fd9 Iustin Pop
        ports[free_port] = []
427 48ce9fd9 Iustin Pop
      ports[free_port].append(("cluster", "port marked as free"))
428 48ce9fd9 Iustin Pop
429 48ce9fd9 Iustin Pop
    # compute tcp/udp duplicate ports
430 48ce9fd9 Iustin Pop
    keys = ports.keys()
431 48ce9fd9 Iustin Pop
    keys.sort()
432 48ce9fd9 Iustin Pop
    for pnum in keys:
433 48ce9fd9 Iustin Pop
      pdata = ports[pnum]
434 48ce9fd9 Iustin Pop
      if len(pdata) > 1:
435 1f864b60 Iustin Pop
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
436 48ce9fd9 Iustin Pop
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
437 48ce9fd9 Iustin Pop
438 48ce9fd9 Iustin Pop
    # highest used tcp port check
439 48ce9fd9 Iustin Pop
    if keys:
440 a8efbb40 Iustin Pop
      if keys[-1] > data.cluster.highest_used_port:
441 48ce9fd9 Iustin Pop
        result.append("Highest used port mismatch, saved %s, computed %s" %
442 a8efbb40 Iustin Pop
                      (data.cluster.highest_used_port, keys[-1]))
443 a8efbb40 Iustin Pop
444 3a26773f Iustin Pop
    if not data.nodes[data.cluster.master_node].master_candidate:
445 3a26773f Iustin Pop
      result.append("Master node is not a master candidate")
446 3a26773f Iustin Pop
447 4a89c54a Iustin Pop
    # master candidate checks
448 e623dbe3 Guido Trotter
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
449 ec0292f1 Iustin Pop
    if mc_now < mc_max:
450 ec0292f1 Iustin Pop
      result.append("Not enough master candidates: actual %d, target %d" %
451 ec0292f1 Iustin Pop
                    (mc_now, mc_max))
452 48ce9fd9 Iustin Pop
453 5bf07049 Iustin Pop
    # node checks
454 81196341 Iustin Pop
    for node_name, node in data.nodes.items():
455 81196341 Iustin Pop
      if node.name != node_name:
456 81196341 Iustin Pop
        result.append("Node '%s' is indexed by wrong name '%s'" %
457 81196341 Iustin Pop
                      (node.name, node_name))
458 5bf07049 Iustin Pop
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
459 5bf07049 Iustin Pop
        result.append("Node %s state is invalid: master_candidate=%s,"
460 5bf07049 Iustin Pop
                      " drain=%s, offline=%s" %
461 5bf07049 Iustin Pop
                      (node.name, node.master_candidate, node.drain,
462 5bf07049 Iustin Pop
                       node.offline))
463 5bf07049 Iustin Pop
464 6520ba14 Guido Trotter
    # nodegroups checks
465 ace16501 Guido Trotter
    nodegroups_names = set()
466 6520ba14 Guido Trotter
    for nodegroup_uuid in data.nodegroups:
467 6520ba14 Guido Trotter
      nodegroup = data.nodegroups[nodegroup_uuid]
468 6520ba14 Guido Trotter
      if nodegroup.uuid != nodegroup_uuid:
469 6520ba14 Guido Trotter
        result.append("nodegroup '%s' (uuid: '%s') indexed by wrong uuid '%s'"
470 6520ba14 Guido Trotter
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
471 ace16501 Guido Trotter
      if nodegroup.name in nodegroups_names:
472 ace16501 Guido Trotter
        result.append("duplicate nodegroup name '%s'" % nodegroup.name)
473 ace16501 Guido Trotter
      else:
474 ace16501 Guido Trotter
        nodegroups_names.add(nodegroup.name)
475 6520ba14 Guido Trotter
476 4a89c54a Iustin Pop
    # drbd minors check
477 1122eb25 Iustin Pop
    _, duplicates = self._UnlockedComputeDRBDMap()
478 4a89c54a Iustin Pop
    for node, minor, instance_a, instance_b in duplicates:
479 4a89c54a Iustin Pop
      result.append("DRBD minor %d on node %s is assigned twice to instances"
480 4a89c54a Iustin Pop
                    " %s and %s" % (minor, node, instance_a, instance_b))
481 4a89c54a Iustin Pop
482 0ce8f948 Iustin Pop
    # IP checks
483 b8716596 Michael Hanselmann
    default_nicparams = data.cluster.nicparams[constants.PP_DEFAULT]
484 b8716596 Michael Hanselmann
    ips = {}
485 b8716596 Michael Hanselmann
486 b8716596 Michael Hanselmann
    def _AddIpAddress(ip, name):
487 b8716596 Michael Hanselmann
      ips.setdefault(ip, []).append(name)
488 b8716596 Michael Hanselmann
489 b8716596 Michael Hanselmann
    _AddIpAddress(data.cluster.master_ip, "cluster_ip")
490 0ce8f948 Iustin Pop
491 0ce8f948 Iustin Pop
    for node in data.nodes.values():
492 b8716596 Michael Hanselmann
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
493 0ce8f948 Iustin Pop
      if node.secondary_ip != node.primary_ip:
494 b8716596 Michael Hanselmann
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
495 b8716596 Michael Hanselmann
496 b8716596 Michael Hanselmann
    for instance in data.instances.values():
497 b8716596 Michael Hanselmann
      for idx, nic in enumerate(instance.nics):
498 b8716596 Michael Hanselmann
        if nic.ip is None:
499 b8716596 Michael Hanselmann
          continue
500 b8716596 Michael Hanselmann
501 b8716596 Michael Hanselmann
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
502 b8716596 Michael Hanselmann
        nic_mode = nicparams[constants.NIC_MODE]
503 b8716596 Michael Hanselmann
        nic_link = nicparams[constants.NIC_LINK]
504 b8716596 Michael Hanselmann
505 b8716596 Michael Hanselmann
        if nic_mode == constants.NIC_MODE_BRIDGED:
506 b8716596 Michael Hanselmann
          link = "bridge:%s" % nic_link
507 b8716596 Michael Hanselmann
        elif nic_mode == constants.NIC_MODE_ROUTED:
508 b8716596 Michael Hanselmann
          link = "route:%s" % nic_link
509 b8716596 Michael Hanselmann
        else:
510 b8716596 Michael Hanselmann
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
511 b8716596 Michael Hanselmann
512 b8716596 Michael Hanselmann
        _AddIpAddress("%s/%s" % (link, nic.ip),
513 b8716596 Michael Hanselmann
                      "instance:%s/nic:%d" % (instance.name, idx))
514 0ce8f948 Iustin Pop
515 0ce8f948 Iustin Pop
    for ip, owners in ips.items():
516 0ce8f948 Iustin Pop
      if len(owners) > 1:
517 0ce8f948 Iustin Pop
        result.append("IP address %s is used by multiple owners: %s" %
518 1f864b60 Iustin Pop
                      (ip, utils.CommaJoin(owners)))
519 b8716596 Michael Hanselmann
520 a8083063 Iustin Pop
    return result
521 a8083063 Iustin Pop
522 4a89c54a Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
523 4a89c54a Iustin Pop
  def VerifyConfig(self):
524 4a89c54a Iustin Pop
    """Verify function.
525 4a89c54a Iustin Pop

526 4a89c54a Iustin Pop
    This is just a wrapper over L{_UnlockedVerifyConfig}.
527 4a89c54a Iustin Pop

528 4a89c54a Iustin Pop
    @rtype: list
529 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
530 4a89c54a Iustin Pop
        configuration errors
531 4a89c54a Iustin Pop

532 4a89c54a Iustin Pop
    """
533 4a89c54a Iustin Pop
    return self._UnlockedVerifyConfig()
534 4a89c54a Iustin Pop
535 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
536 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
537 a8083063 Iustin Pop

538 a8083063 Iustin Pop
    This is used only for drbd, which needs ip/port configuration.
539 a8083063 Iustin Pop

540 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
541 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
542 a8083063 Iustin Pop
    node.
543 a8083063 Iustin Pop

544 f78ede4e Guido Trotter
    This function is for internal use, when the config lock is already held.
545 f78ede4e Guido Trotter

546 a8083063 Iustin Pop
    """
547 a8083063 Iustin Pop
    if disk.children:
548 a8083063 Iustin Pop
      for child in disk.children:
549 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
550 a8083063 Iustin Pop
551 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
552 a8083063 Iustin Pop
      return
553 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
554 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
555 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
556 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
557 3ecf6786 Iustin Pop
                                        node_name)
558 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
559 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
560 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
561 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
562 a8083063 Iustin Pop
                                        " for %s" % str(disk))
563 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
564 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
565 a8083063 Iustin Pop
      if pnode == node_name:
566 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
567 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
568 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
569 a8083063 Iustin Pop
    else:
570 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
571 a8083063 Iustin Pop
    return
572 a8083063 Iustin Pop
573 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
574 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
575 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
576 f78ede4e Guido Trotter

577 f78ede4e Guido Trotter
    This is used only for drbd, which needs ip/port configuration.
578 f78ede4e Guido Trotter

579 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
580 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
581 f78ede4e Guido Trotter
    node.
582 f78ede4e Guido Trotter

583 f78ede4e Guido Trotter
    """
584 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
585 f78ede4e Guido Trotter
586 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
587 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
588 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
589 b2fddf63 Iustin Pop

590 b2fddf63 Iustin Pop
    """
591 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
592 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
593 264bb3c5 Michael Hanselmann
594 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
595 264bb3c5 Michael Hanselmann
    self._WriteConfig()
596 264bb3c5 Michael Hanselmann
597 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
598 b2fddf63 Iustin Pop
  def GetPortList(self):
599 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
600 264bb3c5 Michael Hanselmann

601 264bb3c5 Michael Hanselmann
    """
602 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
603 264bb3c5 Michael Hanselmann
604 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
605 a8083063 Iustin Pop
  def AllocatePort(self):
606 a8083063 Iustin Pop
    """Allocate a port.
607 a8083063 Iustin Pop

608 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
609 b2fddf63 Iustin Pop
    default port range (and in this case we increase
610 b2fddf63 Iustin Pop
    highest_used_port).
611 a8083063 Iustin Pop

612 a8083063 Iustin Pop
    """
613 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
614 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
615 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
616 264bb3c5 Michael Hanselmann
    else:
617 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
618 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
619 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
620 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
621 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
622 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
623 a8083063 Iustin Pop
624 a8083063 Iustin Pop
    self._WriteConfig()
625 a8083063 Iustin Pop
    return port
626 a8083063 Iustin Pop
627 6d2e83d5 Iustin Pop
  def _UnlockedComputeDRBDMap(self):
628 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
629 a81c53c9 Iustin Pop

630 4a89c54a Iustin Pop
    @rtype: (dict, list)
631 c41eea6e Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
632 c41eea6e Iustin Pop
        the returned dict will have all the nodes in it (even if with
633 4a89c54a Iustin Pop
        an empty list), and a list of duplicates; if the duplicates
634 4a89c54a Iustin Pop
        list is not empty, the configuration is corrupted and its caller
635 4a89c54a Iustin Pop
        should raise an exception
636 a81c53c9 Iustin Pop

637 a81c53c9 Iustin Pop
    """
638 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
639 4a89c54a Iustin Pop
      duplicates = []
640 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
641 7c4d6c7b Michael Hanselmann
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
642 7c4d6c7b Michael Hanselmann
        for node, port in ((node_a, minor_a), (node_b, minor_b)):
643 4a89c54a Iustin Pop
          assert node in used, ("Node '%s' of instance '%s' not found"
644 4a89c54a Iustin Pop
                                " in node list" % (node, instance_name))
645 a81c53c9 Iustin Pop
          if port in used[node]:
646 4a89c54a Iustin Pop
            duplicates.append((node, port, instance_name, used[node][port]))
647 4a89c54a Iustin Pop
          else:
648 4a89c54a Iustin Pop
            used[node][port] = instance_name
649 a81c53c9 Iustin Pop
      if disk.children:
650 a81c53c9 Iustin Pop
        for child in disk.children:
651 4a89c54a Iustin Pop
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
652 4a89c54a Iustin Pop
      return duplicates
653 a81c53c9 Iustin Pop
654 4a89c54a Iustin Pop
    duplicates = []
655 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
656 79b26a7a Iustin Pop
    for instance in self._config_data.instances.itervalues():
657 79b26a7a Iustin Pop
      for disk in instance.disks:
658 79b26a7a Iustin Pop
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
659 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
660 79b26a7a Iustin Pop
      if minor in my_dict[node] and my_dict[node][minor] != instance:
661 4a89c54a Iustin Pop
        duplicates.append((node, minor, instance, my_dict[node][minor]))
662 4a89c54a Iustin Pop
      else:
663 4a89c54a Iustin Pop
        my_dict[node][minor] = instance
664 4a89c54a Iustin Pop
    return my_dict, duplicates
665 a81c53c9 Iustin Pop
666 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
667 6d2e83d5 Iustin Pop
  def ComputeDRBDMap(self):
668 6d2e83d5 Iustin Pop
    """Compute the used DRBD minor/nodes.
669 6d2e83d5 Iustin Pop

670 6d2e83d5 Iustin Pop
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
671 6d2e83d5 Iustin Pop

672 6d2e83d5 Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
673 6d2e83d5 Iustin Pop
        the returned dict will have all the nodes in it (even if with
674 6d2e83d5 Iustin Pop
        an empty list).
675 6d2e83d5 Iustin Pop

676 6d2e83d5 Iustin Pop
    """
677 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
678 4a89c54a Iustin Pop
    if duplicates:
679 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
680 4a89c54a Iustin Pop
                                      str(duplicates))
681 4a89c54a Iustin Pop
    return d_map
682 6d2e83d5 Iustin Pop
683 6d2e83d5 Iustin Pop
  @locking.ssynchronized(_config_lock)
684 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
685 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
686 a81c53c9 Iustin Pop

687 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
688 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
689 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
690 a81c53c9 Iustin Pop
    order as the passed nodes.
691 a81c53c9 Iustin Pop

692 32388e6d Iustin Pop
    @type instance: string
693 32388e6d Iustin Pop
    @param instance: the instance for which we allocate minors
694 32388e6d Iustin Pop

695 a81c53c9 Iustin Pop
    """
696 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
697 4a89c54a Iustin Pop
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
698 32388e6d Iustin Pop
699 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
700 4a89c54a Iustin Pop
    if duplicates:
701 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
702 4a89c54a Iustin Pop
                                      str(duplicates))
703 a81c53c9 Iustin Pop
    result = []
704 a81c53c9 Iustin Pop
    for nname in nodes:
705 a81c53c9 Iustin Pop
      ndata = d_map[nname]
706 a81c53c9 Iustin Pop
      if not ndata:
707 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
708 a81c53c9 Iustin Pop
        result.append(0)
709 a81c53c9 Iustin Pop
        ndata[0] = instance
710 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
711 a81c53c9 Iustin Pop
        continue
712 a81c53c9 Iustin Pop
      keys = ndata.keys()
713 a81c53c9 Iustin Pop
      keys.sort()
714 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
715 a81c53c9 Iustin Pop
      if ffree is None:
716 a81c53c9 Iustin Pop
        # return the next minor
717 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
718 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
719 a81c53c9 Iustin Pop
      else:
720 a81c53c9 Iustin Pop
        minor = ffree
721 4a89c54a Iustin Pop
      # double-check minor against current instances
722 4a89c54a Iustin Pop
      assert minor not in d_map[nname], \
723 4a89c54a Iustin Pop
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
724 4a89c54a Iustin Pop
              " already allocated to instance %s" %
725 4a89c54a Iustin Pop
              (minor, nname, d_map[nname][minor]))
726 a81c53c9 Iustin Pop
      ndata[minor] = instance
727 4a89c54a Iustin Pop
      # double-check minor against reservation
728 4a89c54a Iustin Pop
      r_key = (nname, minor)
729 4a89c54a Iustin Pop
      assert r_key not in self._temporary_drbds, \
730 4a89c54a Iustin Pop
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
731 4a89c54a Iustin Pop
              " reserved for instance %s" %
732 4a89c54a Iustin Pop
              (minor, nname, self._temporary_drbds[r_key]))
733 4a89c54a Iustin Pop
      self._temporary_drbds[r_key] = instance
734 4a89c54a Iustin Pop
      result.append(minor)
735 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
736 a81c53c9 Iustin Pop
                  nodes, result)
737 a81c53c9 Iustin Pop
    return result
738 a81c53c9 Iustin Pop
739 61cf6b5e Iustin Pop
  def _UnlockedReleaseDRBDMinors(self, instance):
740 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
741 a81c53c9 Iustin Pop

742 a81c53c9 Iustin Pop
    @type instance: string
743 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
744 a81c53c9 Iustin Pop
                     released
745 a81c53c9 Iustin Pop

746 a81c53c9 Iustin Pop
    """
747 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
748 32388e6d Iustin Pop
           "Invalid argument passed to ReleaseDRBDMinors"
749 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
750 a81c53c9 Iustin Pop
      if name == instance:
751 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
752 a81c53c9 Iustin Pop
753 61cf6b5e Iustin Pop
  @locking.ssynchronized(_config_lock)
754 61cf6b5e Iustin Pop
  def ReleaseDRBDMinors(self, instance):
755 61cf6b5e Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
756 61cf6b5e Iustin Pop

757 61cf6b5e Iustin Pop
    This should be called on the error paths, on the success paths
758 61cf6b5e Iustin Pop
    it's automatically called by the ConfigWriter add and update
759 61cf6b5e Iustin Pop
    functions.
760 61cf6b5e Iustin Pop

761 61cf6b5e Iustin Pop
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
762 61cf6b5e Iustin Pop

763 61cf6b5e Iustin Pop
    @type instance: string
764 61cf6b5e Iustin Pop
    @param instance: the instance for which temporary minors should be
765 61cf6b5e Iustin Pop
                     released
766 61cf6b5e Iustin Pop

767 61cf6b5e Iustin Pop
    """
768 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance)
769 61cf6b5e Iustin Pop
770 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
771 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
772 4a8b186a Michael Hanselmann
    """Get the configuration version.
773 4a8b186a Michael Hanselmann

774 4a8b186a Michael Hanselmann
    @return: Config version
775 4a8b186a Michael Hanselmann

776 4a8b186a Michael Hanselmann
    """
777 4a8b186a Michael Hanselmann
    return self._config_data.version
778 4a8b186a Michael Hanselmann
779 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
780 4a8b186a Michael Hanselmann
  def GetClusterName(self):
781 4a8b186a Michael Hanselmann
    """Get cluster name.
782 4a8b186a Michael Hanselmann

783 4a8b186a Michael Hanselmann
    @return: Cluster name
784 4a8b186a Michael Hanselmann

785 4a8b186a Michael Hanselmann
    """
786 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
787 4a8b186a Michael Hanselmann
788 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
789 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
790 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
791 4a8b186a Michael Hanselmann

792 4a8b186a Michael Hanselmann
    @return: Master hostname
793 4a8b186a Michael Hanselmann

794 4a8b186a Michael Hanselmann
    """
795 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
796 4a8b186a Michael Hanselmann
797 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
798 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
799 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
800 4a8b186a Michael Hanselmann

801 4a8b186a Michael Hanselmann
    @return: Master IP
802 4a8b186a Michael Hanselmann

803 4a8b186a Michael Hanselmann
    """
804 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
805 4a8b186a Michael Hanselmann
806 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
807 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
808 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
809 4a8b186a Michael Hanselmann

810 4a8b186a Michael Hanselmann
    """
811 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
812 4a8b186a Michael Hanselmann
813 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
814 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
815 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
816 4a8b186a Michael Hanselmann

817 4a8b186a Michael Hanselmann
    """
818 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
819 4a8b186a Michael Hanselmann
820 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
821 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
822 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
823 4a8b186a Michael Hanselmann

824 4a8b186a Michael Hanselmann
    """
825 066f465d Guido Trotter
    return self._config_data.cluster.enabled_hypervisors[0]
826 4a8b186a Michael Hanselmann
827 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
828 a8083063 Iustin Pop
  def GetHostKey(self):
829 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
830 a8083063 Iustin Pop

831 c41eea6e Iustin Pop
    @rtype: string
832 c41eea6e Iustin Pop
    @return: the rsa hostkey
833 a8083063 Iustin Pop

834 a8083063 Iustin Pop
    """
835 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
836 a8083063 Iustin Pop
837 bf4af505 Apollon Oikonomopoulos
  @locking.ssynchronized(_config_lock, shared=1)
838 bf4af505 Apollon Oikonomopoulos
  def GetDefaultIAllocator(self):
839 bf4af505 Apollon Oikonomopoulos
    """Get the default instance allocator for this cluster.
840 bf4af505 Apollon Oikonomopoulos

841 bf4af505 Apollon Oikonomopoulos
    """
842 bf4af505 Apollon Oikonomopoulos
    return self._config_data.cluster.default_iallocator
843 bf4af505 Apollon Oikonomopoulos
844 868a98ca Manuel Franceschini
  @locking.ssynchronized(_config_lock, shared=1)
845 868a98ca Manuel Franceschini
  def GetPrimaryIPFamily(self):
846 868a98ca Manuel Franceschini
    """Get cluster primary ip family.
847 868a98ca Manuel Franceschini

848 868a98ca Manuel Franceschini
    @return: primary ip family
849 868a98ca Manuel Franceschini

850 868a98ca Manuel Franceschini
    """
851 868a98ca Manuel Franceschini
    return self._config_data.cluster.primary_ip_family
852 868a98ca Manuel Franceschini
853 eaa98a04 Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
854 eaa98a04 Guido Trotter
  def LookupNodeGroup(self, target):
855 eaa98a04 Guido Trotter
    """Lookup a node group.
856 eaa98a04 Guido Trotter

857 eaa98a04 Guido Trotter
    @type target: string or None
858 eaa98a04 Guido Trotter
    @param  target: group name or uuid or None to look for the default
859 eaa98a04 Guido Trotter
    @rtype: string
860 eaa98a04 Guido Trotter
    @return: nodegroup uuid
861 eaa98a04 Guido Trotter
    @raises errors.OpPrereqError: when the target group cannot be found
862 eaa98a04 Guido Trotter

863 eaa98a04 Guido Trotter
    """
864 eaa98a04 Guido Trotter
    if target is None:
865 eaa98a04 Guido Trotter
      if len(self._config_data.nodegroups) != 1:
866 eaa98a04 Guido Trotter
        raise errors.OpPrereqError("More than one nodegroup exists. Target"
867 eaa98a04 Guido Trotter
                                   " group must be specified explicitely.")
868 eaa98a04 Guido Trotter
      else:
869 eaa98a04 Guido Trotter
        return self._config_data.nodegroups.keys()[0]
870 eaa98a04 Guido Trotter
    if target in self._config_data.nodegroups:
871 eaa98a04 Guido Trotter
      return target
872 eaa98a04 Guido Trotter
    for nodegroup in self._config_data.nodegroups.values():
873 eaa98a04 Guido Trotter
      if nodegroup.name == target:
874 eaa98a04 Guido Trotter
        return nodegroup.uuid
875 eaa98a04 Guido Trotter
    raise errors.OpPrereqError("Nodegroup '%s' not found", target)
876 eaa98a04 Guido Trotter
877 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
878 0debfb35 Guido Trotter
  def AddInstance(self, instance, ec_id):
879 a8083063 Iustin Pop
    """Add an instance to the config.
880 a8083063 Iustin Pop

881 a8083063 Iustin Pop
    This should be used after creating a new instance.
882 a8083063 Iustin Pop

883 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
884 c41eea6e Iustin Pop
    @param instance: the instance object
885 c41eea6e Iustin Pop

886 a8083063 Iustin Pop
    """
887 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
888 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
889 a8083063 Iustin Pop
890 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
891 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
892 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
893 923b1523 Iustin Pop
894 e4640214 Guido Trotter
    all_macs = self._AllMACs()
895 e4640214 Guido Trotter
    for nic in instance.nics:
896 e4640214 Guido Trotter
      if nic.mac in all_macs:
897 e4640214 Guido Trotter
        raise errors.ConfigurationError("Cannot add instance %s:"
898 430b923c Iustin Pop
                                        " MAC address '%s' already in use." %
899 430b923c Iustin Pop
                                        (instance.name, nic.mac))
900 430b923c Iustin Pop
901 0debfb35 Guido Trotter
    self._EnsureUUID(instance, ec_id)
902 e4640214 Guido Trotter
903 b989e85d Iustin Pop
    instance.serial_no = 1
904 d693c864 Iustin Pop
    instance.ctime = instance.mtime = time.time()
905 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
906 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
907 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance.name)
908 a8083063 Iustin Pop
    self._WriteConfig()
909 a8083063 Iustin Pop
910 0debfb35 Guido Trotter
  def _EnsureUUID(self, item, ec_id):
911 430b923c Iustin Pop
    """Ensures a given object has a valid UUID.
912 430b923c Iustin Pop

913 430b923c Iustin Pop
    @param item: the instance or node to be checked
914 0debfb35 Guido Trotter
    @param ec_id: the execution context id for the uuid reservation
915 430b923c Iustin Pop

916 430b923c Iustin Pop
    """
917 430b923c Iustin Pop
    if not item.uuid:
918 4fae38c5 Guido Trotter
      item.uuid = self._GenerateUniqueID(ec_id)
919 be0fc05d Iustin Pop
    elif item.uuid in self._AllIDs(include_temporary=True):
920 be0fc05d Iustin Pop
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
921 be0fc05d Iustin Pop
                                      " in use" % (item.name, item.uuid))
922 430b923c Iustin Pop
923 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
924 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
925 a8083063 Iustin Pop

926 a8083063 Iustin Pop
    """
927 0d68c45d Iustin Pop
    assert isinstance(status, bool), \
928 0d68c45d Iustin Pop
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
929 a8083063 Iustin Pop
930 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
931 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
932 3ecf6786 Iustin Pop
                                      instance_name)
933 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
934 0d68c45d Iustin Pop
    if instance.admin_up != status:
935 0d68c45d Iustin Pop
      instance.admin_up = status
936 b989e85d Iustin Pop
      instance.serial_no += 1
937 d693c864 Iustin Pop
      instance.mtime = time.time()
938 455a3445 Iustin Pop
      self._WriteConfig()
939 a8083063 Iustin Pop
940 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
941 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
942 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
943 6a408fb2 Iustin Pop

944 6a408fb2 Iustin Pop
    """
945 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, True)
946 6a408fb2 Iustin Pop
947 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
948 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
949 a8083063 Iustin Pop
    """Remove the instance from the configuration.
950 a8083063 Iustin Pop

951 a8083063 Iustin Pop
    """
952 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
953 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
954 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
955 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
956 a8083063 Iustin Pop
    self._WriteConfig()
957 a8083063 Iustin Pop
958 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
959 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
960 fc95f88f Iustin Pop
    """Rename an instance.
961 fc95f88f Iustin Pop

962 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
963 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
964 fc95f88f Iustin Pop
    rename.
965 fc95f88f Iustin Pop

966 fc95f88f Iustin Pop
    """
967 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
968 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
969 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
970 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
971 fc95f88f Iustin Pop
    inst.name = new_name
972 b23c4333 Manuel Franceschini
973 b23c4333 Manuel Franceschini
    for disk in inst.disks:
974 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
975 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
976 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
977 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
978 c4feafe8 Iustin Pop
                                              utils.PathJoin(file_storage_dir,
979 c4feafe8 Iustin Pop
                                                             inst.name,
980 c4feafe8 Iustin Pop
                                                             disk.iv_name))
981 b23c4333 Manuel Franceschini
982 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
983 fc95f88f Iustin Pop
    self._WriteConfig()
984 fc95f88f Iustin Pop
985 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
986 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
987 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
988 a8083063 Iustin Pop

989 a8083063 Iustin Pop
    """
990 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, False)
991 a8083063 Iustin Pop
992 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
993 94bbfece Iustin Pop
    """Get the list of instances.
994 94bbfece Iustin Pop

995 94bbfece Iustin Pop
    This function is for internal use, when the config lock is already held.
996 94bbfece Iustin Pop

997 94bbfece Iustin Pop
    """
998 94bbfece Iustin Pop
    return self._config_data.instances.keys()
999 94bbfece Iustin Pop
1000 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1001 a8083063 Iustin Pop
  def GetInstanceList(self):
1002 a8083063 Iustin Pop
    """Get the list of instances.
1003 a8083063 Iustin Pop

1004 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
1005 c41eea6e Iustin Pop
        'instance1.example.com']
1006 a8083063 Iustin Pop

1007 a8083063 Iustin Pop
    """
1008 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
1009 a8083063 Iustin Pop
1010 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1011 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
1012 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
1013 a8083063 Iustin Pop

1014 a8083063 Iustin Pop
    """
1015 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
1016 bcdf16d7 Guido Trotter
                                    self._config_data.instances.keys(),
1017 bcdf16d7 Guido Trotter
                                    case_sensitive=False)
1018 a8083063 Iustin Pop
1019 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
1020 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
1021 94bbfece Iustin Pop

1022 94bbfece Iustin Pop
    This function is for internal use, when the config lock is already held.
1023 94bbfece Iustin Pop

1024 94bbfece Iustin Pop
    """
1025 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
1026 94bbfece Iustin Pop
      return None
1027 94bbfece Iustin Pop
1028 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
1029 94bbfece Iustin Pop
1030 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1031 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
1032 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
1033 a8083063 Iustin Pop

1034 5bbd3f7f Michael Hanselmann
    It takes the information from the configuration file. Other information of
1035 a8083063 Iustin Pop
    an instance are taken from the live systems.
1036 a8083063 Iustin Pop

1037 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
1038 c41eea6e Iustin Pop
        I{instance1.example.com}
1039 a8083063 Iustin Pop

1040 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
1041 c41eea6e Iustin Pop
    @return: the instance object
1042 a8083063 Iustin Pop

1043 a8083063 Iustin Pop
    """
1044 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
1045 a8083063 Iustin Pop
1046 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1047 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
1048 0b2de758 Iustin Pop
    """Get the configuration of all instances.
1049 0b2de758 Iustin Pop

1050 0b2de758 Iustin Pop
    @rtype: dict
1051 5fcc718f Iustin Pop
    @return: dict of (instance, instance_info), where instance_info is what
1052 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
1053 0b2de758 Iustin Pop

1054 0b2de758 Iustin Pop
    """
1055 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1056 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
1057 0b2de758 Iustin Pop
    return my_dict
1058 0b2de758 Iustin Pop
1059 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1060 0debfb35 Guido Trotter
  def AddNode(self, node, ec_id):
1061 a8083063 Iustin Pop
    """Add a node to the configuration.
1062 a8083063 Iustin Pop

1063 c41eea6e Iustin Pop
    @type node: L{objects.Node}
1064 c41eea6e Iustin Pop
    @param node: a Node instance
1065 a8083063 Iustin Pop

1066 a8083063 Iustin Pop
    """
1067 099c52ad Iustin Pop
    logging.info("Adding node %s to configuration", node.name)
1068 d8470559 Michael Hanselmann
1069 0debfb35 Guido Trotter
    self._EnsureUUID(node, ec_id)
1070 430b923c Iustin Pop
1071 b989e85d Iustin Pop
    node.serial_no = 1
1072 d693c864 Iustin Pop
    node.ctime = node.mtime = time.time()
1073 190e3cb6 Guido Trotter
    self._UnlockedAddNodeToGroup(node.name, node.nodegroup)
1074 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
1075 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1076 a8083063 Iustin Pop
    self._WriteConfig()
1077 a8083063 Iustin Pop
1078 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1079 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
1080 a8083063 Iustin Pop
    """Remove a node from the configuration.
1081 a8083063 Iustin Pop

1082 a8083063 Iustin Pop
    """
1083 099c52ad Iustin Pop
    logging.info("Removing node %s from configuration", node_name)
1084 d8470559 Michael Hanselmann
1085 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
1086 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1087 a8083063 Iustin Pop
1088 190e3cb6 Guido Trotter
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1089 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
1090 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1091 a8083063 Iustin Pop
    self._WriteConfig()
1092 a8083063 Iustin Pop
1093 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1094 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
1095 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
1096 a8083063 Iustin Pop

1097 a8083063 Iustin Pop
    """
1098 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
1099 bcdf16d7 Guido Trotter
                                    self._config_data.nodes.keys(),
1100 bcdf16d7 Guido Trotter
                                    case_sensitive=False)
1101 a8083063 Iustin Pop
1102 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
1103 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
1104 a8083063 Iustin Pop

1105 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
1106 c41eea6e Iustin Pop
    held.
1107 f78ede4e Guido Trotter

1108 c41eea6e Iustin Pop
    @param node_name: the node name, e.g. I{node1.example.com}
1109 a8083063 Iustin Pop

1110 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
1111 c41eea6e Iustin Pop
    @return: the node object
1112 a8083063 Iustin Pop

1113 a8083063 Iustin Pop
    """
1114 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
1115 a8083063 Iustin Pop
      return None
1116 a8083063 Iustin Pop
1117 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
1118 a8083063 Iustin Pop
1119 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1120 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
1121 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
1122 f78ede4e Guido Trotter

1123 c41eea6e Iustin Pop
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1124 f78ede4e Guido Trotter

1125 c41eea6e Iustin Pop
    @param node_name: the node name, e.g. I{node1.example.com}
1126 c41eea6e Iustin Pop

1127 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
1128 c41eea6e Iustin Pop
    @return: the node object
1129 f78ede4e Guido Trotter

1130 f78ede4e Guido Trotter
    """
1131 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
1132 f78ede4e Guido Trotter
1133 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
1134 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
1135 a8083063 Iustin Pop

1136 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
1137 c41eea6e Iustin Pop
    held.
1138 c41eea6e Iustin Pop

1139 c41eea6e Iustin Pop
    @rtype: list
1140 f78ede4e Guido Trotter

1141 a8083063 Iustin Pop
    """
1142 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
1143 a8083063 Iustin Pop
1144 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1145 f78ede4e Guido Trotter
  def GetNodeList(self):
1146 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
1147 f78ede4e Guido Trotter

1148 f78ede4e Guido Trotter
    """
1149 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
1150 f78ede4e Guido Trotter
1151 6819dc49 Iustin Pop
  def _UnlockedGetOnlineNodeList(self):
1152 94a02bb5 Iustin Pop
    """Return the list of nodes which are online.
1153 94a02bb5 Iustin Pop

1154 94a02bb5 Iustin Pop
    """
1155 94a02bb5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
1156 94a02bb5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
1157 94a02bb5 Iustin Pop
    return [node.name for node in all_nodes if not node.offline]
1158 94a02bb5 Iustin Pop
1159 94a02bb5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1160 6819dc49 Iustin Pop
  def GetOnlineNodeList(self):
1161 6819dc49 Iustin Pop
    """Return the list of nodes which are online.
1162 6819dc49 Iustin Pop

1163 6819dc49 Iustin Pop
    """
1164 6819dc49 Iustin Pop
    return self._UnlockedGetOnlineNodeList()
1165 6819dc49 Iustin Pop
1166 6819dc49 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1167 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
1168 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
1169 d65e5776 Iustin Pop

1170 d65e5776 Iustin Pop
    @rtype: dict
1171 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
1172 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
1173 d65e5776 Iustin Pop

1174 d65e5776 Iustin Pop
    """
1175 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
1176 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
1177 d65e5776 Iustin Pop
    return my_dict
1178 d65e5776 Iustin Pop
1179 23f06b2b Iustin Pop
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1180 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
1181 ec0292f1 Iustin Pop

1182 23f06b2b Iustin Pop
    @type exceptions: list
1183 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1184 ec0292f1 Iustin Pop
    @rtype: tuple
1185 e623dbe3 Guido Trotter
    @return: tuple of (current, desired and possible, possible)
1186 ec0292f1 Iustin Pop

1187 ec0292f1 Iustin Pop
    """
1188 e623dbe3 Guido Trotter
    mc_now = mc_should = mc_max = 0
1189 23f06b2b Iustin Pop
    for node in self._config_data.nodes.values():
1190 23f06b2b Iustin Pop
      if exceptions and node.name in exceptions:
1191 23f06b2b Iustin Pop
        continue
1192 5bf07049 Iustin Pop
      if not (node.offline or node.drained):
1193 ec0292f1 Iustin Pop
        mc_max += 1
1194 ec0292f1 Iustin Pop
      if node.master_candidate:
1195 ec0292f1 Iustin Pop
        mc_now += 1
1196 e623dbe3 Guido Trotter
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1197 e623dbe3 Guido Trotter
    return (mc_now, mc_should, mc_max)
1198 ec0292f1 Iustin Pop
1199 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1200 23f06b2b Iustin Pop
  def GetMasterCandidateStats(self, exceptions=None):
1201 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
1202 ec0292f1 Iustin Pop

1203 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1204 ec0292f1 Iustin Pop

1205 23f06b2b Iustin Pop
    @type exceptions: list
1206 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1207 ec0292f1 Iustin Pop
    @rtype: tuple
1208 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
1209 ec0292f1 Iustin Pop

1210 ec0292f1 Iustin Pop
    """
1211 23f06b2b Iustin Pop
    return self._UnlockedGetMasterCandidateStats(exceptions)
1212 ec0292f1 Iustin Pop
1213 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
1214 44485f49 Guido Trotter
  def MaintainCandidatePool(self, exceptions):
1215 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
1216 ec0292f1 Iustin Pop

1217 44485f49 Guido Trotter
    @type exceptions: list
1218 44485f49 Guido Trotter
    @param exceptions: if passed, list of nodes that should be ignored
1219 ec0292f1 Iustin Pop
    @rtype: list
1220 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
1221 ec0292f1 Iustin Pop

1222 ec0292f1 Iustin Pop
    """
1223 44485f49 Guido Trotter
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1224 ec0292f1 Iustin Pop
    mod_list = []
1225 ec0292f1 Iustin Pop
    if mc_now < mc_max:
1226 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
1227 ec0292f1 Iustin Pop
      random.shuffle(node_list)
1228 ec0292f1 Iustin Pop
      for name in node_list:
1229 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
1230 ec0292f1 Iustin Pop
          break
1231 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
1232 44485f49 Guido Trotter
        if (node.master_candidate or node.offline or node.drained or
1233 44485f49 Guido Trotter
            node.name in exceptions):
1234 ec0292f1 Iustin Pop
          continue
1235 ee513a66 Iustin Pop
        mod_list.append(node)
1236 ec0292f1 Iustin Pop
        node.master_candidate = True
1237 ec0292f1 Iustin Pop
        node.serial_no += 1
1238 ec0292f1 Iustin Pop
        mc_now += 1
1239 ec0292f1 Iustin Pop
      if mc_now != mc_max:
1240 ec0292f1 Iustin Pop
        # this should not happen
1241 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1242 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1243 ec0292f1 Iustin Pop
      if mod_list:
1244 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
1245 ec0292f1 Iustin Pop
        self._WriteConfig()
1246 ec0292f1 Iustin Pop
1247 ec0292f1 Iustin Pop
    return mod_list
1248 ec0292f1 Iustin Pop
1249 190e3cb6 Guido Trotter
  def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1250 190e3cb6 Guido Trotter
    """Add a given node to the specified group.
1251 190e3cb6 Guido Trotter

1252 190e3cb6 Guido Trotter
    """
1253 190e3cb6 Guido Trotter
    if nodegroup_uuid not in self._config_data.nodegroups:
1254 190e3cb6 Guido Trotter
      # This can happen if a node group gets deleted between its lookup and
1255 190e3cb6 Guido Trotter
      # when we're adding the first node to it, since we don't keep a lock in
1256 190e3cb6 Guido Trotter
      # the meantime. It's ok though, as we'll fail cleanly if the node group
1257 190e3cb6 Guido Trotter
      # is not found anymore.
1258 190e3cb6 Guido Trotter
      raise errors.OpExecError("Unknown nodegroup: %s" % nodegroup_uuid)
1259 190e3cb6 Guido Trotter
    if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1260 190e3cb6 Guido Trotter
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1261 190e3cb6 Guido Trotter
1262 190e3cb6 Guido Trotter
  def _UnlockedRemoveNodeFromGroup(self, node):
1263 190e3cb6 Guido Trotter
    """Remove a given node from its group.
1264 190e3cb6 Guido Trotter

1265 190e3cb6 Guido Trotter
    """
1266 190e3cb6 Guido Trotter
    nodegroup = node.nodegroup
1267 190e3cb6 Guido Trotter
    if nodegroup not in self._config_data.nodegroups:
1268 190e3cb6 Guido Trotter
      logging.warning("Warning: node '%s' has a non-existing nodegroup '%s'"
1269 190e3cb6 Guido Trotter
                      " (while being removed from it)", node.name, nodegroup)
1270 190e3cb6 Guido Trotter
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
1271 190e3cb6 Guido Trotter
    if node.name not in nodegroup_obj.members:
1272 190e3cb6 Guido Trotter
      logging.warning("Warning: node '%s' not a member of its nodegroup '%s'"
1273 190e3cb6 Guido Trotter
                      " (while being removed from it)", node.name, nodegroup)
1274 190e3cb6 Guido Trotter
    else:
1275 190e3cb6 Guido Trotter
      nodegroup_obj.members.remove(node.name)
1276 190e3cb6 Guido Trotter
1277 a8083063 Iustin Pop
  def _BumpSerialNo(self):
1278 a8083063 Iustin Pop
    """Bump up the serial number of the config.
1279 a8083063 Iustin Pop

1280 a8083063 Iustin Pop
    """
1281 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
1282 d693c864 Iustin Pop
    self._config_data.mtime = time.time()
1283 a8083063 Iustin Pop
1284 76d5d3a3 Iustin Pop
  def _AllUUIDObjects(self):
1285 76d5d3a3 Iustin Pop
    """Returns all objects with uuid attributes.
1286 76d5d3a3 Iustin Pop

1287 76d5d3a3 Iustin Pop
    """
1288 76d5d3a3 Iustin Pop
    return (self._config_data.instances.values() +
1289 76d5d3a3 Iustin Pop
            self._config_data.nodes.values() +
1290 3df43542 Guido Trotter
            self._config_data.nodegroups.values() +
1291 76d5d3a3 Iustin Pop
            [self._config_data.cluster])
1292 76d5d3a3 Iustin Pop
1293 a8083063 Iustin Pop
  def _OpenConfig(self):
1294 a8083063 Iustin Pop
    """Read the config data from disk.
1295 a8083063 Iustin Pop

1296 a8083063 Iustin Pop
    """
1297 13998ef2 Michael Hanselmann
    raw_data = utils.ReadFile(self._cfg_file)
1298 13998ef2 Michael Hanselmann
1299 a8083063 Iustin Pop
    try:
1300 13998ef2 Michael Hanselmann
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
1301 13998ef2 Michael Hanselmann
    except Exception, err:
1302 13998ef2 Michael Hanselmann
      raise errors.ConfigurationError(err)
1303 5b263ed7 Michael Hanselmann
1304 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
1305 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
1306 5b263ed7 Michael Hanselmann
1307 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
1308 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
1309 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
1310 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
1311 90d726a8 Iustin Pop
1312 90d726a8 Iustin Pop
    # Upgrade configuration if needed
1313 90d726a8 Iustin Pop
    data.UpgradeConfig()
1314 90d726a8 Iustin Pop
1315 a8083063 Iustin Pop
    self._config_data = data
1316 3c7f6c44 Iustin Pop
    # reset the last serial as -1 so that the next write will cause
1317 0779e3aa Iustin Pop
    # ssconf update
1318 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
1319 a8083063 Iustin Pop
1320 76d5d3a3 Iustin Pop
    # And finally run our (custom) config upgrade sequence
1321 76d5d3a3 Iustin Pop
    self._UpgradeConfig()
1322 76d5d3a3 Iustin Pop
1323 76d5d3a3 Iustin Pop
  def _UpgradeConfig(self):
1324 76d5d3a3 Iustin Pop
    """Run upgrade steps that cannot be done purely in the objects.
1325 76d5d3a3 Iustin Pop

1326 76d5d3a3 Iustin Pop
    This is because some data elements need uniqueness across the
1327 76d5d3a3 Iustin Pop
    whole configuration, etc.
1328 76d5d3a3 Iustin Pop

1329 111c4e2f Guido Trotter
    @warning: this function will call L{_WriteConfig()}, but also
1330 111c4e2f Guido Trotter
        L{DropECReservations} so it needs to be called only from a
1331 111c4e2f Guido Trotter
        "safe" place (the constructor). If one wanted to call it with
1332 111c4e2f Guido Trotter
        the lock held, a DropECReservationUnlocked would need to be
1333 111c4e2f Guido Trotter
        created first, to avoid causing deadlock.
1334 76d5d3a3 Iustin Pop

1335 76d5d3a3 Iustin Pop
    """
1336 76d5d3a3 Iustin Pop
    modified = False
1337 76d5d3a3 Iustin Pop
    for item in self._AllUUIDObjects():
1338 76d5d3a3 Iustin Pop
      if item.uuid is None:
1339 4fae38c5 Guido Trotter
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
1340 76d5d3a3 Iustin Pop
        modified = True
1341 f9e81396 Guido Trotter
    if not self._config_data.nodegroups:
1342 f9e81396 Guido Trotter
      default_nodegroup_uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
1343 f9e81396 Guido Trotter
      default_nodegroup = objects.NodeGroup(
1344 f9e81396 Guido Trotter
          uuid=default_nodegroup_uuid,
1345 f9e81396 Guido Trotter
          name="default",
1346 f9e81396 Guido Trotter
          members=[],
1347 f9e81396 Guido Trotter
          )
1348 f9e81396 Guido Trotter
      self._config_data.nodegroups[default_nodegroup_uuid] = default_nodegroup
1349 f9e81396 Guido Trotter
      modified = True
1350 190e3cb6 Guido Trotter
    for node in self._config_data.nodes.values():
1351 190e3cb6 Guido Trotter
      if not node.nodegroup:
1352 190e3cb6 Guido Trotter
        node.nodegroup = self.LookupNodeGroup(None)
1353 190e3cb6 Guido Trotter
        modified = True
1354 190e3cb6 Guido Trotter
      # This is technically *not* an upgrade, but needs to be done both when
1355 190e3cb6 Guido Trotter
      # nodegroups are being added, and upon normally loading the config,
1356 190e3cb6 Guido Trotter
      # because the members list of a node group is discarded upon
1357 190e3cb6 Guido Trotter
      # serializing/deserializing the object.
1358 190e3cb6 Guido Trotter
      self._UnlockedAddNodeToGroup(node.name, node.nodegroup)
1359 76d5d3a3 Iustin Pop
    if modified:
1360 76d5d3a3 Iustin Pop
      self._WriteConfig()
1361 4fae38c5 Guido Trotter
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
1362 4fae38c5 Guido Trotter
      # only called at config init time, without the lock held
1363 4fae38c5 Guido Trotter
      self.DropECReservations(_UPGRADE_CONFIG_JID)
1364 4fae38c5 Guido Trotter
1365 a4eae71f Michael Hanselmann
  def _DistributeConfig(self, feedback_fn):
1366 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
1367 a8083063 Iustin Pop

1368 a8083063 Iustin Pop
    Currently, this only copies the configuration file. In the future,
1369 a8083063 Iustin Pop
    it could be used to encapsulate the 2/3-phase update mechanism.
1370 a8083063 Iustin Pop

1371 a8083063 Iustin Pop
    """
1372 a8083063 Iustin Pop
    if self._offline:
1373 a8083063 Iustin Pop
      return True
1374 a4eae71f Michael Hanselmann
1375 a8083063 Iustin Pop
    bad = False
1376 a8083063 Iustin Pop
1377 6a5b8b4b Iustin Pop
    node_list = []
1378 6a5b8b4b Iustin Pop
    addr_list = []
1379 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
1380 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
1381 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
1382 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
1383 6b294c53 Iustin Pop
    # in between
1384 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
1385 6a5b8b4b Iustin Pop
      if node_name == myhostname:
1386 6a5b8b4b Iustin Pop
        continue
1387 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
1388 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
1389 6a5b8b4b Iustin Pop
        continue
1390 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
1391 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
1392 6b294c53 Iustin Pop
1393 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1394 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
1395 1b54fc6c Guido Trotter
    for to_node, to_result in result.items():
1396 3cebe102 Michael Hanselmann
      msg = to_result.fail_msg
1397 1b54fc6c Guido Trotter
      if msg:
1398 1b54fc6c Guido Trotter
        msg = ("Copy of file %s to node %s failed: %s" %
1399 dd7db360 Iustin Pop
               (self._cfg_file, to_node, msg))
1400 1b54fc6c Guido Trotter
        logging.error(msg)
1401 a4eae71f Michael Hanselmann
1402 a4eae71f Michael Hanselmann
        if feedback_fn:
1403 a4eae71f Michael Hanselmann
          feedback_fn(msg)
1404 a4eae71f Michael Hanselmann
1405 a8083063 Iustin Pop
        bad = True
1406 a4eae71f Michael Hanselmann
1407 a8083063 Iustin Pop
    return not bad
1408 a8083063 Iustin Pop
1409 a4eae71f Michael Hanselmann
  def _WriteConfig(self, destination=None, feedback_fn=None):
1410 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
1411 a8083063 Iustin Pop

1412 a8083063 Iustin Pop
    """
1413 a4eae71f Michael Hanselmann
    assert feedback_fn is None or callable(feedback_fn)
1414 a4eae71f Michael Hanselmann
1415 d2231b8c Iustin Pop
    # Warn on config errors, but don't abort the save - the
1416 d2231b8c Iustin Pop
    # configuration has already been modified, and we can't revert;
1417 d2231b8c Iustin Pop
    # the best we can do is to warn the user and save as is, leaving
1418 d2231b8c Iustin Pop
    # recovery to the user
1419 4a89c54a Iustin Pop
    config_errors = self._UnlockedVerifyConfig()
1420 4a89c54a Iustin Pop
    if config_errors:
1421 d2231b8c Iustin Pop
      errmsg = ("Configuration data is not consistent: %s" %
1422 1f864b60 Iustin Pop
                (utils.CommaJoin(config_errors)))
1423 d2231b8c Iustin Pop
      logging.critical(errmsg)
1424 d2231b8c Iustin Pop
      if feedback_fn:
1425 d2231b8c Iustin Pop
        feedback_fn(errmsg)
1426 d2231b8c Iustin Pop
1427 a8083063 Iustin Pop
    if destination is None:
1428 a8083063 Iustin Pop
      destination = self._cfg_file
1429 a8083063 Iustin Pop
    self._BumpSerialNo()
1430 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
1431 13998ef2 Michael Hanselmann
1432 e60c73a1 René Nussbaumer
    getents = self._getents()
1433 e60c73a1 René Nussbaumer
    utils.WriteFile(destination, data=txt, gid=getents.confd_gid, mode=0640)
1434 13998ef2 Michael Hanselmann
1435 14e15659 Iustin Pop
    self.write_count += 1
1436 3d3a04bc Iustin Pop
1437 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
1438 a4eae71f Michael Hanselmann
    self._DistributeConfig(feedback_fn)
1439 a8083063 Iustin Pop
1440 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
1441 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
1442 d9a855f1 Michael Hanselmann
      if not self._offline:
1443 cd34faf2 Michael Hanselmann
        result = rpc.RpcRunner.call_write_ssconf_files(
1444 6819dc49 Iustin Pop
          self._UnlockedGetOnlineNodeList(),
1445 e1e75d00 Iustin Pop
          self._UnlockedGetSsconfValues())
1446 a4eae71f Michael Hanselmann
1447 e1e75d00 Iustin Pop
        for nname, nresu in result.items():
1448 3cebe102 Michael Hanselmann
          msg = nresu.fail_msg
1449 e1e75d00 Iustin Pop
          if msg:
1450 a4eae71f Michael Hanselmann
            errmsg = ("Error while uploading ssconf files to"
1451 a4eae71f Michael Hanselmann
                      " node %s: %s" % (nname, msg))
1452 a4eae71f Michael Hanselmann
            logging.warning(errmsg)
1453 a4eae71f Michael Hanselmann
1454 a4eae71f Michael Hanselmann
            if feedback_fn:
1455 a4eae71f Michael Hanselmann
              feedback_fn(errmsg)
1456 a4eae71f Michael Hanselmann
1457 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
1458 54d1a06e Michael Hanselmann
1459 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
1460 054596f0 Iustin Pop
    """Return the values needed by ssconf.
1461 054596f0 Iustin Pop

1462 054596f0 Iustin Pop
    @rtype: dict
1463 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
1464 054596f0 Iustin Pop
        associated value
1465 054596f0 Iustin Pop

1466 054596f0 Iustin Pop
    """
1467 a3316e4a Iustin Pop
    fn = "\n".join
1468 81a49123 Iustin Pop
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1469 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1470 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1471 c3029d0a Luca Bigliardi
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
1472 5909fb97 Luca Bigliardi
                    for ninfo in node_info]
1473 c3029d0a Luca Bigliardi
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
1474 5909fb97 Luca Bigliardi
                    for ninfo in node_info]
1475 a3316e4a Iustin Pop
1476 81a49123 Iustin Pop
    instance_data = fn(instance_names)
1477 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
1478 81a49123 Iustin Pop
    on_data = fn(node.name for node in node_info if not node.offline)
1479 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1480 8113a52e Luca Bigliardi
    mc_ips_data = fn(node.primary_ip for node in node_info
1481 8113a52e Luca Bigliardi
                     if node.master_candidate)
1482 a3316e4a Iustin Pop
    node_data = fn(node_names)
1483 f9780ccd Luca Bigliardi
    node_pri_ips_data = fn(node_pri_ips)
1484 f9780ccd Luca Bigliardi
    node_snd_ips_data = fn(node_snd_ips)
1485 f56618e0 Iustin Pop
1486 054596f0 Iustin Pop
    cluster = self._config_data.cluster
1487 5d60b3bd Iustin Pop
    cluster_tags = fn(cluster.GetTags())
1488 4f7a6a10 Iustin Pop
1489 4f7a6a10 Iustin Pop
    hypervisor_list = fn(cluster.enabled_hypervisors)
1490 4f7a6a10 Iustin Pop
1491 0fbae49a Balazs Lecz
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
1492 0fbae49a Balazs Lecz
1493 6f076453 Guido Trotter
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
1494 6f076453 Guido Trotter
                  self._config_data.nodegroups.values()]
1495 6f076453 Guido Trotter
    nodegroups_data = fn(utils.NiceSort(nodegroups))
1496 6f076453 Guido Trotter
1497 03d1dba2 Michael Hanselmann
    return {
1498 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1499 5d60b3bd Iustin Pop
      constants.SS_CLUSTER_TAGS: cluster_tags,
1500 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1501 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
1502 8113a52e Luca Bigliardi
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
1503 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
1504 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1505 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
1506 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
1507 f9780ccd Luca Bigliardi
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
1508 f9780ccd Luca Bigliardi
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
1509 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
1510 81a49123 Iustin Pop
      constants.SS_ONLINE_NODES: on_data,
1511 868a98ca Manuel Franceschini
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
1512 81a49123 Iustin Pop
      constants.SS_INSTANCE_LIST: instance_data,
1513 8a113c7a Iustin Pop
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1514 4f7a6a10 Iustin Pop
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
1515 5c465a95 Iustin Pop
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
1516 0fbae49a Balazs Lecz
      constants.SS_UID_POOL: uid_pool,
1517 6f076453 Guido Trotter
      constants.SS_NODEGROUPS: nodegroups_data,
1518 03d1dba2 Michael Hanselmann
      }
1519 03d1dba2 Michael Hanselmann
1520 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1521 d367b66c Manuel Franceschini
  def GetSsconfValues(self):
1522 d367b66c Manuel Franceschini
    """Wrapper using lock around _UnlockedGetSsconf().
1523 d367b66c Manuel Franceschini

1524 d367b66c Manuel Franceschini
    """
1525 d367b66c Manuel Franceschini
    return self._UnlockedGetSsconfValues()
1526 d367b66c Manuel Franceschini
1527 d367b66c Manuel Franceschini
  @locking.ssynchronized(_config_lock, shared=1)
1528 a8083063 Iustin Pop
  def GetVGName(self):
1529 a8083063 Iustin Pop
    """Return the volume group name.
1530 a8083063 Iustin Pop

1531 a8083063 Iustin Pop
    """
1532 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1533 a8083063 Iustin Pop
1534 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1535 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1536 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1537 89ff8e15 Manuel Franceschini

1538 89ff8e15 Manuel Franceschini
    """
1539 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1540 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1541 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1542 89ff8e15 Manuel Franceschini
1543 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1544 9e33896b Luca Bigliardi
  def GetDRBDHelper(self):
1545 9e33896b Luca Bigliardi
    """Return DRBD usermode helper.
1546 9e33896b Luca Bigliardi

1547 9e33896b Luca Bigliardi
    """
1548 9e33896b Luca Bigliardi
    return self._config_data.cluster.drbd_usermode_helper
1549 9e33896b Luca Bigliardi
1550 9e33896b Luca Bigliardi
  @locking.ssynchronized(_config_lock)
1551 9e33896b Luca Bigliardi
  def SetDRBDHelper(self, drbd_helper):
1552 9e33896b Luca Bigliardi
    """Set DRBD usermode helper.
1553 9e33896b Luca Bigliardi

1554 9e33896b Luca Bigliardi
    """
1555 9e33896b Luca Bigliardi
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
1556 9e33896b Luca Bigliardi
    self._config_data.cluster.serial_no += 1
1557 9e33896b Luca Bigliardi
    self._WriteConfig()
1558 9e33896b Luca Bigliardi
1559 9e33896b Luca Bigliardi
  @locking.ssynchronized(_config_lock, shared=1)
1560 a8083063 Iustin Pop
  def GetMACPrefix(self):
1561 a8083063 Iustin Pop
    """Return the mac prefix.
1562 a8083063 Iustin Pop

1563 a8083063 Iustin Pop
    """
1564 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1565 62779dd0 Iustin Pop
1566 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1567 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1568 5bbd3f7f Michael Hanselmann
    """Returns information about the cluster
1569 62779dd0 Iustin Pop

1570 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1571 c41eea6e Iustin Pop
    @return: the cluster object
1572 62779dd0 Iustin Pop

1573 62779dd0 Iustin Pop
    """
1574 62779dd0 Iustin Pop
    return self._config_data.cluster
1575 e00fb268 Iustin Pop
1576 51cb1581 Luca Bigliardi
  @locking.ssynchronized(_config_lock, shared=1)
1577 51cb1581 Luca Bigliardi
  def HasAnyDiskOfType(self, dev_type):
1578 51cb1581 Luca Bigliardi
    """Check if in there is at disk of the given type in the configuration.
1579 51cb1581 Luca Bigliardi

1580 51cb1581 Luca Bigliardi
    """
1581 51cb1581 Luca Bigliardi
    return self._config_data.HasAnyDiskOfType(dev_type)
1582 51cb1581 Luca Bigliardi
1583 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1584 a4eae71f Michael Hanselmann
  def Update(self, target, feedback_fn):
1585 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1586 e00fb268 Iustin Pop

1587 e00fb268 Iustin Pop
    This function must be called when an object (as returned by
1588 e00fb268 Iustin Pop
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
1589 e00fb268 Iustin Pop
    caller wants the modifications saved to the backing store. Note
1590 e00fb268 Iustin Pop
    that all modified objects will be saved, but the target argument
1591 e00fb268 Iustin Pop
    is the one the caller wants to ensure that it's saved.
1592 e00fb268 Iustin Pop

1593 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1594 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1595 c41eea6e Iustin Pop
        the cluster
1596 a4eae71f Michael Hanselmann
    @param feedback_fn: Callable feedback function
1597 c41eea6e Iustin Pop

1598 e00fb268 Iustin Pop
    """
1599 e00fb268 Iustin Pop
    if self._config_data is None:
1600 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1601 3ecf6786 Iustin Pop
                                   " cannot save.")
1602 f34901f8 Iustin Pop
    update_serial = False
1603 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1604 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1605 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1606 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1607 f34901f8 Iustin Pop
      update_serial = True
1608 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1609 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1610 e00fb268 Iustin Pop
    else:
1611 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1612 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1613 e00fb268 Iustin Pop
    if not test:
1614 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1615 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1616 f34901f8 Iustin Pop
    target.serial_no += 1
1617 d693c864 Iustin Pop
    target.mtime = now = time.time()
1618 f34901f8 Iustin Pop
1619 cff4c037 Iustin Pop
    if update_serial:
1620 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1621 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1622 d693c864 Iustin Pop
      self._config_data.cluster.mtime = now
1623 b989e85d Iustin Pop
1624 61cf6b5e Iustin Pop
    if isinstance(target, objects.Instance):
1625 61cf6b5e Iustin Pop
      self._UnlockedReleaseDRBDMinors(target.name)
1626 61cf6b5e Iustin Pop
1627 a4eae71f Michael Hanselmann
    self._WriteConfig(feedback_fn=feedback_fn)
1628 73064714 Guido Trotter
1629 73064714 Guido Trotter
  @locking.ssynchronized(_config_lock)
1630 73064714 Guido Trotter
  def DropECReservations(self, ec_id):
1631 73064714 Guido Trotter
    """Drop per-execution-context reservations
1632 73064714 Guido Trotter

1633 73064714 Guido Trotter
    """
1634 d8aee57e Iustin Pop
    for rm in self._all_rms:
1635 d8aee57e Iustin Pop
      rm.DropECReservations(ec_id)