Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 0b5ad33e

History | View | Annotate | Download (34.1 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 tempfile
36 a8083063 Iustin Pop
import random
37 d8470559 Michael Hanselmann
import logging
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 f78ede4e Guido Trotter
51 5b263ed7 Michael Hanselmann
def _ValidateConfig(data):
52 c41eea6e Iustin Pop
  """Verifies that a configuration objects looks valid.
53 c41eea6e Iustin Pop

54 c41eea6e Iustin Pop
  This only verifies the version of the configuration.
55 c41eea6e Iustin Pop

56 c41eea6e Iustin Pop
  @raise errors.ConfigurationError: if the version differs from what
57 c41eea6e Iustin Pop
      we expect
58 c41eea6e Iustin Pop

59 c41eea6e Iustin Pop
  """
60 5b263ed7 Michael Hanselmann
  if data.version != constants.CONFIG_VERSION:
61 243cdbcc Michael Hanselmann
    raise errors.ConfigurationError("Cluster configuration version"
62 243cdbcc Michael Hanselmann
                                    " mismatch, got %s instead of %s" %
63 5b263ed7 Michael Hanselmann
                                    (data.version,
64 243cdbcc Michael Hanselmann
                                     constants.CONFIG_VERSION))
65 a8083063 Iustin Pop
66 319856a9 Michael Hanselmann
67 a8083063 Iustin Pop
class ConfigWriter:
68 098c0958 Michael Hanselmann
  """The interface to the cluster configuration.
69 a8083063 Iustin Pop

70 098c0958 Michael Hanselmann
  """
71 a8083063 Iustin Pop
  def __init__(self, cfg_file=None, offline=False):
72 14e15659 Iustin Pop
    self.write_count = 0
73 f78ede4e Guido Trotter
    self._lock = _config_lock
74 a8083063 Iustin Pop
    self._config_data = None
75 a8083063 Iustin Pop
    self._offline = offline
76 a8083063 Iustin Pop
    if cfg_file is None:
77 a8083063 Iustin Pop
      self._cfg_file = constants.CLUSTER_CONF_FILE
78 a8083063 Iustin Pop
    else:
79 a8083063 Iustin Pop
      self._cfg_file = cfg_file
80 923b1523 Iustin Pop
    self._temporary_ids = set()
81 a81c53c9 Iustin Pop
    self._temporary_drbds = {}
82 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
83 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
84 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
85 89e1fc26 Iustin Pop
    # file than after it was modified
86 89e1fc26 Iustin Pop
    self._my_hostname = utils.HostInfo().name
87 3d3a04bc Iustin Pop
    self._OpenConfig()
88 a8083063 Iustin Pop
89 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
90 a8083063 Iustin Pop
  @staticmethod
91 a8083063 Iustin Pop
  def IsCluster():
92 a8083063 Iustin Pop
    """Check if the cluster is configured.
93 a8083063 Iustin Pop

94 a8083063 Iustin Pop
    """
95 a8083063 Iustin Pop
    return os.path.exists(constants.CLUSTER_CONF_FILE)
96 a8083063 Iustin Pop
97 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
98 a8083063 Iustin Pop
  def GenerateMAC(self):
99 a8083063 Iustin Pop
    """Generate a MAC for an instance.
100 a8083063 Iustin Pop

101 a8083063 Iustin Pop
    This should check the current instances for duplicates.
102 a8083063 Iustin Pop

103 a8083063 Iustin Pop
    """
104 a8083063 Iustin Pop
    prefix = self._config_data.cluster.mac_prefix
105 a8083063 Iustin Pop
    all_macs = self._AllMACs()
106 a8083063 Iustin Pop
    retries = 64
107 a8083063 Iustin Pop
    while retries > 0:
108 a8083063 Iustin Pop
      byte1 = random.randrange(0, 256)
109 a8083063 Iustin Pop
      byte2 = random.randrange(0, 256)
110 a8083063 Iustin Pop
      byte3 = random.randrange(0, 256)
111 a8083063 Iustin Pop
      mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
112 a8083063 Iustin Pop
      if mac not in all_macs:
113 a8083063 Iustin Pop
        break
114 a8083063 Iustin Pop
      retries -= 1
115 a8083063 Iustin Pop
    else:
116 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't generate unique MAC")
117 a8083063 Iustin Pop
    return mac
118 a8083063 Iustin Pop
119 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
120 1862d460 Alexander Schreiber
  def IsMacInUse(self, mac):
121 1862d460 Alexander Schreiber
    """Predicate: check if the specified MAC is in use in the Ganeti cluster.
122 1862d460 Alexander Schreiber

123 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
124 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
125 1862d460 Alexander Schreiber

126 1862d460 Alexander Schreiber
    """
127 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
128 1862d460 Alexander Schreiber
    return mac in all_macs
129 1862d460 Alexander Schreiber
130 f9518d38 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
131 f9518d38 Iustin Pop
  def GenerateDRBDSecret(self):
132 f9518d38 Iustin Pop
    """Generate a DRBD secret.
133 f9518d38 Iustin Pop

134 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
135 f9518d38 Iustin Pop

136 f9518d38 Iustin Pop
    """
137 f9518d38 Iustin Pop
    all_secrets = self._AllDRBDSecrets()
138 f9518d38 Iustin Pop
    retries = 64
139 f9518d38 Iustin Pop
    while retries > 0:
140 f9518d38 Iustin Pop
      secret = utils.GenerateSecret()
141 f9518d38 Iustin Pop
      if secret not in all_secrets:
142 f9518d38 Iustin Pop
        break
143 f9518d38 Iustin Pop
      retries -= 1
144 f9518d38 Iustin Pop
    else:
145 f9518d38 Iustin Pop
      raise errors.ConfigurationError("Can't generate unique DRBD secret")
146 f9518d38 Iustin Pop
    return secret
147 f9518d38 Iustin Pop
148 923b1523 Iustin Pop
  def _ComputeAllLVs(self):
149 923b1523 Iustin Pop
    """Compute the list of all LVs.
150 923b1523 Iustin Pop

151 923b1523 Iustin Pop
    """
152 923b1523 Iustin Pop
    lvnames = set()
153 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
154 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
155 923b1523 Iustin Pop
      for lv_list in node_data.values():
156 923b1523 Iustin Pop
        lvnames.update(lv_list)
157 923b1523 Iustin Pop
    return lvnames
158 923b1523 Iustin Pop
159 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
160 923b1523 Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
161 923b1523 Iustin Pop
    """Generate an unique disk name.
162 923b1523 Iustin Pop

163 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
164 923b1523 Iustin Pop
    duplicates.
165 923b1523 Iustin Pop

166 c41eea6e Iustin Pop
    @param exceptions: a list with some other names which should be checked
167 c41eea6e Iustin Pop
        for uniqueness (used for example when you want to get
168 c41eea6e Iustin Pop
        more than one id at one time without adding each one in
169 c41eea6e Iustin Pop
        turn to the config file)
170 923b1523 Iustin Pop

171 c41eea6e Iustin Pop
    @rtype: string
172 c41eea6e Iustin Pop
    @return: the unique id
173 923b1523 Iustin Pop

174 923b1523 Iustin Pop
    """
175 923b1523 Iustin Pop
    existing = set()
176 923b1523 Iustin Pop
    existing.update(self._temporary_ids)
177 923b1523 Iustin Pop
    existing.update(self._ComputeAllLVs())
178 923b1523 Iustin Pop
    existing.update(self._config_data.instances.keys())
179 923b1523 Iustin Pop
    existing.update(self._config_data.nodes.keys())
180 923b1523 Iustin Pop
    if exceptions is not None:
181 923b1523 Iustin Pop
      existing.update(exceptions)
182 923b1523 Iustin Pop
    retries = 64
183 923b1523 Iustin Pop
    while retries > 0:
184 24818e8f Michael Hanselmann
      unique_id = utils.NewUUID()
185 923b1523 Iustin Pop
      if unique_id not in existing and unique_id is not None:
186 923b1523 Iustin Pop
        break
187 923b1523 Iustin Pop
    else:
188 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Not able generate an unique ID"
189 3ecf6786 Iustin Pop
                                      " (last tried ID: %s" % unique_id)
