Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 36b66e6e

History | View | Annotate | Download (45.7 kB)

1 2f31098c Iustin Pop
#
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 a8083063 Iustin Pop
# Copyright (C) 2006, 2007 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 a8083063 Iustin Pop
import os
35 a8083063 Iustin Pop
import random
36 d8470559 Michael Hanselmann
import logging
37 d693c864 Iustin Pop
import time
38 a8083063 Iustin Pop
39 a8083063 Iustin Pop
from ganeti import errors
40 f78ede4e Guido Trotter
from ganeti import locking
41 a8083063 Iustin Pop
from ganeti import utils
42 a8083063 Iustin Pop
from ganeti import constants
43 a8083063 Iustin Pop
from ganeti import rpc
44 a8083063 Iustin Pop
from ganeti import objects
45 8d14b30d Iustin Pop
from ganeti import serializer
46 243cdbcc Michael Hanselmann
47 243cdbcc Michael Hanselmann
48 f78ede4e Guido Trotter
_config_lock = locking.SharedLock()
49 f78ede4e Guido Trotter
50 4fae38c5 Guido Trotter
# job id used for resource management at config upgrade time
51 4fae38c5 Guido Trotter
_UPGRADE_CONFIG_JID="jid-cfg-upgrade"
52 4fae38c5 Guido Trotter
53 f78ede4e Guido Trotter
54 5b263ed7 Michael Hanselmann
def _ValidateConfig(data):
55 c41eea6e Iustin Pop
  """Verifies that a configuration objects looks valid.
56 c41eea6e Iustin Pop

57 c41eea6e Iustin Pop
  This only verifies the version of the configuration.
58 c41eea6e Iustin Pop

59 c41eea6e Iustin Pop
  @raise errors.ConfigurationError: if the version differs from what
60 c41eea6e Iustin Pop
      we expect
61 c41eea6e Iustin Pop

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

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

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

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

128 098c0958 Michael Hanselmann
  """
129 a8083063 Iustin Pop
  def __init__(self, cfg_file=None, offline=False):
130 14e15659 Iustin Pop
    self.write_count = 0
131 f78ede4e Guido Trotter
    self._lock = _config_lock
132 a8083063 Iustin Pop
    self._config_data = None
133 a8083063 Iustin Pop
    self._offline = offline
134 a8083063 Iustin Pop
    if cfg_file is None:
135 a8083063 Iustin Pop
      self._cfg_file = constants.CLUSTER_CONF_FILE
136 a8083063 Iustin Pop
    else:
137 a8083063 Iustin Pop
      self._cfg_file = cfg_file
138 4fae38c5 Guido Trotter
    self._temporary_ids = TemporaryReservationManager()
139 a81c53c9 Iustin Pop
    self._temporary_drbds = {}
140 36b66e6e Guido Trotter
    self._temporary_macs = TemporaryReservationManager()
141 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
142 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
143 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
144 89e1fc26 Iustin Pop
    # file than after it was modified
145 89e1fc26 Iustin Pop
    self._my_hostname = utils.HostInfo().name
146 3c7f6c44 Iustin Pop
    self._last_cluster_serial = -1
147 3d3a04bc Iustin Pop
    self._OpenConfig()
148 a8083063 Iustin Pop
149 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
150 a8083063 Iustin Pop
  @staticmethod
151 a8083063 Iustin Pop
  def IsCluster():
152 a8083063 Iustin Pop
    """Check if the cluster is configured.
153 a8083063 Iustin Pop

154 a8083063 Iustin Pop
    """
155 a8083063 Iustin Pop
    return os.path.exists(constants.CLUSTER_CONF_FILE)
156 a8083063 Iustin Pop
157 36b66e6e Guido Trotter
  def _GenerateOneMAC(self):
158 36b66e6e Guido Trotter
    """Generate one mac address
159 36b66e6e Guido Trotter

160 36b66e6e Guido Trotter
    """
161 36b66e6e Guido Trotter
    prefix = self._config_data.cluster.mac_prefix
162 36b66e6e Guido Trotter
    byte1 = random.randrange(0, 256)
163 36b66e6e Guido Trotter
    byte2 = random.randrange(0, 256)
164 36b66e6e Guido Trotter
    byte3 = random.randrange(0, 256)
165 36b66e6e Guido Trotter
    mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
166 36b66e6e Guido Trotter
    return mac
167 36b66e6e Guido Trotter
168 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
169 36b66e6e Guido Trotter
  def GenerateMAC(self, ec_id):
170 a8083063 Iustin Pop
    """Generate a MAC for an instance.
171 a8083063 Iustin Pop

172 a8083063 Iustin Pop
    This should check the current instances for duplicates.
173 a8083063 Iustin Pop

174 a8083063 Iustin Pop
    """
175 36b66e6e Guido Trotter
    existing = self._AllMACs()
176 36b66e6e Guido Trotter
    return self._temporary_ids.Generate(existing, self._GenerateOneMAC, ec_id)
177 a8083063 Iustin Pop
178 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
179 36b66e6e Guido Trotter
  def ReserveMAC(self, mac, ec_id):
180 36b66e6e Guido Trotter
    """Reserve a MAC for an instance.
181 1862d460 Alexander Schreiber

182 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
183 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
184 1862d460 Alexander Schreiber

185 1862d460 Alexander Schreiber
    """
186 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
187 36b66e6e Guido Trotter
    if mac in all_macs:
188 36b66e6e Guido Trotter
      raise errors.ReservationError("mac already in use")
189 36b66e6e Guido Trotter
    else:
190 36b66e6e Guido Trotter
      self._temporary_macs.Reserve(mac, ec_id)
191 1862d460 Alexander Schreiber
192 f9518d38 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
193 f9518d38 Iustin Pop
  def GenerateDRBDSecret(self):
194 f9518d38 Iustin Pop
    """Generate a DRBD secret.
195 f9518d38 Iustin Pop

196 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
197 f9518d38 Iustin Pop

198 f9518d38 Iustin Pop
    """
199 f9518d38 Iustin Pop
    all_secrets = self._AllDRBDSecrets()
200 f9518d38 Iustin Pop
    retries = 64
201 f9518d38 Iustin Pop
    while retries > 0:
202 f9518d38 Iustin Pop
      secret = utils.GenerateSecret()
203 f9518d38 Iustin Pop
      if secret not in all_secrets:
204 f9518d38 Iustin Pop
        break
205 f9518d38 Iustin Pop
      retries -= 1
206 f9518d38 Iustin Pop
    else:
207 f9518d38 Iustin Pop
      raise errors.ConfigurationError("Can't generate unique DRBD secret")
208 f9518d38 Iustin Pop
    return secret
209 f9518d38 Iustin Pop
210 34e54ebc Iustin Pop
  def _AllLVs(self):
211 923b1523 Iustin Pop
    """Compute the list of all LVs.
212 923b1523 Iustin Pop

213 923b1523 Iustin Pop
    """
214 923b1523 Iustin Pop
    lvnames = set()
215 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
216 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
217 923b1523 Iustin Pop
      for lv_list in node_data.values():
218 923b1523 Iustin Pop
        lvnames.update(lv_list)
219 923b1523 Iustin Pop
    return lvnames
220 923b1523 Iustin Pop
221 34e54ebc Iustin Pop
  def _AllIDs(self, include_temporary):
222 34e54ebc Iustin Pop
    """Compute the list of all UUIDs and names we have.
223 34e54ebc Iustin Pop

224 34e54ebc Iustin Pop
    @type include_temporary: boolean
225 34e54ebc Iustin Pop
    @param include_temporary: whether to include the _temporary_ids set
226 34e54ebc Iustin Pop
    @rtype: set
227 34e54ebc Iustin Pop
    @return: a set of IDs
228 34e54ebc Iustin Pop

229 34e54ebc Iustin Pop
    """
230 34e54ebc Iustin Pop
    existing = set()
231 34e54ebc Iustin Pop
    if include_temporary:
232 4fae38c5 Guido Trotter
      existing.update(self._temporary_ids.GetReserved())
233 34e54ebc Iustin Pop
    existing.update(self._AllLVs())