190 923b1523 Iustin Pop
    self._temporary_ids.add(unique_id)
191 923b1523 Iustin Pop
    return unique_id
192 923b1523 Iustin Pop
193 a8083063 Iustin Pop
  def _AllMACs(self):
194 a8083063 Iustin Pop
    """Return all MACs present in the config.
195 a8083063 Iustin Pop

196 c41eea6e Iustin Pop
    @rtype: list
197 c41eea6e Iustin Pop
    @return: the list of all MACs
198 c41eea6e Iustin Pop

199 a8083063 Iustin Pop
    """
200 a8083063 Iustin Pop
    result = []
201 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
202 a8083063 Iustin Pop
      for nic in instance.nics:
203 a8083063 Iustin Pop
        result.append(nic.mac)
204 a8083063 Iustin Pop
205 a8083063 Iustin Pop
    return result
206 a8083063 Iustin Pop
207 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
208 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
209 f9518d38 Iustin Pop

210 c41eea6e Iustin Pop
    @rtype: list
211 c41eea6e Iustin Pop
    @return: the list of all DRBD secrets
212 c41eea6e Iustin Pop

213 f9518d38 Iustin Pop
    """
214 f9518d38 Iustin Pop
    def helper(disk, result):
215 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
216 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
217 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
218 f9518d38 Iustin Pop
      if disk.children:
219 f9518d38 Iustin Pop
        for child in disk.children:
220 f9518d38 Iustin Pop
          helper(child, result)
221 f9518d38 Iustin Pop
222 f9518d38 Iustin Pop
    result = []
223 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
224 f9518d38 Iustin Pop
      for disk in instance.disks:
225 f9518d38 Iustin Pop
        helper(disk, result)
226 f9518d38 Iustin Pop
227 f9518d38 Iustin Pop
    return result
228 f9518d38 Iustin Pop
229 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
230 a8083063 Iustin Pop
  def VerifyConfig(self):
231 a8efbb40 Iustin Pop
    """Verify function.
232 a8efbb40 Iustin Pop

233 a8083063 Iustin Pop
    """
234 a8083063 Iustin Pop
    result = []
235 a8083063 Iustin Pop
    seen_macs = []
236 48ce9fd9 Iustin Pop
    ports = {}
237 a8083063 Iustin Pop
    data = self._config_data
238 a8083063 Iustin Pop
    for instance_name in data.instances:
239 a8083063 Iustin Pop
      instance = data.instances[instance_name]
240 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
241 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
242 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
243 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
244 a8083063 Iustin Pop
        if snode not in data.nodes:
245 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
246 a8083063 Iustin Pop
                        (instance_name, snode))
247 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
248 a8083063 Iustin Pop
        if nic.mac in seen_macs:
249 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
250 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
251 a8083063 Iustin Pop
        else:
252 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
253 48ce9fd9 Iustin Pop
254 48ce9fd9 Iustin Pop
      # gather the drbd ports for duplicate checks
255 48ce9fd9 Iustin Pop
      for dsk in instance.disks:
256 48ce9fd9 Iustin Pop
        if dsk.dev_type in constants.LDS_DRBD:
257 48ce9fd9 Iustin Pop
          tcp_port = dsk.logical_id[2]
258 48ce9fd9 Iustin Pop
          if tcp_port not in ports:
259 48ce9fd9 Iustin Pop
            ports[tcp_port] = []
260 48ce9fd9 Iustin Pop
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
261 48ce9fd9 Iustin Pop
      # gather network port reservation
262 48ce9fd9 Iustin Pop
      net_port = getattr(instance, "network_port", None)
263 48ce9fd9 Iustin Pop
      if net_port is not None:
264 48ce9fd9 Iustin Pop
        if net_port not in ports:
265 48ce9fd9 Iustin Pop
          ports[net_port] = []
266 48ce9fd9 Iustin Pop
        ports[net_port].append((instance.name, "network port"))
267 48ce9fd9 Iustin Pop
268 48ce9fd9 Iustin Pop
    # cluster-wide pool of free ports
269 a8efbb40 Iustin Pop
    for free_port in data.cluster.tcpudp_port_pool:
270 48ce9fd9 Iustin Pop
      if free_port not in ports:
271 48ce9fd9 Iustin Pop
        ports[free_port] = []
272 48ce9fd9 Iustin Pop
      ports[free_port].append(("cluster", "port marked as free"))
273 48ce9fd9 Iustin Pop
274 48ce9fd9 Iustin Pop
    # compute tcp/udp duplicate ports
275 48ce9fd9 Iustin Pop
    keys = ports.keys()
276 48ce9fd9 Iustin Pop
    keys.sort()
277 48ce9fd9 Iustin Pop
    for pnum in keys:
278 48ce9fd9 Iustin Pop
      pdata = ports[pnum]
279 48ce9fd9 Iustin Pop
      if len(pdata) > 1:
280 48ce9fd9 Iustin Pop
        txt = ", ".join(["%s/%s" % val for val in pdata])
281 48ce9fd9 Iustin Pop
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
282 48ce9fd9 Iustin Pop
283 48ce9fd9 Iustin Pop
    # highest used tcp port check
284 48ce9fd9 Iustin Pop
    if keys:
285 a8efbb40 Iustin Pop
      if keys[-1] > data.cluster.highest_used_port:
286 48ce9fd9 Iustin Pop
        result.append("Highest used port mismatch, saved %s, computed %s" %
287 a8efbb40 Iustin Pop
                      (data.cluster.highest_used_port, keys[-1]))
288 a8efbb40 Iustin Pop
289 3a26773f Iustin Pop
    if not data.nodes[data.cluster.master_node].master_candidate:
290 3a26773f Iustin Pop
      result.append("Master node is not a master candidate")
291 3a26773f Iustin Pop
292 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
293 ec0292f1 Iustin Pop
    if mc_now < mc_max:
294 ec0292f1 Iustin Pop
      result.append("Not enough master candidates: actual %d, target %d" %
295 ec0292f1 Iustin Pop
                    (mc_now, mc_max))
296 48ce9fd9 Iustin Pop
297 a8083063 Iustin Pop
    return result
298 a8083063 Iustin Pop
299 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
300 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
301 a8083063 Iustin Pop

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

304 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
305 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
306 a8083063 Iustin Pop
    node.
307 a8083063 Iustin Pop

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

310 a8083063 Iustin Pop
    """
311 a8083063 Iustin Pop
    if disk.children:
312 a8083063 Iustin Pop
      for child in disk.children:
313 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
314 a8083063 Iustin Pop
315 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
316 a8083063 Iustin Pop
      return
317 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
318 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
319 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
320 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
321 3ecf6786 Iustin Pop
                                        node_name)
322 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
323 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
324 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
325 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
326 a8083063 Iustin Pop
                                        " for %s" % str(disk))
327 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
328 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
329 a8083063 Iustin Pop
      if pnode == node_name:
330 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
331 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
332 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
333 a8083063 Iustin Pop
    else:
334 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
335 a8083063 Iustin Pop
    return
336 a8083063 Iustin Pop
337 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
338 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
339 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
340 f78ede4e Guido Trotter

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

343 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
344 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
345 f78ede4e Guido Trotter
    node.
346 f78ede4e Guido Trotter

347 f78ede4e Guido Trotter
    """
348 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
349 f78ede4e Guido Trotter
350 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
351 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
352 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
353 b2fddf63 Iustin Pop

354 b2fddf63 Iustin Pop
    """
355 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
356 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
357 264bb3c5 Michael Hanselmann
358 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
359 264bb3c5 Michael Hanselmann
    self._WriteConfig()
360 264bb3c5 Michael Hanselmann
361 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
362 b2fddf63 Iustin Pop
  def GetPortList(self):
363 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
364 264bb3c5 Michael Hanselmann

365 264bb3c5 Michael Hanselmann
    """
366 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
367 264bb3c5 Michael Hanselmann
368 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
369 a8083063 Iustin Pop
  def AllocatePort(self):
370 a8083063 Iustin Pop
    """Allocate a port.
371 a8083063 Iustin Pop

372 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
373 b2fddf63 Iustin Pop
    default port range (and in this case we increase
374 b2fddf63 Iustin Pop
    highest_used_port).
375 a8083063 Iustin Pop