234 34e54ebc Iustin Pop
    existing.update(self._config_data.instances.keys())
235 34e54ebc Iustin Pop
    existing.update(self._config_data.nodes.keys())
236 76d5d3a3 Iustin Pop
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
237 34e54ebc Iustin Pop
    return existing
238 34e54ebc Iustin Pop
239 4fae38c5 Guido Trotter
  def _GenerateUniqueID(self, ec_id):
240 430b923c Iustin Pop
    """Generate an unique UUID.
241 923b1523 Iustin Pop

242 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
243 923b1523 Iustin Pop
    duplicates.
244 923b1523 Iustin Pop

245 c41eea6e Iustin Pop
    @rtype: string
246 c41eea6e Iustin Pop
    @return: the unique id
247 923b1523 Iustin Pop

248 923b1523 Iustin Pop
    """
249 4fae38c5 Guido Trotter
    existing = self._AllIDs(include_temporary=False)
250 4fae38c5 Guido Trotter
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
251 923b1523 Iustin Pop
252 430b923c Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
253 4fae38c5 Guido Trotter
  def GenerateUniqueID(self, ec_id):
254 430b923c Iustin Pop
    """Generate an unique ID.
255 430b923c Iustin Pop

256 430b923c Iustin Pop
    This is just a wrapper over the unlocked version.
257 430b923c Iustin Pop

258 4fae38c5 Guido Trotter
    @type ec_id: string
259 4fae38c5 Guido Trotter
    @param ec_id: unique id for the job to reserve the id to
260 34d657ba Iustin Pop

261 34d657ba Iustin Pop
    """
262 4fae38c5 Guido Trotter
    return self._GenerateUniqueID(ec_id)
263 34d657ba Iustin Pop
264 a8083063 Iustin Pop
  def _AllMACs(self):
265 a8083063 Iustin Pop
    """Return all MACs present in the config.
266 a8083063 Iustin Pop

267 c41eea6e Iustin Pop
    @rtype: list
268 c41eea6e Iustin Pop
    @return: the list of all MACs
269 c41eea6e Iustin Pop

270 a8083063 Iustin Pop
    """
271 a8083063 Iustin Pop
    result = []
272 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
273 a8083063 Iustin Pop
      for nic in instance.nics:
274 a8083063 Iustin Pop
        result.append(nic.mac)
275 a8083063 Iustin Pop
276 a8083063 Iustin Pop
    return result
277 a8083063 Iustin Pop
278 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
279 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
280 f9518d38 Iustin Pop

281 c41eea6e Iustin Pop
    @rtype: list
282 c41eea6e Iustin Pop
    @return: the list of all DRBD secrets
283 c41eea6e Iustin Pop

284 f9518d38 Iustin Pop
    """
285 f9518d38 Iustin Pop
    def helper(disk, result):
286 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
287 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
288 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
289 f9518d38 Iustin Pop
      if disk.children:
290 f9518d38 Iustin Pop
        for child in disk.children:
291 f9518d38 Iustin Pop
          helper(child, result)
292 f9518d38 Iustin Pop
293 f9518d38 Iustin Pop
    result = []
294 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
295 f9518d38 Iustin Pop
      for disk in instance.disks:
296 f9518d38 Iustin Pop
        helper(disk, result)
297 f9518d38 Iustin Pop
298 f9518d38 Iustin Pop
    return result
299 f9518d38 Iustin Pop
300 4b98ac29 Iustin Pop
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
301 4b98ac29 Iustin Pop
    """Compute duplicate disk IDs
302 4b98ac29 Iustin Pop

303 4b98ac29 Iustin Pop
    @type disk: L{objects.Disk}
304 4b98ac29 Iustin Pop
    @param disk: the disk at which to start searching
305 4b98ac29 Iustin Pop
    @type l_ids: list
306 4b98ac29 Iustin Pop
    @param l_ids: list of current logical ids
307 4b98ac29 Iustin Pop
    @type p_ids: list
308 4b98ac29 Iustin Pop
    @param p_ids: list of current physical ids
309 4b98ac29 Iustin Pop
    @rtype: list
310 4b98ac29 Iustin Pop
    @return: a list of error messages
311 4b98ac29 Iustin Pop

312 4b98ac29 Iustin Pop
    """
313 4b98ac29 Iustin Pop
    result = []
314 25ae22e4 Iustin Pop
    if disk.logical_id is not None:
315 25ae22e4 Iustin Pop
      if disk.logical_id in l_ids:
316 25ae22e4 Iustin Pop
        result.append("duplicate logical id %s" % str(disk.logical_id))
317 25ae22e4 Iustin Pop
      else:
318 25ae22e4 Iustin Pop
        l_ids.append(disk.logical_id)
319 25ae22e4 Iustin Pop
    if disk.physical_id is not None:
320 25ae22e4 Iustin Pop
      if disk.physical_id in p_ids:
321 25ae22e4 Iustin Pop
        result.append("duplicate physical id %s" % str(disk.physical_id))
322 25ae22e4 Iustin Pop
      else:
323 25ae22e4 Iustin Pop
        p_ids.append(disk.physical_id)
324 4b98ac29 Iustin Pop
325 4b98ac29 Iustin Pop
    if disk.children:
326 4b98ac29 Iustin Pop
      for child in disk.children:
327 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
328 4b98ac29 Iustin Pop
    return result
329 4b98ac29 Iustin Pop
330 4a89c54a Iustin Pop
  def _UnlockedVerifyConfig(self):
331 a8efbb40 Iustin Pop
    """Verify function.
332 a8efbb40 Iustin Pop

333 4a89c54a Iustin Pop
    @rtype: list
334 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
335 4a89c54a Iustin Pop
        configuration errors
336 4a89c54a Iustin Pop