376 a8083063 Iustin Pop
    """
377 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
378 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
379 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
380 264bb3c5 Michael Hanselmann
    else:
381 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
382 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
383 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
384 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
385 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
386 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
387 a8083063 Iustin Pop
388 a8083063 Iustin Pop
    self._WriteConfig()
389 a8083063 Iustin Pop
    return port
390 a8083063 Iustin Pop
391 a81c53c9 Iustin Pop
  def _ComputeDRBDMap(self, instance):
392 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
393 a81c53c9 Iustin Pop

394 c41eea6e Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
395 c41eea6e Iustin Pop
        the returned dict will have all the nodes in it (even if with
396 c41eea6e Iustin Pop
        an empty list).
397 a81c53c9 Iustin Pop

398 a81c53c9 Iustin Pop
    """
399 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
400 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
401 f9518d38 Iustin Pop
        nodeA, nodeB, dummy, minorA, minorB = disk.logical_id[:5]
402 a81c53c9 Iustin Pop
        for node, port in ((nodeA, minorA), (nodeB, minorB)):
403 a81c53c9 Iustin Pop
          assert node in used, "Instance node not found in node list"
404 a81c53c9 Iustin Pop
          if port in used[node]:
405 a81c53c9 Iustin Pop
            raise errors.ProgrammerError("DRBD minor already used:"
406 a81c53c9 Iustin Pop
                                         " %s/%s, %s/%s" %
407 a81c53c9 Iustin Pop
                                         (node, port, instance_name,
408 a81c53c9 Iustin Pop
                                          used[node][port]))
409 a81c53c9 Iustin Pop
410 a81c53c9 Iustin Pop
          used[node][port] = instance_name
411 a81c53c9 Iustin Pop
      if disk.children:
412 a81c53c9 Iustin Pop
        for child in disk.children:
413 a81c53c9 Iustin Pop
          _AppendUsedPorts(instance_name, child, used)
414 a81c53c9 Iustin Pop
415 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
416 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
417 a81c53c9 Iustin Pop
      my_dict[node][minor] = instance
418 a81c53c9 Iustin Pop
    for instance in self._config_data.instances.itervalues():
419 a81c53c9 Iustin Pop
      for disk in instance.disks:
420 a81c53c9 Iustin Pop
        _AppendUsedPorts(instance.name, disk, my_dict)
421 a81c53c9 Iustin Pop
    return my_dict
422 a81c53c9 Iustin Pop
423 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
424 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
425 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
426 a81c53c9 Iustin Pop

427 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
428 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
429 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
430 a81c53c9 Iustin Pop
    order as the passed nodes.
431 a81c53c9 Iustin Pop

432 a81c53c9 Iustin Pop
    """
433 a81c53c9 Iustin Pop
    d_map = self._ComputeDRBDMap(instance)
434 a81c53c9 Iustin Pop
    result = []
435 a81c53c9 Iustin Pop
    for nname in nodes:
436 a81c53c9 Iustin Pop
      ndata = d_map[nname]
437 a81c53c9 Iustin Pop
      if not ndata:
438 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
439 a81c53c9 Iustin Pop
        result.append(0)
440 a81c53c9 Iustin Pop
        ndata[0] = instance
441 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
442 a81c53c9 Iustin Pop
        continue
443 a81c53c9 Iustin Pop
      keys = ndata.keys()
444 a81c53c9 Iustin Pop
      keys.sort()
445 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
446 a81c53c9 Iustin Pop
      if ffree is None:
447 a81c53c9 Iustin Pop
        # return the next minor
448 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
449 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
450 a81c53c9 Iustin Pop
      else:
451 a81c53c9 Iustin Pop
        minor = ffree
452 a81c53c9 Iustin Pop
      result.append(minor)
453 a81c53c9 Iustin Pop
      ndata[minor] = instance
454 a81c53c9 Iustin Pop
      assert (nname, minor) not in self._temporary_drbds, \
455 a81c53c9 Iustin Pop
             "Attempt to reuse reserved DRBD minor"
456 a81c53c9 Iustin Pop
      self._temporary_drbds[(nname, minor)] = instance
457 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
458 a81c53c9 Iustin Pop
                  nodes, result)
459 a81c53c9 Iustin Pop
    return result
460 a81c53c9 Iustin Pop
461 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
462 a81c53c9 Iustin Pop
  def ReleaseDRBDMinors(self, instance):
463 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
464 a81c53c9 Iustin Pop

465 a81c53c9 Iustin Pop
    This should be called on both the error paths and on the success
466 a81c53c9 Iustin Pop
    paths (after the instance has been added or updated).
467 a81c53c9 Iustin Pop

468 a81c53c9 Iustin Pop
    @type instance: string
469 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
470 a81c53c9 Iustin Pop
                     released
471 a81c53c9 Iustin Pop

472 a81c53c9 Iustin Pop
    """
473 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
474 a81c53c9 Iustin Pop
      if name == instance:
475 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
476 a81c53c9 Iustin Pop
477 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
478 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
479 4a8b186a Michael Hanselmann
    """Get the configuration version.
480 4a8b186a Michael Hanselmann

481 4a8b186a Michael Hanselmann
    @return: Config version
482 4a8b186a Michael Hanselmann

483 4a8b186a Michael Hanselmann
    """
484 4a8b186a Michael Hanselmann
    return self._config_data.version
485 4a8b186a Michael Hanselmann
486 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
487 4a8b186a Michael Hanselmann
  def GetClusterName(self):
488 4a8b186a Michael Hanselmann
    """Get cluster name.
489 4a8b186a Michael Hanselmann

490 4a8b186a Michael Hanselmann
    @return: Cluster name
491 4a8b186a Michael Hanselmann

492 4a8b186a Michael Hanselmann
    """
493 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
494 4a8b186a Michael Hanselmann
495 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
496 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
497 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
498 4a8b186a Michael Hanselmann

499 4a8b186a Michael Hanselmann
    @return: Master hostname
500 4a8b186a Michael Hanselmann

501 4a8b186a Michael Hanselmann
    """
502 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
503 4a8b186a Michael Hanselmann
504 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
505 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
506 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
507 4a8b186a Michael Hanselmann

508 4a8b186a Michael Hanselmann
    @return: Master IP
509 4a8b186a Michael Hanselmann

510 4a8b186a Michael Hanselmann
    """
511 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
512 4a8b186a Michael Hanselmann
513 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
514 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
515 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
516 4a8b186a Michael Hanselmann

517 4a8b186a Michael Hanselmann
    """
518 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
519 4a8b186a Michael Hanselmann
520 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
521 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
522 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
523 4a8b186a Michael Hanselmann

524 4a8b186a Michael Hanselmann
    """
525 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
526 4a8b186a Michael Hanselmann
527 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
528 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
529 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
530 4a8b186a Michael Hanselmann

531 4a8b186a Michael Hanselmann
    """
532 64272529 Iustin Pop
    return self._config_data.cluster.default_hypervisor
533 4a8b186a Michael Hanselmann
534 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
535 a8083063 Iustin Pop
  def GetHostKey(self):
536 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
537 a8083063 Iustin Pop

538 c41eea6e Iustin Pop
    @rtype: string
539 c41eea6e Iustin Pop
    @return: the rsa hostkey
540 a8083063 Iustin Pop

541 a8083063 Iustin Pop
    """
542 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
543 a8083063 Iustin Pop
544 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
545 a8083063 Iustin Pop
  def AddInstance(self, instance):
546 a8083063 Iustin Pop
    """Add an instance to the config.
547 a8083063 Iustin Pop

548 a8083063 Iustin Pop
    This should be used after creating a new instance.
549 a8083063 Iustin Pop

550 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
551 c41eea6e Iustin Pop
    @param instance: the instance object
552 c41eea6e Iustin Pop

553 a8083063 Iustin Pop
    """
554 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
555 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
556 a8083063 Iustin Pop
557 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
558 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
559 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
560 923b1523 Iustin Pop
561 b989e85d Iustin Pop
    instance.serial_no = 1
562 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
563 a8083063 Iustin Pop
    self._WriteConfig()
564 a8083063 Iustin Pop
565 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
566 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
567 a8083063 Iustin Pop

568 a8083063 Iustin Pop
    """
569 6a408fb2 Iustin Pop
    if status not in ("up", "down"):
570 6a408fb2 Iustin Pop
      raise errors.ProgrammerError("Invalid status '%s' passed to"
571 6a408fb2 Iustin Pop
                                   " ConfigWriter._SetInstanceStatus()" %
572 6a408fb2 Iustin Pop
                                   status)
573 a8083063 Iustin Pop
574 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
575 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
576 3ecf6786 Iustin Pop
                                      instance_name)
577 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
578 455a3445 Iustin Pop
    if instance.status != status:
579 455a3445 Iustin Pop
      instance.status = status
580 b989e85d Iustin Pop
      instance.serial_no += 1
581 455a3445 Iustin Pop
      self._WriteConfig()
582 a8083063 Iustin Pop
583 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
584 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
585 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
586 6a408fb2 Iustin Pop

587 6a408fb2 Iustin Pop
    """
588 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
589 6a408fb2 Iustin Pop
590 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
591 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
592 a8083063 Iustin Pop
    """Remove the instance from the configuration.
593 a8083063 Iustin Pop

594 a8083063 Iustin Pop
    """
595 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
596 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
597 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
598 a8083063 Iustin Pop
    self._WriteConfig()
599 a8083063 Iustin Pop
600 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
601 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
602 fc95f88f Iustin Pop
    """Rename an instance.
603 fc95f88f Iustin Pop

604 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
605 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
606 fc95f88f Iustin Pop
    rename.
607 fc95f88f Iustin Pop

608 fc95f88f Iustin Pop
    """
609 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
610 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
611 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
612 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
613 fc95f88f Iustin Pop
    inst.name = new_name
614 b23c4333 Manuel Franceschini
615 b23c4333 Manuel Franceschini
    for disk in inst.disks:
616 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
617 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
618 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
619 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
620 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
621 b23c4333 Manuel Franceschini
                                                           inst.name,
622 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
623 b23c4333 Manuel Franceschini
624 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
625 fc95f88f Iustin Pop
    self._WriteConfig()
626 fc95f88f Iustin Pop
627 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
628 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
629 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
630 a8083063 Iustin Pop

631 a8083063 Iustin Pop
    """
632 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
633 a8083063 Iustin Pop
634 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
635 94bbfece Iustin Pop
    """Get the list of instances.
636 94bbfece Iustin Pop

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

639 94bbfece Iustin Pop
    """
640 94bbfece Iustin Pop
    return self._config_data.instances.keys()
641 94bbfece Iustin Pop
642 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
643 a8083063 Iustin Pop
  def GetInstanceList(self):
644 a8083063 Iustin Pop
    """Get the list of instances.
645 a8083063 Iustin Pop

646 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
647 c41eea6e Iustin Pop
        'instance1.example.com']
648 a8083063 Iustin Pop

649 a8083063 Iustin Pop
    """
650 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
651 a8083063 Iustin Pop
652 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
653 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
654 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
655 a8083063 Iustin Pop

656 a8083063 Iustin Pop
    """