337 a8083063 Iustin Pop
    """
338 a8083063 Iustin Pop
    result = []
339 a8083063 Iustin Pop
    seen_macs = []
340 48ce9fd9 Iustin Pop
    ports = {}
341 a8083063 Iustin Pop
    data = self._config_data
342 4b98ac29 Iustin Pop
    seen_lids = []
343 4b98ac29 Iustin Pop
    seen_pids = []
344 9a5fba23 Guido Trotter
345 9a5fba23 Guido Trotter
    # global cluster checks
346 9a5fba23 Guido Trotter
    if not data.cluster.enabled_hypervisors:
347 9a5fba23 Guido Trotter
      result.append("enabled hypervisors list doesn't have any entries")
348 9a5fba23 Guido Trotter
    invalid_hvs = set(data.cluster.enabled_hypervisors) - constants.HYPER_TYPES
349 9a5fba23 Guido Trotter
    if invalid_hvs:
350 9a5fba23 Guido Trotter
      result.append("enabled hypervisors contains invalid entries: %s" %
351 9a5fba23 Guido Trotter
                    invalid_hvs)
352 9a5fba23 Guido Trotter
353 9a5fba23 Guido Trotter
    if data.cluster.master_node not in data.nodes:
354 9a5fba23 Guido Trotter
      result.append("cluster has invalid primary node '%s'" %
355 9a5fba23 Guido Trotter
                    data.cluster.master_node)
356 9a5fba23 Guido Trotter
357 9a5fba23 Guido Trotter
    # per-instance checks
358 a8083063 Iustin Pop
    for instance_name in data.instances:
359 a8083063 Iustin Pop
      instance = data.instances[instance_name]
360 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
361 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
362 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
363 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
364 a8083063 Iustin Pop
        if snode not in data.nodes:
365 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
366 a8083063 Iustin Pop
                        (instance_name, snode))
367 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
368 a8083063 Iustin Pop
        if nic.mac in seen_macs:
369 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
370 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
371 a8083063 Iustin Pop
        else:
372 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
373 48ce9fd9 Iustin Pop
374 48ce9fd9 Iustin Pop
      # gather the drbd ports for duplicate checks
375 48ce9fd9 Iustin Pop
      for dsk in instance.disks:
376 48ce9fd9 Iustin Pop
        if dsk.dev_type in constants.LDS_DRBD:
377 48ce9fd9 Iustin Pop
          tcp_port = dsk.logical_id[2]
378 48ce9fd9 Iustin Pop
          if tcp_port not in ports:
379 48ce9fd9 Iustin Pop
            ports[tcp_port] = []
380 48ce9fd9 Iustin Pop
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
381 48ce9fd9 Iustin Pop
      # gather network port reservation
382 48ce9fd9 Iustin Pop
      net_port = getattr(instance, "network_port", None)
383 48ce9fd9 Iustin Pop
      if net_port is not None:
384 48ce9fd9 Iustin Pop
        if net_port not in ports:
385 48ce9fd9 Iustin Pop
          ports[net_port] = []
386 48ce9fd9 Iustin Pop
        ports[net_port].append((instance.name, "network port"))
387 48ce9fd9 Iustin Pop
388 332d0e37 Iustin Pop
      # instance disk verify
389 332d0e37 Iustin Pop
      for idx, disk in enumerate(instance.disks):
390 332d0e37 Iustin Pop
        result.extend(["instance '%s' disk %d error: %s" %
391 332d0e37 Iustin Pop
                       (instance.name, idx, msg) for msg in disk.Verify()])
392 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
393 332d0e37 Iustin Pop
394 48ce9fd9 Iustin Pop
    # cluster-wide pool of free ports
395 a8efbb40 Iustin Pop
    for free_port in data.cluster.tcpudp_port_pool:
396 48ce9fd9 Iustin Pop
      if free_port not in ports:
397 48ce9fd9 Iustin Pop
        ports[free_port] = []
398 48ce9fd9 Iustin Pop
      ports[free_port].append(("cluster", "port marked as free"))
399 48ce9fd9 Iustin Pop
400 48ce9fd9 Iustin Pop
    # compute tcp/udp duplicate ports
401 48ce9fd9 Iustin Pop
    keys = ports.keys()
402 48ce9fd9 Iustin Pop
    keys.sort()
403 48ce9fd9 Iustin Pop
    for pnum in keys:
404 48ce9fd9 Iustin Pop
      pdata = ports[pnum]
405 48ce9fd9 Iustin Pop
      if len(pdata) > 1:
406 48ce9fd9 Iustin Pop
        txt = ", ".join(["%s/%s" % val for val in pdata])
407 48ce9fd9 Iustin Pop
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
408 48ce9fd9 Iustin Pop
409 48ce9fd9 Iustin Pop
    # highest used tcp port check
410 48ce9fd9 Iustin Pop
    if keys:
411 a8efbb40 Iustin Pop
      if keys[-1] > data.cluster.highest_used_port:
412 48ce9fd9 Iustin Pop
        result.append("Highest used port mismatch, saved %s, computed %s" %
413 a8efbb40 Iustin Pop
                      (data.cluster.highest_used_port, keys[-1]))
414 a8efbb40 Iustin Pop
415 3a26773f Iustin Pop
    if not data.nodes[data.cluster.master_node].master_candidate:
416 3a26773f Iustin Pop
      result.append("Master node is not a master candidate")
417 3a26773f Iustin Pop
418 4a89c54a Iustin Pop
    # master candidate checks
419 e623dbe3 Guido Trotter
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
420 ec0292f1 Iustin Pop
    if mc_now < mc_max:
421 ec0292f1 Iustin Pop
      result.append("Not enough master candidates: actual %d, target %d" %
422 ec0292f1 Iustin Pop
                    (mc_now, mc_max))
423 48ce9fd9 Iustin Pop
424 5bf07049 Iustin Pop
    # node checks
425 5bf07049 Iustin Pop
    for node in data.nodes.values():
426 5bf07049 Iustin Pop
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
427 5bf07049 Iustin Pop
        result.append("Node %s state is invalid: master_candidate=%s,"
428 5bf07049 Iustin Pop
                      " drain=%s, offline=%s" %
429 5bf07049 Iustin Pop
                      (node.name, node.master_candidate, node.drain,
430 5bf07049 Iustin Pop
                       node.offline))
431 5bf07049 Iustin Pop
432 4a89c54a Iustin Pop
    # drbd minors check
433 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
434 4a89c54a Iustin Pop
    for node, minor, instance_a, instance_b in duplicates:
435 4a89c54a Iustin Pop
      result.append("DRBD minor %d on node %s is assigned twice to instances"
436 4a89c54a Iustin Pop
                    " %s and %s" % (minor, node, instance_a, instance_b))
437 4a89c54a Iustin Pop
438 0ce8f948 Iustin Pop
    # IP checks
439 0ce8f948 Iustin Pop
    ips = { data.cluster.master_ip: ["cluster_ip"] }
440 0ce8f948 Iustin Pop
    def _helper(ip, name):
441 0ce8f948 Iustin Pop
      if ip in ips:
442 0ce8f948 Iustin Pop
        ips[ip].append(name)
443 0ce8f948 Iustin Pop
      else:
444 0ce8f948 Iustin Pop
        ips[ip] = [name]
445 0ce8f948 Iustin Pop
446 0ce8f948 Iustin Pop
    for node in data.nodes.values():
447 0ce8f948 Iustin Pop
      _helper(node.primary_ip, "node:%s/primary" % node.name)
448 0ce8f948 Iustin Pop
      if node.secondary_ip != node.primary_ip:
449 0ce8f948 Iustin Pop
        _helper(node.secondary_ip, "node:%s/secondary" % node.name)
450 0ce8f948 Iustin Pop
451 0ce8f948 Iustin Pop
    for ip, owners in ips.items():
452 0ce8f948 Iustin Pop
      if len(owners) > 1:
453 0ce8f948 Iustin Pop
        result.append("IP address %s is used by multiple owners: %s" %
454 0ce8f948 Iustin Pop
                      (ip, ", ".join(owners)))
455 a8083063 Iustin Pop
    return result
456 a8083063 Iustin Pop
457 4a89c54a Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
458 4a89c54a Iustin Pop
  def VerifyConfig(self):
459 4a89c54a Iustin Pop
    """Verify function.
460 4a89c54a Iustin Pop

461 4a89c54a Iustin Pop
    This is just a wrapper over L{_UnlockedVerifyConfig}.
462 4a89c54a Iustin Pop

463 4a89c54a Iustin Pop
    @rtype: list
464 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
465 4a89c54a Iustin Pop
        configuration errors
466 4a89c54a Iustin Pop

467 4a89c54a Iustin Pop
    """
468 4a89c54a Iustin Pop
    return self._UnlockedVerifyConfig()
469 4a89c54a Iustin Pop
470 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
471 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
472 a8083063 Iustin Pop

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

475 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
476 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
477 a8083063 Iustin Pop
    node.
478 a8083063 Iustin Pop

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

481 a8083063 Iustin Pop
    """
482 a8083063 Iustin Pop
    if disk.children:
483 a8083063 Iustin Pop
      for child in disk.children:
484 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
485 a8083063 Iustin Pop
486 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
487 a8083063 Iustin Pop
      return
488 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
489 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
490 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
491 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
492 3ecf6786 Iustin Pop
                                        node_name)
493 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
494 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
495 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
496 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
497 a8083063 Iustin Pop
                                        " for %s" % str(disk))
498 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
499 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
500 a8083063 Iustin Pop
      if pnode == node_name:
501 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
502 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
503 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
504 a8083063 Iustin Pop
    else:
505 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
506 a8083063 Iustin Pop
    return
507 a8083063 Iustin Pop
508 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
509 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
510 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
511 f78ede4e Guido Trotter

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

514 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
515 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
516 f78ede4e Guido Trotter
    node.
517 f78ede4e Guido Trotter

518 f78ede4e Guido Trotter
    """
519 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
520 f78ede4e Guido Trotter
521 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
522 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
523 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
524 b2fddf63 Iustin Pop

525 b2fddf63 Iustin Pop
    """
526 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
527 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
528 264bb3c5 Michael Hanselmann
529 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
530 264bb3c5 Michael Hanselmann
    self._WriteConfig()
531 264bb3c5 Michael Hanselmann
532 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
533 b2fddf63 Iustin Pop
  def GetPortList(self):
534 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
535 264bb3c5 Michael Hanselmann

536 264bb3c5 Michael Hanselmann
    """
537 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
538 264bb3c5 Michael Hanselmann
539 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
540 a8083063 Iustin Pop
  def AllocatePort(self):
541 a8083063 Iustin Pop
    """Allocate a port.
542 a8083063 Iustin Pop

543 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
544 b2fddf63 Iustin Pop
    default port range (and in this case we increase
545 b2fddf63 Iustin Pop
    highest_used_port).
546 a8083063 Iustin Pop

547 a8083063 Iustin Pop
    """
548 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
549 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
550 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
551 264bb3c5 Michael Hanselmann
    else:
552 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
553 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
554 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
555 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
556 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
557 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
558 a8083063 Iustin Pop
559 a8083063 Iustin Pop
    self._WriteConfig()
560 a8083063 Iustin Pop
    return port
561 a8083063 Iustin Pop
562 6d2e83d5 Iustin Pop
  def _UnlockedComputeDRBDMap(self):
563 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
564 a81c53c9 Iustin Pop

565 4a89c54a Iustin Pop
    @rtype: (dict, list)
566 c41eea6e Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
567 c41eea6e Iustin Pop
        the returned dict will have all the nodes in it (even if with
568 4a89c54a Iustin Pop
        an empty list), and a list of duplicates; if the duplicates
569 4a89c54a Iustin Pop
        list is not empty, the configuration is corrupted and its caller
570 4a89c54a Iustin Pop
        should raise an exception
571 a81c53c9 Iustin Pop

572 a81c53c9 Iustin Pop
    """
573 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
574 4a89c54a Iustin Pop
      duplicates = []
575 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
576 7c4d6c7b Michael Hanselmann
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
577 7c4d6c7b Michael Hanselmann
        for node, port in ((node_a, minor_a), (node_b, minor_b)):
578 4a89c54a Iustin Pop
          assert node in used, ("Node '%s' of instance '%s' not found"
579 4a89c54a Iustin Pop
                                " in node list" % (node, instance_name))
580 a81c53c9 Iustin Pop
          if port in used[node]:
581 4a89c54a Iustin Pop
            duplicates.append((node, port, instance_name, used[node][port]))
582 4a89c54a Iustin Pop
          else:
583 4a89c54a Iustin Pop
            used[node][port] = instance_name
584 a81c53c9 Iustin Pop
      if disk.children:
585 a81c53c9 Iustin Pop
        for child in disk.children:
586 4a89c54a Iustin Pop
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
587 4a89c54a Iustin Pop
      return duplicates
588 a81c53c9 Iustin Pop
589 4a89c54a Iustin Pop
    duplicates = []
590 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
591 79b26a7a Iustin Pop
    for instance in self._config_data.instances.itervalues():
592 79b26a7a Iustin Pop
      for disk in instance.disks:
593 79b26a7a Iustin Pop
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
594 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
595 79b26a7a Iustin Pop
      if minor in my_dict[node] and my_dict[node][minor] != instance:
596 4a89c54a Iustin Pop
        duplicates.append((node, minor, instance, my_dict[node][minor]))
597 4a89c54a Iustin Pop
      else:
598 4a89c54a Iustin Pop
        my_dict[node][minor] = instance
599 4a89c54a Iustin Pop
    return my_dict, duplicates
600 a81c53c9 Iustin Pop
601 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
602 6d2e83d5 Iustin Pop
  def ComputeDRBDMap(self):
603 6d2e83d5 Iustin Pop
    """Compute the used DRBD minor/nodes.
604 6d2e83d5 Iustin Pop

605 6d2e83d5 Iustin Pop
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
606 6d2e83d5 Iustin Pop

607 6d2e83d5 Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
608 6d2e83d5 Iustin Pop
        the returned dict will have all the nodes in it (even if with
609 6d2e83d5 Iustin Pop
        an empty list).
610 6d2e83d5 Iustin Pop

611 6d2e83d5 Iustin Pop
    """
612 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
613 4a89c54a Iustin Pop
    if duplicates:
614 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
615 4a89c54a Iustin Pop
                                      str(duplicates))
616 4a89c54a Iustin Pop
    return d_map
617 6d2e83d5 Iustin Pop
618 6d2e83d5 Iustin Pop
  @locking.ssynchronized(_config_lock)
619 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
620 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
621 a81c53c9 Iustin Pop

622 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
623 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
624 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
625 a81c53c9 Iustin Pop
    order as the passed nodes.
626 a81c53c9 Iustin Pop

627 32388e6d Iustin Pop
    @type instance: string
628 32388e6d Iustin Pop
    @param instance: the instance for which we allocate minors
629 32388e6d Iustin Pop

630 a81c53c9 Iustin Pop
    """
631 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
632 4a89c54a Iustin Pop
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
633 32388e6d Iustin Pop
634 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
635 4a89c54a Iustin Pop
    if duplicates:
636 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
637 4a89c54a Iustin Pop
                                      str(duplicates))
638 a81c53c9 Iustin Pop
    result = []
639 a81c53c9 Iustin Pop
    for nname in nodes:
640 a81c53c9 Iustin Pop
      ndata = d_map[nname]
641 a81c53c9 Iustin Pop
      if not ndata:
642 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
643 a81c53c9 Iustin Pop
        result.append(0)
644 a81c53c9 Iustin Pop
        ndata[0] = instance
645 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
646 a81c53c9 Iustin Pop
        continue
647 a81c53c9 Iustin Pop
      keys = ndata.keys()
648 a81c53c9 Iustin Pop
      keys.sort()
649 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
650 a81c53c9 Iustin Pop
      if ffree is None:
651 a81c53c9 Iustin Pop
        # return the next minor
652 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
653 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
654 a81c53c9 Iustin Pop
      else:
655 a81c53c9 Iustin Pop
        minor = ffree
656 4a89c54a Iustin Pop
      # double-check minor against current instances
657 4a89c54a Iustin Pop
      assert minor not in d_map[nname], \
658 4a89c54a Iustin Pop
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
659 4a89c54a Iustin Pop
              " already allocated to instance %s" %
660 4a89c54a Iustin Pop
              (minor, nname, d_map[nname][minor]))
661 a81c53c9 Iustin Pop
      ndata[minor] = instance
662 4a89c54a Iustin Pop
      # double-check minor against reservation
663 4a89c54a Iustin Pop
      r_key = (nname, minor)
664 4a89c54a Iustin Pop
      assert r_key not in self._temporary_drbds, \
665 4a89c54a Iustin Pop
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
666 4a89c54a Iustin Pop
              " reserved for instance %s" %
667 4a89c54a Iustin Pop
              (minor, nname, self._temporary_drbds[r_key]))
668 4a89c54a Iustin Pop
      self._temporary_drbds[r_key] = instance
669 4a89c54a Iustin Pop
      result.append(minor)
670 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
671 a81c53c9 Iustin Pop
                  nodes, result)
672 a81c53c9 Iustin Pop
    return result
673 a81c53c9 Iustin Pop
674 61cf6b5e Iustin Pop
  def _UnlockedReleaseDRBDMinors(self, instance):
675 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
676 a81c53c9 Iustin Pop

677 a81c53c9 Iustin Pop
    @type instance: string
678 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
679 a81c53c9 Iustin Pop
                     released
680 a81c53c9 Iustin Pop

681 a81c53c9 Iustin Pop
    """
682 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
683 32388e6d Iustin Pop
           "Invalid argument passed to ReleaseDRBDMinors"
684 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
685 a81c53c9 Iustin Pop
      if name == instance:
686 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
687 a81c53c9 Iustin Pop
688 61cf6b5e Iustin Pop
  @locking.ssynchronized(_config_lock)
689 61cf6b5e Iustin Pop
  def ReleaseDRBDMinors(self, instance):
690 61cf6b5e Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
691 61cf6b5e Iustin Pop

692 61cf6b5e Iustin Pop
    This should be called on the error paths, on the success paths
693 61cf6b5e Iustin Pop
    it's automatically called by the ConfigWriter add and update
694 61cf6b5e Iustin Pop
    functions.
695 61cf6b5e Iustin Pop

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

698 61cf6b5e Iustin Pop
    @type instance: string