657 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
658 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
659 a8083063 Iustin Pop
660 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
661 94bbfece Iustin Pop
    """Returns informations about an instance.
662 94bbfece Iustin Pop

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

665 94bbfece Iustin Pop
    """
666 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
667 94bbfece Iustin Pop
      return None
668 94bbfece Iustin Pop
669 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
670 94bbfece Iustin Pop
671 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
672 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
673 a8083063 Iustin Pop
    """Returns informations about an instance.
674 a8083063 Iustin Pop

675 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
676 a8083063 Iustin Pop
    an instance are taken from the live systems.
677 a8083063 Iustin Pop

678 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
679 c41eea6e Iustin Pop
        I{instance1.example.com}
680 a8083063 Iustin Pop

681 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
682 c41eea6e Iustin Pop
    @return: the instance object
683 a8083063 Iustin Pop

684 a8083063 Iustin Pop
    """
685 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
686 a8083063 Iustin Pop
687 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
688 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
689 0b2de758 Iustin Pop
    """Get the configuration of all instances.
690 0b2de758 Iustin Pop

691 0b2de758 Iustin Pop
    @rtype: dict
692 0b2de758 Iustin Pop
    @returns: dict of (instance, instance_info), where instance_info is what
693 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
694 0b2de758 Iustin Pop

695 0b2de758 Iustin Pop
    """
696 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
697 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
698 0b2de758 Iustin Pop
    return my_dict
699 0b2de758 Iustin Pop
700 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
701 a8083063 Iustin Pop
  def AddNode(self, node):
702 a8083063 Iustin Pop
    """Add a node to the configuration.
703 a8083063 Iustin Pop

704 c41eea6e Iustin Pop
    @type node: L{objects.Node}
705 c41eea6e Iustin Pop
    @param node: a Node instance
706 a8083063 Iustin Pop

707 a8083063 Iustin Pop
    """
708 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
709 d8470559 Michael Hanselmann
710 b989e85d Iustin Pop
    node.serial_no = 1
711 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
712 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
713 a8083063 Iustin Pop
    self._WriteConfig()
714 a8083063 Iustin Pop
715 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
716 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
717 a8083063 Iustin Pop
    """Remove a node from the configuration.
718 a8083063 Iustin Pop

719 a8083063 Iustin Pop
    """
720 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
721 d8470559 Michael Hanselmann
722 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
723 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
724 a8083063 Iustin Pop
725 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
726 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
727 a8083063 Iustin Pop
    self._WriteConfig()
728 a8083063 Iustin Pop
729 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
730 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
731 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
732 a8083063 Iustin Pop

733 a8083063 Iustin Pop
    """
734 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
735 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
736 a8083063 Iustin Pop
737 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
738 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
739 a8083063 Iustin Pop

740 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
741 c41eea6e Iustin Pop
    held.
742 f78ede4e Guido Trotter

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

745 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
746 c41eea6e Iustin Pop
    @return: the node object
747 a8083063 Iustin Pop

748 a8083063 Iustin Pop
    """
749 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
750 a8083063 Iustin Pop
      return None
751 a8083063 Iustin Pop
752 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
753 a8083063 Iustin Pop
754 f78ede4e Guido Trotter
755 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
756 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
757 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
758 f78ede4e Guido Trotter

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

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

763 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
764 c41eea6e Iustin Pop
    @return: the node object
765 f78ede4e Guido Trotter

766 f78ede4e Guido Trotter
    """
767 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
768 f78ede4e Guido Trotter
769 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
770 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
771 a8083063 Iustin Pop

772 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
773 c41eea6e Iustin Pop
    held.
774 c41eea6e Iustin Pop

775 c41eea6e Iustin Pop
    @rtype: list
776 f78ede4e Guido Trotter

777 a8083063 Iustin Pop
    """
778 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
779 a8083063 Iustin Pop
780 f78ede4e Guido Trotter
781 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
782 f78ede4e Guido Trotter
  def GetNodeList(self):
783 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
784 f78ede4e Guido Trotter

785 f78ede4e Guido Trotter
    """
786 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
787 f78ede4e Guido Trotter
788 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
789 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
790 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
791 d65e5776 Iustin Pop

792 d65e5776 Iustin Pop
    @rtype: dict
793 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
794 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
795 d65e5776 Iustin Pop

796 d65e5776 Iustin Pop
    """
797 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
798 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
799 d65e5776 Iustin Pop
    return my_dict
800 d65e5776 Iustin Pop
801 ec0292f1 Iustin Pop
  def _UnlockedGetMasterCandidateStats(self):
802 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
803 ec0292f1 Iustin Pop

804 ec0292f1 Iustin Pop
    @rtype: tuple
805 ec0292f1 Iustin Pop
    @return: tuple of (current, desired and possible)
806 ec0292f1 Iustin Pop

807 ec0292f1 Iustin Pop
    """
808 ec0292f1 Iustin Pop
    mc_now = mc_max = 0
809 ec0292f1 Iustin Pop
    for node in self._config_data.nodes.itervalues():
810 ec0292f1 Iustin Pop
      if not node.offline:
811 ec0292f1 Iustin Pop
        mc_max += 1
812 ec0292f1 Iustin Pop
      if node.master_candidate:
813 ec0292f1 Iustin Pop
        mc_now += 1
814 ec0292f1 Iustin Pop
    mc_max = min(mc_max, self._config_data.cluster.candidate_pool_size)
815 ec0292f1 Iustin Pop
    return (mc_now, mc_max)
816 ec0292f1 Iustin Pop
817 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
818 ec0292f1 Iustin Pop
  def GetMasterCandidateStats(self):
819 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
820 ec0292f1 Iustin Pop

821 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
822 ec0292f1 Iustin Pop

823 ec0292f1 Iustin Pop
    @rtype: tuple
824 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
825 ec0292f1 Iustin Pop

826 ec0292f1 Iustin Pop
    """