699 61cf6b5e Iustin Pop
    @param instance: the instance for which temporary minors should be
700 61cf6b5e Iustin Pop
                     released
701 61cf6b5e Iustin Pop

702 61cf6b5e Iustin Pop
    """
703 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance)
704 61cf6b5e Iustin Pop
705 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
706 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
707 4a8b186a Michael Hanselmann
    """Get the configuration version.
708 4a8b186a Michael Hanselmann

709 4a8b186a Michael Hanselmann
    @return: Config version
710 4a8b186a Michael Hanselmann

711 4a8b186a Michael Hanselmann
    """
712 4a8b186a Michael Hanselmann
    return self._config_data.version
713 4a8b186a Michael Hanselmann
714 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
715 4a8b186a Michael Hanselmann
  def GetClusterName(self):
716 4a8b186a Michael Hanselmann
    """Get cluster name.
717 4a8b186a Michael Hanselmann

718 4a8b186a Michael Hanselmann
    @return: Cluster name
719 4a8b186a Michael Hanselmann

720 4a8b186a Michael Hanselmann
    """
721 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
722 4a8b186a Michael Hanselmann
723 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
724 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
725 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
726 4a8b186a Michael Hanselmann

727 4a8b186a Michael Hanselmann
    @return: Master hostname
728 4a8b186a Michael Hanselmann

729 4a8b186a Michael Hanselmann
    """
730 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
731 4a8b186a Michael Hanselmann
732 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
733 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
734 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
735 4a8b186a Michael Hanselmann

736 4a8b186a Michael Hanselmann
    @return: Master IP
737 4a8b186a Michael Hanselmann

738 4a8b186a Michael Hanselmann
    """
739 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
740 4a8b186a Michael Hanselmann
741 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
742 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
743 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
744 4a8b186a Michael Hanselmann

745 4a8b186a Michael Hanselmann
    """
746 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
747 4a8b186a Michael Hanselmann
748 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
749 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
750 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
751 4a8b186a Michael Hanselmann

752 4a8b186a Michael Hanselmann
    """
753 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
754 4a8b186a Michael Hanselmann
755 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
756 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
757 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
758 4a8b186a Michael Hanselmann

759 4a8b186a Michael Hanselmann
    """
760 066f465d Guido Trotter
    return self._config_data.cluster.enabled_hypervisors[0]
761 4a8b186a Michael Hanselmann
762 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
763 a8083063 Iustin Pop
  def GetHostKey(self):
764 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
765 a8083063 Iustin Pop

766 c41eea6e Iustin Pop
    @rtype: string
767 c41eea6e Iustin Pop
    @return: the rsa hostkey
768 a8083063 Iustin Pop

769 a8083063 Iustin Pop
    """
770 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
771 a8083063 Iustin Pop
772 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
773 0debfb35 Guido Trotter
  def AddInstance(self, instance, ec_id):
774 a8083063 Iustin Pop
    """Add an instance to the config.
775 a8083063 Iustin Pop

776 a8083063 Iustin Pop
    This should be used after creating a new instance.
777 a8083063 Iustin Pop

778 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
779 c41eea6e Iustin Pop
    @param instance: the instance object
780 c41eea6e Iustin Pop

781 a8083063 Iustin Pop
    """
782 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
783 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
784 a8083063 Iustin Pop
785 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
786 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
787 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
788 923b1523 Iustin Pop
789 e4640214 Guido Trotter
    all_macs = self._AllMACs()
790 e4640214 Guido Trotter
    for nic in instance.nics:
791 e4640214 Guido Trotter
      if nic.mac in all_macs:
792 e4640214 Guido Trotter
        raise errors.ConfigurationError("Cannot add instance %s:"
793 430b923c Iustin Pop
                                        " MAC address '%s' already in use." %
794 430b923c Iustin Pop
                                        (instance.name, nic.mac))
795 430b923c Iustin Pop
796 0debfb35 Guido Trotter
    self._EnsureUUID(instance, ec_id)
797 e4640214 Guido Trotter
798 b989e85d Iustin Pop
    instance.serial_no = 1
799 d693c864 Iustin Pop
    instance.ctime = instance.mtime = time.time()
800 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
801 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
802 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance.name)
803 a8083063 Iustin Pop
    self._WriteConfig()
804 a8083063 Iustin Pop
805 0debfb35 Guido Trotter
  def _EnsureUUID(self, item, ec_id):
806 430b923c Iustin Pop
    """Ensures a given object has a valid UUID.
807 430b923c Iustin Pop

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

811 430b923c Iustin Pop
    """
812 430b923c Iustin Pop
    if not item.uuid:
813 4fae38c5 Guido Trotter
      item.uuid = self._GenerateUniqueID(ec_id)
814 430b923c Iustin Pop
    elif item.uuid in self._AllIDs(temporary=True):
815 430b923c Iustin Pop
      raise errors.ConfigurationError("Cannot add '%s': UUID already in use" %
816 430b923c Iustin Pop
                                      (item.name, item.uuid))
817 430b923c Iustin Pop
818 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
819 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
820 a8083063 Iustin Pop

821 a8083063 Iustin Pop
    """
822 0d68c45d Iustin Pop
    assert isinstance(status, bool), \
823 0d68c45d Iustin Pop
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
824 a8083063 Iustin Pop
825 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
826 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
827 3ecf6786 Iustin Pop
                                      instance_name)
828 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
829 0d68c45d Iustin Pop
    if instance.admin_up != status:
830 0d68c45d Iustin Pop
      instance.admin_up = status
831 b989e85d Iustin Pop
      instance.serial_no += 1
832 d693c864 Iustin Pop
      instance.mtime = time.time()
833 455a3445 Iustin Pop
      self._WriteConfig()
834 a8083063 Iustin Pop
835 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
836 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
837 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
838 6a408fb2 Iustin Pop

839 6a408fb2 Iustin Pop
    """
840 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, True)
841 6a408fb2 Iustin Pop
842 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
843 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
844 a8083063 Iustin Pop
    """Remove the instance from the configuration.
845 a8083063 Iustin Pop

846 a8083063 Iustin Pop
    """
847 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
848 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
849 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
850 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
851 a8083063 Iustin Pop
    self._WriteConfig()
852 a8083063 Iustin Pop
853 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
854 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
855 fc95f88f Iustin Pop
    """Rename an instance.
856 fc95f88f Iustin Pop

857 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
858 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
859 fc95f88f Iustin Pop
    rename.
860 fc95f88f Iustin Pop

861 fc95f88f Iustin Pop
    """
862 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
863 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
864 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
865 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
866 fc95f88f Iustin Pop
    inst.name = new_name
867 b23c4333 Manuel Franceschini
868 b23c4333 Manuel Franceschini
    for disk in inst.disks:
869 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
870 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
871 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
872 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
873 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
874 b23c4333 Manuel Franceschini
                                                           inst.name,
875 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
876 b23c4333 Manuel Franceschini
877 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
878 fc95f88f Iustin Pop
    self._WriteConfig()
879 fc95f88f Iustin Pop
880 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
881 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
882 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
883 a8083063 Iustin Pop

884 a8083063 Iustin Pop
    """
885 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, False)
886 a8083063 Iustin Pop
887 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
888 94bbfece Iustin Pop
    """Get the list of instances.
889 94bbfece Iustin Pop

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

892 94bbfece Iustin Pop
    """
893 94bbfece Iustin Pop
    return self._config_data.instances.keys()
894 94bbfece Iustin Pop
895 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
896 a8083063 Iustin Pop
  def GetInstanceList(self):
897 a8083063 Iustin Pop
    """Get the list of instances.
898 a8083063 Iustin Pop

899 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
900 c41eea6e Iustin Pop
        'instance1.example.com']
901 a8083063 Iustin Pop

902 a8083063 Iustin Pop
    """
903 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
904 a8083063 Iustin Pop
905 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
906 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
907 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
908 a8083063 Iustin Pop

909 a8083063 Iustin Pop
    """
910 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
911 bcdf16d7 Guido Trotter
                                    self._config_data.instances.keys(),
912 bcdf16d7 Guido Trotter
                                    case_sensitive=False)
913 a8083063 Iustin Pop
914 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
915 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
916 94bbfece Iustin Pop

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

919 94bbfece Iustin Pop
    """
920 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
921 94bbfece Iustin Pop
      return None
922 94bbfece Iustin Pop
923 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
924 94bbfece Iustin Pop
925 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
926 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
927 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
928 a8083063 Iustin Pop

929 5bbd3f7f Michael Hanselmann
    It takes the information from the configuration file. Other information of
930 a8083063 Iustin Pop
    an instance are taken from the live systems.
931 a8083063 Iustin Pop

932 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
933 c41eea6e Iustin Pop
        I{instance1.example.com}
934 a8083063 Iustin Pop

935 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
936 c41eea6e Iustin Pop
    @return: the instance object
937 a8083063 Iustin Pop

938 a8083063 Iustin Pop
    """
939 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
940 a8083063 Iustin Pop
941 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
942 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
943 0b2de758 Iustin Pop
    """Get the configuration of all instances.
944 0b2de758 Iustin Pop

945 0b2de758 Iustin Pop
    @rtype: dict
946 5fcc718f Iustin Pop
    @return: dict of (instance, instance_info), where instance_info is what
947 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
948 0b2de758 Iustin Pop

949 0b2de758 Iustin Pop
    """
950 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
951 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
952 0b2de758 Iustin Pop
    return my_dict
953 0b2de758 Iustin Pop
954 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
955 0debfb35 Guido Trotter
  def AddNode(self, node, ec_id):
956 a8083063 Iustin Pop
    """Add a node to the configuration.
957 a8083063 Iustin Pop

958 c41eea6e Iustin Pop
    @type node: L{objects.Node}
959 c41eea6e Iustin Pop
    @param node: a Node instance
960 a8083063 Iustin Pop

961 a8083063 Iustin Pop
    """
962 099c52ad Iustin Pop
    logging.info("Adding node %s to configuration", node.name)
963 d8470559 Michael Hanselmann
964 0debfb35 Guido Trotter
    self._EnsureUUID(node, ec_id)
965 430b923c Iustin Pop
966 b989e85d Iustin Pop
    node.serial_no = 1
967 d693c864 Iustin Pop
    node.ctime = node.mtime = time.time()
968 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
969 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
970 a8083063 Iustin Pop
    self._WriteConfig()
971 a8083063 Iustin Pop
972 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
973 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
974 a8083063 Iustin Pop
    """Remove a node from the configuration.
975 a8083063 Iustin Pop

976 a8083063 Iustin Pop
    """
977 099c52ad Iustin Pop
    logging.info("Removing node %s from configuration", node_name)
978 d8470559 Michael Hanselmann
979 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
980 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
981 a8083063 Iustin Pop
982 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
983 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
984 a8083063 Iustin Pop
    self._WriteConfig()
985 a8083063 Iustin Pop
986 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
987 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
988 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
989 a8083063 Iustin Pop

990 a8083063 Iustin Pop
    """
991 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
992 bcdf16d7 Guido Trotter
                                    self._config_data.nodes.keys(),
993 bcdf16d7 Guido Trotter
                                    case_sensitive=False)
994 a8083063 Iustin Pop
995 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
996 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
997 a8083063 Iustin Pop

998 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
999 c41eea6e Iustin Pop
    held.
1000 f78ede4e Guido Trotter

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

1003 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
1004 c41eea6e Iustin Pop
    @return: the node object
1005 a8083063 Iustin Pop

1006 a8083063 Iustin Pop
    """
1007 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
1008 a8083063 Iustin Pop
      return None
1009 a8083063 Iustin Pop
1010 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
1011 a8083063 Iustin Pop
1012 f78ede4e Guido Trotter
1013 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1014 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
1015 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
1016 f78ede4e Guido Trotter

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

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

1021 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
1022 c41eea6e Iustin Pop
    @return: the node object
1023 f78ede4e Guido Trotter

1024 f78ede4e Guido Trotter
    """
1025 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
1026 f78ede4e Guido Trotter
1027 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
1028 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
1029 a8083063 Iustin Pop

1030 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
1031 c41eea6e Iustin Pop
    held.
1032 c41eea6e Iustin Pop

1033 c41eea6e Iustin Pop
    @rtype: list
1034 f78ede4e Guido Trotter

1035 a8083063 Iustin Pop
    """
1036 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
1037 a8083063 Iustin Pop
1038 f78ede4e Guido Trotter
1039 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1040 f78ede4e Guido Trotter
  def GetNodeList(self):
1041 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
1042 f78ede4e Guido Trotter

1043 f78ede4e Guido Trotter
    """
1044 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
1045 f78ede4e Guido Trotter
1046 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1047 94a02bb5 Iustin Pop
  def GetOnlineNodeList(self):
1048 94a02bb5 Iustin Pop
    """Return the list of nodes which are online.
1049 94a02bb5 Iustin Pop

1050 94a02bb5 Iustin Pop
    """
1051 94a02bb5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
1052 94a02bb5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
1053 94a02bb5 Iustin Pop
    return [node.name for node in all_nodes if not node.offline]
1054 94a02bb5 Iustin Pop
1055 94a02bb5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1056 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
1057 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
1058 d65e5776 Iustin Pop

1059 d65e5776 Iustin Pop
    @rtype: dict
1060 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
1061 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
1062 d65e5776 Iustin Pop

1063 d65e5776 Iustin Pop
    """
1064 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
1065 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
1066 d65e5776 Iustin Pop
    return my_dict
1067 d65e5776 Iustin Pop
1068 23f06b2b Iustin Pop
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1069 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
1070 ec0292f1 Iustin Pop

1071 23f06b2b Iustin Pop
    @type exceptions: list
1072 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1073 ec0292f1 Iustin Pop
    @rtype: tuple
1074 e623dbe3 Guido Trotter
    @return: tuple of (current, desired and possible, possible)
1075 ec0292f1 Iustin Pop

1076 ec0292f1 Iustin Pop
    """
1077 e623dbe3 Guido Trotter
    mc_now = mc_should = mc_max = 0
1078 23f06b2b Iustin Pop
    for node in self._config_data.nodes.values():
1079 23f06b2b Iustin Pop
      if exceptions and node.name in exceptions:
1080 23f06b2b Iustin Pop
        continue
1081 5bf07049 Iustin Pop
      if not (node.offline or node.drained):
1082 ec0292f1 Iustin Pop
        mc_max += 1
1083 ec0292f1 Iustin Pop
      if node.master_candidate:
1084 ec0292f1 Iustin Pop
        mc_now += 1
1085 e623dbe3 Guido Trotter
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1086 e623dbe3 Guido Trotter
    return (mc_now, mc_should, mc_max)
1087 ec0292f1 Iustin Pop
1088 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1089 23f06b2b Iustin Pop
  def GetMasterCandidateStats(self, exceptions=None):
1090 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
1091 ec0292f1 Iustin Pop

1092 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1093 ec0292f1 Iustin Pop

1094 23f06b2b Iustin Pop
    @type exceptions: list
1095 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1096 ec0292f1 Iustin Pop
    @rtype: tuple
1097 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
1098 ec0292f1 Iustin Pop

1099 ec0292f1 Iustin Pop
    """
1100 23f06b2b Iustin Pop
    return self._UnlockedGetMasterCandidateStats(exceptions)
1101 ec0292f1 Iustin Pop
1102 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
1103 44485f49 Guido Trotter
  def MaintainCandidatePool(self, exceptions):
1104 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
1105 ec0292f1 Iustin Pop

1106 44485f49 Guido Trotter
    @type exceptions: list
1107 44485f49 Guido Trotter
    @param exceptions: if passed, list of nodes that should be ignored
1108 ec0292f1 Iustin Pop
    @rtype: list
1109 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
1110 ec0292f1 Iustin Pop

1111 ec0292f1 Iustin Pop
    """
1112 44485f49 Guido Trotter
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1113 ec0292f1 Iustin Pop
    mod_list = []
1114 ec0292f1 Iustin Pop
    if mc_now < mc_max:
1115 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
1116 ec0292f1 Iustin Pop
      random.shuffle(node_list)