827 ec0292f1 Iustin Pop
    return self._UnlockedGetMasterCandidateStats()
828 ec0292f1 Iustin Pop
829 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
830 ec0292f1 Iustin Pop
  def MaintainCandidatePool(self):
831 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
832 ec0292f1 Iustin Pop

833 ec0292f1 Iustin Pop
    @rtype: list
834 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
835 ec0292f1 Iustin Pop

836 ec0292f1 Iustin Pop
    """
837 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
838 ec0292f1 Iustin Pop
    mod_list = []
839 ec0292f1 Iustin Pop
    if mc_now < mc_max:
840 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
841 ec0292f1 Iustin Pop
      random.shuffle(node_list)
842 ec0292f1 Iustin Pop
      for name in node_list:
843 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
844 ec0292f1 Iustin Pop
          break
845 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
846 ec0292f1 Iustin Pop
        if node.master_candidate or node.offline:
847 ec0292f1 Iustin Pop
          continue
848 ee513a66 Iustin Pop
        mod_list.append(node)
849 ec0292f1 Iustin Pop
        node.master_candidate = True
850 ec0292f1 Iustin Pop
        node.serial_no += 1
851 ec0292f1 Iustin Pop
        mc_now += 1
852 ec0292f1 Iustin Pop
      if mc_now != mc_max:
853 ec0292f1 Iustin Pop
        # this should not happen
854 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
855 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
856 ec0292f1 Iustin Pop
      if mod_list:
857 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
858 ec0292f1 Iustin Pop
        self._WriteConfig()
859 ec0292f1 Iustin Pop
860 ec0292f1 Iustin Pop
    return mod_list
861 ec0292f1 Iustin Pop
862 a8083063 Iustin Pop
  def _BumpSerialNo(self):
863 a8083063 Iustin Pop
    """Bump up the serial number of the config.
864 a8083063 Iustin Pop

865 a8083063 Iustin Pop
    """
866 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
867 a8083063 Iustin Pop
868 a8083063 Iustin Pop
  def _OpenConfig(self):
869 a8083063 Iustin Pop
    """Read the config data from disk.
870 a8083063 Iustin Pop

871 a8083063 Iustin Pop
    """
872 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
873 a8083063 Iustin Pop
    try:
874 a8083063 Iustin Pop
      try:
875 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
876 a8083063 Iustin Pop
      except Exception, err:
877 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
878 a8083063 Iustin Pop
    finally:
879 a8083063 Iustin Pop
      f.close()
880 5b263ed7 Michael Hanselmann
881 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
882 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
883 5b263ed7 Michael Hanselmann
884 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
885 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
886 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
887 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
888 a8083063 Iustin Pop
    self._config_data = data
889 0779e3aa Iustin Pop
    # init the last serial as -1 so that the next write will cause
890 0779e3aa Iustin Pop
    # ssconf update
891 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
892 a8083063 Iustin Pop
893 a8083063 Iustin Pop
  def _DistributeConfig(self):
894 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
895 a8083063 Iustin Pop

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

899 a8083063 Iustin Pop
    """
900 a8083063 Iustin Pop
    if self._offline:
901 a8083063 Iustin Pop
      return True
902 a8083063 Iustin Pop
    bad = False
903 a8083063 Iustin Pop
904 6a5b8b4b Iustin Pop
    node_list = []
905 6a5b8b4b Iustin Pop
    addr_list = []
906 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
907 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
908 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
909 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
910 6b294c53 Iustin Pop
    # in between
911 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
912 6a5b8b4b Iustin Pop
      if node_name == myhostname:
913 6a5b8b4b Iustin Pop
        continue
914 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
915 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
916 6a5b8b4b Iustin Pop
        continue
917 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
918 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
919 6b294c53 Iustin Pop
920 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
921 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
922 6a5b8b4b Iustin Pop
    for node in node_list:
923 a8083063 Iustin Pop
      if not result[node]:
924 74a48621 Iustin Pop
        logging.error("copy of file %s to node %s failed",
925 74a48621 Iustin Pop
                      self._cfg_file, node)
926 a8083063 Iustin Pop
        bad = True
927 a8083063 Iustin Pop
    return not bad
928 a8083063 Iustin Pop
929 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
930 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
931 a8083063 Iustin Pop

932 a8083063 Iustin Pop
    """
933 a8083063 Iustin Pop
    if destination is None:
934 a8083063 Iustin Pop
      destination = self._cfg_file
935 a8083063 Iustin Pop
    self._BumpSerialNo()
936 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
937 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
938 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
939 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
940 a8083063 Iustin Pop
    try:
941 8d14b30d Iustin Pop
      f.write(txt)
942 a8083063 Iustin Pop
      os.fsync(f.fileno())
943 a8083063 Iustin Pop
    finally:
944 a8083063 Iustin Pop
      f.close()
945 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
946 a8083063 Iustin Pop
    os.rename(name, destination)
947 14e15659 Iustin Pop
    self.write_count += 1
948 3d3a04bc Iustin Pop
949 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
950 a8083063 Iustin Pop
    self._DistributeConfig()
951 a8083063 Iustin Pop
952 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
953 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
954 d9a855f1 Michael Hanselmann
      if not self._offline:
955 03d1dba2 Michael Hanselmann
        rpc.RpcRunner.call_write_ssconf_files(self._UnlockedGetNodeList(),
956 03d1dba2 Michael Hanselmann
                                              self._UnlockedGetSsconfValues())
957 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
958 54d1a06e Michael Hanselmann
959 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
960 054596f0 Iustin Pop
    """Return the values needed by ssconf.