1117 ec0292f1 Iustin Pop
      for name in node_list:
1118 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
1119 ec0292f1 Iustin Pop
          break
1120 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
1121 44485f49 Guido Trotter
        if (node.master_candidate or node.offline or node.drained or
1122 44485f49 Guido Trotter
            node.name in exceptions):
1123 ec0292f1 Iustin Pop
          continue
1124 ee513a66 Iustin Pop
        mod_list.append(node)
1125 ec0292f1 Iustin Pop
        node.master_candidate = True
1126 ec0292f1 Iustin Pop
        node.serial_no += 1
1127 ec0292f1 Iustin Pop
        mc_now += 1
1128 ec0292f1 Iustin Pop
      if mc_now != mc_max:
1129 ec0292f1 Iustin Pop
        # this should not happen
1130 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1131 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1132 ec0292f1 Iustin Pop
      if mod_list:
1133 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
1134 ec0292f1 Iustin Pop
        self._WriteConfig()
1135 ec0292f1 Iustin Pop
1136 ec0292f1 Iustin Pop
    return mod_list
1137 ec0292f1 Iustin Pop
1138 a8083063 Iustin Pop
  def _BumpSerialNo(self):
1139 a8083063 Iustin Pop
    """Bump up the serial number of the config.
1140 a8083063 Iustin Pop

1141 a8083063 Iustin Pop
    """
1142 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
1143 d693c864 Iustin Pop
    self._config_data.mtime = time.time()
1144 a8083063 Iustin Pop
1145 76d5d3a3 Iustin Pop
  def _AllUUIDObjects(self):
1146 76d5d3a3 Iustin Pop
    """Returns all objects with uuid attributes.
1147 76d5d3a3 Iustin Pop

1148 76d5d3a3 Iustin Pop
    """
1149 76d5d3a3 Iustin Pop
    return (self._config_data.instances.values() +
1150 76d5d3a3 Iustin Pop
            self._config_data.nodes.values() +
1151 76d5d3a3 Iustin Pop
            [self._config_data.cluster])
1152 76d5d3a3 Iustin Pop
1153 a8083063 Iustin Pop
  def _OpenConfig(self):
1154 a8083063 Iustin Pop
    """Read the config data from disk.
1155 a8083063 Iustin Pop

1156 a8083063 Iustin Pop
    """
1157 13998ef2 Michael Hanselmann
    raw_data = utils.ReadFile(self._cfg_file)
1158 13998ef2 Michael Hanselmann
1159 a8083063 Iustin Pop
    try:
1160 13998ef2 Michael Hanselmann
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
1161 13998ef2 Michael Hanselmann
    except Exception, err:
1162 13998ef2 Michael Hanselmann
      raise errors.ConfigurationError(err)
1163 5b263ed7 Michael Hanselmann
1164 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
1165 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
1166 5b263ed7 Michael Hanselmann
1167 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
1168 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
1169 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
1170 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
1171 90d726a8 Iustin Pop
1172 90d726a8 Iustin Pop
    # Upgrade configuration if needed
1173 90d726a8 Iustin Pop
    data.UpgradeConfig()
1174 90d726a8 Iustin Pop
1175 a8083063 Iustin Pop
    self._config_data = data
1176 3c7f6c44 Iustin Pop
    # reset the last serial as -1 so that the next write will cause
1177 0779e3aa Iustin Pop
    # ssconf update
1178 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
1179 a8083063 Iustin Pop
1180 76d5d3a3 Iustin Pop
    # And finally run our (custom) config upgrade sequence
1181 76d5d3a3 Iustin Pop
    self._UpgradeConfig()
1182 76d5d3a3 Iustin Pop
1183 76d5d3a3 Iustin Pop
  def _UpgradeConfig(self):
1184 76d5d3a3 Iustin Pop
    """Run upgrade steps that cannot be done purely in the objects.
1185 76d5d3a3 Iustin Pop

1186 76d5d3a3 Iustin Pop
    This is because some data elements need uniqueness across the
1187 76d5d3a3 Iustin Pop
    whole configuration, etc.
1188 76d5d3a3 Iustin Pop

1189 76d5d3a3 Iustin Pop
    @warning: this function will call L{_WriteConfig()}, so it needs
1190 76d5d3a3 Iustin Pop
        to either be called with the lock held or from a safe place
1191 76d5d3a3 Iustin Pop
        (the constructor)
1192 76d5d3a3 Iustin Pop

1193 76d5d3a3 Iustin Pop
    """
1194 76d5d3a3 Iustin Pop
    modified = False
1195 76d5d3a3 Iustin Pop
    for item in self._AllUUIDObjects():
1196 76d5d3a3 Iustin Pop
      if item.uuid is None:
1197 4fae38c5 Guido Trotter
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
1198 76d5d3a3 Iustin Pop
        modified = True
1199 76d5d3a3 Iustin Pop
    if modified:
1200 76d5d3a3 Iustin Pop
      self._WriteConfig()
1201 4fae38c5 Guido Trotter
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
1202 4fae38c5 Guido Trotter
      # only called at config init time, without the lock held
1203 4fae38c5 Guido Trotter
      self.DropECReservations(_UPGRADE_CONFIG_JID)
1204 4fae38c5 Guido Trotter
1205 76d5d3a3 Iustin Pop
1206 a4eae71f Michael Hanselmann
  def _DistributeConfig(self, feedback_fn):
1207 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
1208 a8083063 Iustin Pop

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

1212 a8083063 Iustin Pop
    """
1213 a8083063 Iustin Pop
    if self._offline:
1214 a8083063 Iustin Pop
      return True
1215 a4eae71f Michael Hanselmann
1216 a8083063 Iustin Pop
    bad = False
1217 a8083063 Iustin Pop
1218 6a5b8b4b Iustin Pop
    node_list = []
1219 6a5b8b4b Iustin Pop
    addr_list = []
1220 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
1221 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
1222 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
1223 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
1224 6b294c53 Iustin Pop
    # in between
1225 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
1226 6a5b8b4b Iustin Pop
      if node_name == myhostname:
1227 6a5b8b4b Iustin Pop
        continue
1228 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
1229 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
1230 6a5b8b4b Iustin Pop
        continue
1231 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
1232 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
1233 6b294c53 Iustin Pop
1234 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1235 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
1236 1b54fc6c Guido Trotter
    for to_node, to_result in result.items():
1237 3cebe102 Michael Hanselmann
      msg = to_result.fail_msg
1238 1b54fc6c Guido Trotter
      if msg:
1239 1b54fc6c Guido Trotter
        msg = ("Copy of file %s to node %s failed: %s" %
1240 dd7db360 Iustin Pop
               (self._cfg_file, to_node, msg))
1241 1b54fc6c Guido Trotter
        logging.error(msg)
1242 a4eae71f Michael Hanselmann
1243 a4eae71f Michael Hanselmann
        if feedback_fn:
1244 a4eae71f Michael Hanselmann
          feedback_fn(msg)
1245 a4eae71f Michael Hanselmann
1246 a8083063 Iustin Pop
        bad = True
1247 a4eae71f Michael Hanselmann
1248 a8083063 Iustin Pop
    return not bad
1249 a8083063 Iustin Pop
1250 a4eae71f Michael Hanselmann
  def _WriteConfig(self, destination=None, feedback_fn=None):
1251 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
1252 a8083063 Iustin Pop

1253 a8083063 Iustin Pop
    """
1254 a4eae71f Michael Hanselmann
    assert feedback_fn is None or callable(feedback_fn)
1255 a4eae71f Michael Hanselmann
1256 d2231b8c Iustin Pop
    # Warn on config errors, but don't abort the save - the
1257 d2231b8c Iustin Pop
    # configuration has already been modified, and we can't revert;
1258 d2231b8c Iustin Pop
    # the best we can do is to warn the user and save as is, leaving
1259 d2231b8c Iustin Pop
    # recovery to the user
1260 4a89c54a Iustin Pop
    config_errors = self._UnlockedVerifyConfig()
1261 4a89c54a Iustin Pop
    if config_errors:
1262 d2231b8c Iustin Pop
      errmsg = ("Configuration data is not consistent: %s" %
1263 d2231b8c Iustin Pop
                (", ".join(config_errors)))
1264 d2231b8c Iustin Pop
      logging.critical(errmsg)
1265 d2231b8c Iustin Pop
      if feedback_fn:
1266 d2231b8c Iustin Pop
        feedback_fn(errmsg)
1267 d2231b8c Iustin Pop
1268 a8083063 Iustin Pop
    if destination is None:
1269 a8083063 Iustin Pop
      destination = self._cfg_file
1270 a8083063 Iustin Pop
    self._BumpSerialNo()
1271 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
1272 13998ef2 Michael Hanselmann
1273 13998ef2 Michael Hanselmann
    utils.WriteFile(destination, data=txt)
1274 13998ef2 Michael Hanselmann
1275 14e15659 Iustin Pop
    self.write_count += 1
1276 3d3a04bc Iustin Pop
1277 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
1278 a4eae71f Michael Hanselmann
    self._DistributeConfig(feedback_fn)
1279 a8083063 Iustin Pop
1280 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
1281 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
1282 d9a855f1 Michael Hanselmann
      if not self._offline:
1283 cd34faf2 Michael Hanselmann
        result = rpc.RpcRunner.call_write_ssconf_files(
1284 e1e75d00 Iustin Pop
          self._UnlockedGetNodeList(),
1285 e1e75d00 Iustin Pop
          self._UnlockedGetSsconfValues())
1286 a4eae71f Michael Hanselmann
1287 e1e75d00 Iustin Pop
        for nname, nresu in result.items():
1288 3cebe102 Michael Hanselmann
          msg = nresu.fail_msg
1289 e1e75d00 Iustin Pop
          if msg:
1290 a4eae71f Michael Hanselmann
            errmsg = ("Error while uploading ssconf files to"
1291 a4eae71f Michael Hanselmann
                      " node %s: %s" % (nname, msg))
1292 a4eae71f Michael Hanselmann
            logging.warning(errmsg)
1293 a4eae71f Michael Hanselmann
1294 a4eae71f Michael Hanselmann
            if feedback_fn:
1295 a4eae71f Michael Hanselmann
              feedback_fn(errmsg)
1296 a4eae71f Michael Hanselmann
1297 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
1298 54d1a06e Michael Hanselmann
1299 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
1300 054596f0 Iustin Pop
    """Return the values needed by ssconf.
1301 054596f0 Iustin Pop

1302 054596f0 Iustin Pop
    @rtype: dict
1303 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
1304 054596f0 Iustin Pop
        associated value
1305 054596f0 Iustin Pop

1306 054596f0 Iustin Pop
    """
1307 a3316e4a Iustin Pop
    fn = "\n".join
1308 81a49123 Iustin Pop
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1309 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1310 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1311 c3029d0a Luca Bigliardi
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
1312 5909fb97 Luca Bigliardi
                    for ninfo in node_info]
1313 c3029d0a Luca Bigliardi
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
1314 5909fb97 Luca Bigliardi
                    for ninfo in node_info]
1315 a3316e4a Iustin Pop
1316 81a49123 Iustin Pop
    instance_data = fn(instance_names)
1317 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
1318 81a49123 Iustin Pop
    on_data = fn(node.name for node in node_info if not node.offline)
1319 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1320 8113a52e Luca Bigliardi
    mc_ips_data = fn(node.primary_ip for node in node_info
1321 8113a52e Luca Bigliardi
                     if node.master_candidate)
1322 a3316e4a Iustin Pop
    node_data = fn(node_names)
1323 f9780ccd Luca Bigliardi
    node_pri_ips_data = fn(node_pri_ips)
1324 f9780ccd Luca Bigliardi
    node_snd_ips_data = fn(node_snd_ips)
1325 f56618e0 Iustin Pop
1326 054596f0 Iustin Pop
    cluster = self._config_data.cluster
1327 5d60b3bd Iustin Pop
    cluster_tags = fn(cluster.GetTags())
1328 03d1dba2 Michael Hanselmann
    return {
1329 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1330 5d60b3bd Iustin Pop
      constants.SS_CLUSTER_TAGS: cluster_tags,
1331 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1332 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
1333 8113a52e Luca Bigliardi
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
1334 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
1335 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1336 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
1337 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
1338 f9780ccd Luca Bigliardi
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
1339 f9780ccd Luca Bigliardi
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
1340 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
1341 81a49123 Iustin Pop
      constants.SS_ONLINE_NODES: on_data,
1342 81a49123 Iustin Pop
      constants.SS_INSTANCE_LIST: instance_data,
1343 8a113c7a Iustin Pop
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1344 03d1dba2 Michael Hanselmann
      }
1345 03d1dba2 Michael Hanselmann
1346 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1347 a8083063 Iustin Pop
  def GetVGName(self):
1348 a8083063 Iustin Pop
    """Return the volume group name.
1349 a8083063 Iustin Pop

1350 a8083063 Iustin Pop
    """
1351 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1352 a8083063 Iustin Pop
1353 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1354 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1355 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1356 89ff8e15 Manuel Franceschini

1357 89ff8e15 Manuel Franceschini
    """
1358 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1359 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1360 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1361 89ff8e15 Manuel Franceschini
1362 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1363 a8083063 Iustin Pop
  def GetMACPrefix(self):
1364 a8083063 Iustin Pop
    """Return the mac prefix.
1365 a8083063 Iustin Pop

1366 a8083063 Iustin Pop
    """
1367 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1368 62779dd0 Iustin Pop
1369 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1370 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1371 5bbd3f7f Michael Hanselmann
    """Returns information about the cluster
1372 62779dd0 Iustin Pop

1373 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1374 c41eea6e Iustin Pop
    @return: the cluster object
1375 62779dd0 Iustin Pop

1376 62779dd0 Iustin Pop
    """
1377 62779dd0 Iustin Pop
    return self._config_data.cluster
1378 e00fb268 Iustin Pop
1379 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1380 a4eae71f Michael Hanselmann
  def Update(self, target, feedback_fn):
1381 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1382 e00fb268 Iustin Pop

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

1389 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1390 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1391 c41eea6e Iustin Pop
        the cluster
1392 a4eae71f Michael Hanselmann
    @param feedback_fn: Callable feedback function
1393 c41eea6e Iustin Pop

1394 e00fb268 Iustin Pop
    """
1395 e00fb268 Iustin Pop
    if self._config_data is None:
1396 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1397 3ecf6786 Iustin Pop
                                   " cannot save.")
1398 f34901f8 Iustin Pop
    update_serial = False
1399 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1400 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1401 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1402 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1403 f34901f8 Iustin Pop
      update_serial = True
1404 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1405 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1406 e00fb268 Iustin Pop
    else:
1407 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1408 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1409 e00fb268 Iustin Pop
    if not test:
1410 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1411 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1412 f34901f8 Iustin Pop
    target.serial_no += 1
1413 d693c864 Iustin Pop
    target.mtime = now = time.time()
1414 f34901f8 Iustin Pop
1415 cff4c037 Iustin Pop
    if update_serial:
1416 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1417 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1418 d693c864 Iustin Pop
      self._config_data.cluster.mtime = now
1419 b989e85d Iustin Pop
1420 61cf6b5e Iustin Pop
    if isinstance(target, objects.Instance):
1421 61cf6b5e Iustin Pop
      self._UnlockedReleaseDRBDMinors(target.name)
1422 61cf6b5e Iustin Pop
1423 a4eae71f Michael Hanselmann
    self._WriteConfig(feedback_fn=feedback_fn)
1424 73064714 Guido Trotter
1425 73064714 Guido Trotter
  @locking.ssynchronized(_config_lock)
1426 73064714 Guido Trotter
  def DropECReservations(self, ec_id):
1427 73064714 Guido Trotter
    """Drop per-execution-context reservations
1428 73064714 Guido Trotter

1429 73064714 Guido Trotter
    """
1430 4fae38c5 Guido Trotter
    self._temporary_ids.DropECReservations(ec_id)
1431 36b66e6e Guido Trotter
    self._temporary_macs.DropECReservations(ec_id)