961 054596f0 Iustin Pop

962 054596f0 Iustin Pop
    @rtype: dict
963 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
964 054596f0 Iustin Pop
        associated value
965 054596f0 Iustin Pop

966 054596f0 Iustin Pop
    """
967 a3316e4a Iustin Pop
    fn = "\n".join
968 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
969 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
970 a3316e4a Iustin Pop
971 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
972 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
973 a3316e4a Iustin Pop
    node_data = fn(node_names)
974 f56618e0 Iustin Pop
975 054596f0 Iustin Pop
    cluster = self._config_data.cluster
976 03d1dba2 Michael Hanselmann
    return {
977 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
978 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
979 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
980 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
981 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
982 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
983 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
984 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
985 03d1dba2 Michael Hanselmann
      }
986 03d1dba2 Michael Hanselmann
987 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
988 f6bd6e98 Michael Hanselmann
  def InitConfig(self, version, cluster_config, master_node_config):
989 a8083063 Iustin Pop
    """Create the initial cluster configuration.
990 a8083063 Iustin Pop

991 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
992 b9eeeb02 Michael Hanselmann
    node, and no instances.
993 a8083063 Iustin Pop

994 f6bd6e98 Michael Hanselmann
    @type version: int
995 f6bd6e98 Michael Hanselmann
    @param version: Configuration version
996 b9eeeb02 Michael Hanselmann
    @type cluster_config: objects.Cluster
997 b9eeeb02 Michael Hanselmann
    @param cluster_config: Cluster configuration
998 b9eeeb02 Michael Hanselmann
    @type master_node_config: objects.Node
999 b9eeeb02 Michael Hanselmann
    @param master_node_config: Master node configuration
1000 b9eeeb02 Michael Hanselmann

1001 b9eeeb02 Michael Hanselmann
    """
1002 b9eeeb02 Michael Hanselmann
    nodes = {
1003 b9eeeb02 Michael Hanselmann
      master_node_config.name: master_node_config,
1004 b9eeeb02 Michael Hanselmann
      }
1005 b9eeeb02 Michael Hanselmann
1006 f6bd6e98 Michael Hanselmann
    self._config_data = objects.ConfigData(version=version,
1007 f6bd6e98 Michael Hanselmann
                                           cluster=cluster_config,
1008 b9eeeb02 Michael Hanselmann
                                           nodes=nodes,
1009 a8083063 Iustin Pop
                                           instances={},
1010 9d38c6e1 Iustin Pop
                                           serial_no=1)
1011 a8083063 Iustin Pop
    self._WriteConfig()
1012 a8083063 Iustin Pop
1013 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1014 a8083063 Iustin Pop
  def GetVGName(self):
1015 a8083063 Iustin Pop
    """Return the volume group name.
1016 a8083063 Iustin Pop

1017 a8083063 Iustin Pop
    """
1018 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1019 a8083063 Iustin Pop
1020 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1021 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1022 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1023 89ff8e15 Manuel Franceschini

1024 89ff8e15 Manuel Franceschini
    """
1025 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1026 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1027 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1028 89ff8e15 Manuel Franceschini
1029 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1030 a8083063 Iustin Pop
  def GetDefBridge(self):
1031 a8083063 Iustin Pop
    """Return the default bridge.
1032 a8083063 Iustin Pop

1033 a8083063 Iustin Pop
    """
1034 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
1035 a8083063 Iustin Pop
1036 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1037 a8083063 Iustin Pop
  def GetMACPrefix(self):
1038 a8083063 Iustin Pop
    """Return the mac prefix.
1039 a8083063 Iustin Pop

1040 a8083063 Iustin Pop
    """
1041 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1042 62779dd0 Iustin Pop
1043 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1044 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1045 62779dd0 Iustin Pop
    """Returns informations about the cluster
1046 62779dd0 Iustin Pop

1047 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1048 c41eea6e Iustin Pop
    @return: the cluster object
1049 62779dd0 Iustin Pop

1050 62779dd0 Iustin Pop
    """
1051 62779dd0 Iustin Pop
    return self._config_data.cluster
1052 e00fb268 Iustin Pop
1053 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1054 e00fb268 Iustin Pop
  def Update(self, target):
1055 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1056 e00fb268 Iustin Pop

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

1063 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1064 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1065 c41eea6e Iustin Pop
        the cluster
1066 c41eea6e Iustin Pop

1067 e00fb268 Iustin Pop
    """
1068 e00fb268 Iustin Pop
    if self._config_data is None:
1069 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1070 3ecf6786 Iustin Pop
                                   " cannot save.")
1071 f34901f8 Iustin Pop
    update_serial = False
1072 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1073 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1074 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1075 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1076 f34901f8 Iustin Pop
      update_serial = True
1077 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1078 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1079 e00fb268 Iustin Pop
    else:
1080 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1081 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1082 e00fb268 Iustin Pop
    if not test:
1083 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1084 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1085 f34901f8 Iustin Pop
    target.serial_no += 1
1086 f34901f8 Iustin Pop
1087 cff4c037 Iustin Pop
    if update_serial:
1088 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1089 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1090 b989e85d Iustin Pop
1091 e00fb268 Iustin Pop
    self._WriteConfig()