Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 6906a9d8

History | View | Annotate | Download (35.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 3c7f6c44 Iustin Pop
    self._last_cluster_serial = -1
88 3d3a04bc Iustin Pop
    self._OpenConfig()
89 a8083063 Iustin Pop
90 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
91 a8083063 Iustin Pop
  @staticmethod
92 a8083063 Iustin Pop
  def IsCluster():
93 a8083063 Iustin Pop
    """Check if the cluster is configured.
94 a8083063 Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

428 6d2e83d5 Iustin Pop
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
429 6d2e83d5 Iustin Pop

430 6d2e83d5 Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
431 6d2e83d5 Iustin Pop
        the returned dict will have all the nodes in it (even if with
432 6d2e83d5 Iustin Pop
        an empty list).
433 6d2e83d5 Iustin Pop

434 6d2e83d5 Iustin Pop
    """
435 6d2e83d5 Iustin Pop
    return self._UnlockedComputeDRBDMap()
436 6d2e83d5 Iustin Pop
437 6d2e83d5 Iustin Pop
  @locking.ssynchronized(_config_lock)
438 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
439 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
440 a81c53c9 Iustin Pop

441 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
442 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
443 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
444 a81c53c9 Iustin Pop
    order as the passed nodes.
445 a81c53c9 Iustin Pop

446 32388e6d Iustin Pop
    @type instance: string
447 32388e6d Iustin Pop
    @param instance: the instance for which we allocate minors
448 32388e6d Iustin Pop

449 a81c53c9 Iustin Pop
    """
450 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
451 32388e6d Iustin Pop
           "Invalid argument passed to AllocateDRBDMinor"
452 32388e6d Iustin Pop
453 6d2e83d5 Iustin Pop
    d_map = self._UnlockedComputeDRBDMap()
454 a81c53c9 Iustin Pop
    result = []
455 a81c53c9 Iustin Pop
    for nname in nodes:
456 a81c53c9 Iustin Pop
      ndata = d_map[nname]
457 a81c53c9 Iustin Pop
      if not ndata:
458 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
459 a81c53c9 Iustin Pop
        result.append(0)
460 a81c53c9 Iustin Pop
        ndata[0] = instance
461 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
462 a81c53c9 Iustin Pop
        continue
463 a81c53c9 Iustin Pop
      keys = ndata.keys()
464 a81c53c9 Iustin Pop
      keys.sort()
465 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
466 a81c53c9 Iustin Pop
      if ffree is None:
467 a81c53c9 Iustin Pop
        # return the next minor
468 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
469 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
470 a81c53c9 Iustin Pop
      else:
471 a81c53c9 Iustin Pop
        minor = ffree
472 a81c53c9 Iustin Pop
      result.append(minor)
473 a81c53c9 Iustin Pop
      ndata[minor] = instance
474 a81c53c9 Iustin Pop
      assert (nname, minor) not in self._temporary_drbds, \
475 a81c53c9 Iustin Pop
             "Attempt to reuse reserved DRBD minor"
476 a81c53c9 Iustin Pop
      self._temporary_drbds[(nname, minor)] = instance
477 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
478 a81c53c9 Iustin Pop
                  nodes, result)
479 a81c53c9 Iustin Pop
    return result
480 a81c53c9 Iustin Pop
481 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
482 a81c53c9 Iustin Pop
  def ReleaseDRBDMinors(self, instance):
483 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
484 a81c53c9 Iustin Pop

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

488 a81c53c9 Iustin Pop
    @type instance: string
489 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
490 a81c53c9 Iustin Pop
                     released
491 a81c53c9 Iustin Pop

492 a81c53c9 Iustin Pop
    """
493 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
494 32388e6d Iustin Pop
           "Invalid argument passed to ReleaseDRBDMinors"
495 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
496 a81c53c9 Iustin Pop
      if name == instance:
497 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
498 a81c53c9 Iustin Pop
499 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
500 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
501 4a8b186a Michael Hanselmann
    """Get the configuration version.
502 4a8b186a Michael Hanselmann

503 4a8b186a Michael Hanselmann
    @return: Config version
504 4a8b186a Michael Hanselmann

505 4a8b186a Michael Hanselmann
    """
506 4a8b186a Michael Hanselmann
    return self._config_data.version
507 4a8b186a Michael Hanselmann
508 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
509 4a8b186a Michael Hanselmann
  def GetClusterName(self):
510 4a8b186a Michael Hanselmann
    """Get cluster name.
511 4a8b186a Michael Hanselmann

512 4a8b186a Michael Hanselmann
    @return: Cluster name
513 4a8b186a Michael Hanselmann

514 4a8b186a Michael Hanselmann
    """
515 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
516 4a8b186a Michael Hanselmann
517 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
518 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
519 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
520 4a8b186a Michael Hanselmann

521 4a8b186a Michael Hanselmann
    @return: Master hostname
522 4a8b186a Michael Hanselmann

523 4a8b186a Michael Hanselmann
    """
524 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
525 4a8b186a Michael Hanselmann
526 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
527 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
528 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
529 4a8b186a Michael Hanselmann

530 4a8b186a Michael Hanselmann
    @return: Master IP
531 4a8b186a Michael Hanselmann

532 4a8b186a Michael Hanselmann
    """
533 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
534 4a8b186a Michael Hanselmann
535 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
536 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
537 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
538 4a8b186a Michael Hanselmann

539 4a8b186a Michael Hanselmann
    """
540 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
541 4a8b186a Michael Hanselmann
542 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
543 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
544 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
545 4a8b186a Michael Hanselmann

546 4a8b186a Michael Hanselmann
    """
547 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
548 4a8b186a Michael Hanselmann
549 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
550 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
551 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
552 4a8b186a Michael Hanselmann

553 4a8b186a Michael Hanselmann
    """
554 64272529 Iustin Pop
    return self._config_data.cluster.default_hypervisor
555 4a8b186a Michael Hanselmann
556 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
557 a8083063 Iustin Pop
  def GetHostKey(self):
558 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
559 a8083063 Iustin Pop

560 c41eea6e Iustin Pop
    @rtype: string
561 c41eea6e Iustin Pop
    @return: the rsa hostkey
562 a8083063 Iustin Pop

563 a8083063 Iustin Pop
    """
564 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
565 a8083063 Iustin Pop
566 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
567 a8083063 Iustin Pop
  def AddInstance(self, instance):
568 a8083063 Iustin Pop
    """Add an instance to the config.
569 a8083063 Iustin Pop

570 a8083063 Iustin Pop
    This should be used after creating a new instance.
571 a8083063 Iustin Pop

572 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
573 c41eea6e Iustin Pop
    @param instance: the instance object
574 c41eea6e Iustin Pop

575 a8083063 Iustin Pop
    """
576 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
577 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
578 a8083063 Iustin Pop
579 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
580 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
581 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
582 923b1523 Iustin Pop
583 b989e85d Iustin Pop
    instance.serial_no = 1
584 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
585 a8083063 Iustin Pop
    self._WriteConfig()
586 a8083063 Iustin Pop
587 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
588 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
589 a8083063 Iustin Pop

590 a8083063 Iustin Pop
    """
591 6a408fb2 Iustin Pop
    if status not in ("up", "down"):
592 6a408fb2 Iustin Pop
      raise errors.ProgrammerError("Invalid status '%s' passed to"
593 6a408fb2 Iustin Pop
                                   " ConfigWriter._SetInstanceStatus()" %
594 6a408fb2 Iustin Pop
                                   status)
595 a8083063 Iustin Pop
596 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
597 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
598 3ecf6786 Iustin Pop
                                      instance_name)
599 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
600 455a3445 Iustin Pop
    if instance.status != status:
601 455a3445 Iustin Pop
      instance.status = status
602 b989e85d Iustin Pop
      instance.serial_no += 1
603 455a3445 Iustin Pop
      self._WriteConfig()
604 a8083063 Iustin Pop
605 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
606 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
607 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
608 6a408fb2 Iustin Pop

609 6a408fb2 Iustin Pop
    """
610 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
611 6a408fb2 Iustin Pop
612 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
613 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
614 a8083063 Iustin Pop
    """Remove the instance from the configuration.
615 a8083063 Iustin Pop

616 a8083063 Iustin Pop
    """
617 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
618 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
619 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
620 a8083063 Iustin Pop
    self._WriteConfig()
621 a8083063 Iustin Pop
622 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
623 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
624 fc95f88f Iustin Pop
    """Rename an instance.
625 fc95f88f Iustin Pop

626 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
627 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
628 fc95f88f Iustin Pop
    rename.
629 fc95f88f Iustin Pop

630 fc95f88f Iustin Pop
    """
631 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
632 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
633 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
634 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
635 fc95f88f Iustin Pop
    inst.name = new_name
636 b23c4333 Manuel Franceschini
637 b23c4333 Manuel Franceschini
    for disk in inst.disks:
638 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
639 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
640 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
641 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
642 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
643 b23c4333 Manuel Franceschini
                                                           inst.name,
644 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
645 b23c4333 Manuel Franceschini
646 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
647 fc95f88f Iustin Pop
    self._WriteConfig()
648 fc95f88f Iustin Pop
649 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
650 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
651 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
652 a8083063 Iustin Pop

653 a8083063 Iustin Pop
    """
654 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
655 a8083063 Iustin Pop
656 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
657 94bbfece Iustin Pop
    """Get the list of instances.
658 94bbfece Iustin Pop

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

661 94bbfece Iustin Pop
    """
662 94bbfece Iustin Pop
    return self._config_data.instances.keys()
663 94bbfece Iustin Pop
664 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
665 a8083063 Iustin Pop
  def GetInstanceList(self):
666 a8083063 Iustin Pop
    """Get the list of instances.
667 a8083063 Iustin Pop

668 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
669 c41eea6e Iustin Pop
        'instance1.example.com']
670 a8083063 Iustin Pop

671 a8083063 Iustin Pop
    """
672 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
673 a8083063 Iustin Pop
674 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
675 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
676 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
677 a8083063 Iustin Pop

678 a8083063 Iustin Pop
    """
679 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
680 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
681 a8083063 Iustin Pop
682 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
683 94bbfece Iustin Pop
    """Returns informations about an instance.
684 94bbfece Iustin Pop

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

687 94bbfece Iustin Pop
    """
688 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
689 94bbfece Iustin Pop
      return None
690 94bbfece Iustin Pop
691 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
692 94bbfece Iustin Pop
693 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
694 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
695 a8083063 Iustin Pop
    """Returns informations about an instance.
696 a8083063 Iustin Pop

697 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
698 a8083063 Iustin Pop
    an instance are taken from the live systems.
699 a8083063 Iustin Pop

700 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
701 c41eea6e Iustin Pop
        I{instance1.example.com}
702 a8083063 Iustin Pop

703 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
704 c41eea6e Iustin Pop
    @return: the instance object
705 a8083063 Iustin Pop

706 a8083063 Iustin Pop
    """
707 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
708 a8083063 Iustin Pop
709 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
710 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
711 0b2de758 Iustin Pop
    """Get the configuration of all instances.
712 0b2de758 Iustin Pop

713 0b2de758 Iustin Pop
    @rtype: dict
714 0b2de758 Iustin Pop
    @returns: dict of (instance, instance_info), where instance_info is what
715 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
716 0b2de758 Iustin Pop

717 0b2de758 Iustin Pop
    """
718 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
719 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
720 0b2de758 Iustin Pop
    return my_dict
721 0b2de758 Iustin Pop
722 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
723 a8083063 Iustin Pop
  def AddNode(self, node):
724 a8083063 Iustin Pop
    """Add a node to the configuration.
725 a8083063 Iustin Pop

726 c41eea6e Iustin Pop
    @type node: L{objects.Node}
727 c41eea6e Iustin Pop
    @param node: a Node instance
728 a8083063 Iustin Pop

729 a8083063 Iustin Pop
    """
730 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
731 d8470559 Michael Hanselmann
732 b989e85d Iustin Pop
    node.serial_no = 1
733 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
734 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
735 a8083063 Iustin Pop
    self._WriteConfig()
736 a8083063 Iustin Pop
737 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
738 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
739 a8083063 Iustin Pop
    """Remove a node from the configuration.
740 a8083063 Iustin Pop

741 a8083063 Iustin Pop
    """
742 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
743 d8470559 Michael Hanselmann
744 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
745 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
746 a8083063 Iustin Pop
747 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
748 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
749 a8083063 Iustin Pop
    self._WriteConfig()
750 a8083063 Iustin Pop
751 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
752 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
753 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
754 a8083063 Iustin Pop

755 a8083063 Iustin Pop
    """
756 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
757 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
758 a8083063 Iustin Pop
759 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
760 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
761 a8083063 Iustin Pop

762 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
763 c41eea6e Iustin Pop
    held.
764 f78ede4e Guido Trotter

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

767 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
768 c41eea6e Iustin Pop
    @return: the node object
769 a8083063 Iustin Pop

770 a8083063 Iustin Pop
    """
771 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
772 a8083063 Iustin Pop
      return None
773 a8083063 Iustin Pop
774 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
775 a8083063 Iustin Pop
776 f78ede4e Guido Trotter
777 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
778 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
779 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
780 f78ede4e Guido Trotter

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

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

785 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
786 c41eea6e Iustin Pop
    @return: the node object
787 f78ede4e Guido Trotter

788 f78ede4e Guido Trotter
    """
789 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
790 f78ede4e Guido Trotter
791 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
792 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
793 a8083063 Iustin Pop

794 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
795 c41eea6e Iustin Pop
    held.
796 c41eea6e Iustin Pop

797 c41eea6e Iustin Pop
    @rtype: list
798 f78ede4e Guido Trotter

799 a8083063 Iustin Pop
    """
800 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
801 a8083063 Iustin Pop
802 f78ede4e Guido Trotter
803 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
804 f78ede4e Guido Trotter
  def GetNodeList(self):
805 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
806 f78ede4e Guido Trotter

807 f78ede4e Guido Trotter
    """
808 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
809 f78ede4e Guido Trotter
810 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
811 94a02bb5 Iustin Pop
  def GetOnlineNodeList(self):
812 94a02bb5 Iustin Pop
    """Return the list of nodes which are online.
813 94a02bb5 Iustin Pop

814 94a02bb5 Iustin Pop
    """
815 94a02bb5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
816 94a02bb5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
817 94a02bb5 Iustin Pop
    return [node.name for node in all_nodes if not node.offline]
818 94a02bb5 Iustin Pop
819 94a02bb5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
820 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
821 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
822 d65e5776 Iustin Pop

823 d65e5776 Iustin Pop
    @rtype: dict
824 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
825 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
826 d65e5776 Iustin Pop

827 d65e5776 Iustin Pop
    """
828 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
829 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
830 d65e5776 Iustin Pop
    return my_dict
831 d65e5776 Iustin Pop
832 ec0292f1 Iustin Pop
  def _UnlockedGetMasterCandidateStats(self):
833 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
834 ec0292f1 Iustin Pop

835 ec0292f1 Iustin Pop
    @rtype: tuple
836 ec0292f1 Iustin Pop
    @return: tuple of (current, desired and possible)
837 ec0292f1 Iustin Pop

838 ec0292f1 Iustin Pop
    """
839 ec0292f1 Iustin Pop
    mc_now = mc_max = 0
840 ec0292f1 Iustin Pop
    for node in self._config_data.nodes.itervalues():
841 ec0292f1 Iustin Pop
      if not node.offline:
842 ec0292f1 Iustin Pop
        mc_max += 1
843 ec0292f1 Iustin Pop
      if node.master_candidate:
844 ec0292f1 Iustin Pop
        mc_now += 1
845 ec0292f1 Iustin Pop
    mc_max = min(mc_max, self._config_data.cluster.candidate_pool_size)
846 ec0292f1 Iustin Pop
    return (mc_now, mc_max)
847 ec0292f1 Iustin Pop
848 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
849 ec0292f1 Iustin Pop
  def GetMasterCandidateStats(self):
850 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
851 ec0292f1 Iustin Pop

852 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
853 ec0292f1 Iustin Pop

854 ec0292f1 Iustin Pop
    @rtype: tuple
855 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
856 ec0292f1 Iustin Pop

857 ec0292f1 Iustin Pop
    """
858 ec0292f1 Iustin Pop
    return self._UnlockedGetMasterCandidateStats()
859 ec0292f1 Iustin Pop
860 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
861 ec0292f1 Iustin Pop
  def MaintainCandidatePool(self):
862 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
863 ec0292f1 Iustin Pop

864 ec0292f1 Iustin Pop
    @rtype: list
865 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
866 ec0292f1 Iustin Pop

867 ec0292f1 Iustin Pop
    """
868 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
869 ec0292f1 Iustin Pop
    mod_list = []
870 ec0292f1 Iustin Pop
    if mc_now < mc_max:
871 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
872 ec0292f1 Iustin Pop
      random.shuffle(node_list)
873 ec0292f1 Iustin Pop
      for name in node_list:
874 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
875 ec0292f1 Iustin Pop
          break
876 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
877 ec0292f1 Iustin Pop
        if node.master_candidate or node.offline:
878 ec0292f1 Iustin Pop
          continue
879 ee513a66 Iustin Pop
        mod_list.append(node)
880 ec0292f1 Iustin Pop
        node.master_candidate = True
881 ec0292f1 Iustin Pop
        node.serial_no += 1
882 ec0292f1 Iustin Pop
        mc_now += 1
883 ec0292f1 Iustin Pop
      if mc_now != mc_max:
884 ec0292f1 Iustin Pop
        # this should not happen
885 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
886 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
887 ec0292f1 Iustin Pop
      if mod_list:
888 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
889 ec0292f1 Iustin Pop
        self._WriteConfig()
890 ec0292f1 Iustin Pop
891 ec0292f1 Iustin Pop
    return mod_list
892 ec0292f1 Iustin Pop
893 a8083063 Iustin Pop
  def _BumpSerialNo(self):
894 a8083063 Iustin Pop
    """Bump up the serial number of the config.
895 a8083063 Iustin Pop

896 a8083063 Iustin Pop
    """
897 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
898 a8083063 Iustin Pop
899 a8083063 Iustin Pop
  def _OpenConfig(self):
900 a8083063 Iustin Pop
    """Read the config data from disk.
901 a8083063 Iustin Pop

902 a8083063 Iustin Pop
    """
903 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
904 a8083063 Iustin Pop
    try:
905 a8083063 Iustin Pop
      try:
906 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
907 a8083063 Iustin Pop
      except Exception, err:
908 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
909 a8083063 Iustin Pop
    finally:
910 a8083063 Iustin Pop
      f.close()
911 5b263ed7 Michael Hanselmann
912 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
913 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
914 5b263ed7 Michael Hanselmann
915 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
916 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
917 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
918 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
919 a8083063 Iustin Pop
    self._config_data = data
920 3c7f6c44 Iustin Pop
    # reset the last serial as -1 so that the next write will cause
921 0779e3aa Iustin Pop
    # ssconf update
922 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
923 a8083063 Iustin Pop
924 a8083063 Iustin Pop
  def _DistributeConfig(self):
925 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
926 a8083063 Iustin Pop

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

930 a8083063 Iustin Pop
    """
931 a8083063 Iustin Pop
    if self._offline:
932 a8083063 Iustin Pop
      return True
933 a8083063 Iustin Pop
    bad = False
934 a8083063 Iustin Pop
935 6a5b8b4b Iustin Pop
    node_list = []
936 6a5b8b4b Iustin Pop
    addr_list = []
937 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
938 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
939 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
940 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
941 6b294c53 Iustin Pop
    # in between
942 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
943 6a5b8b4b Iustin Pop
      if node_name == myhostname:
944 6a5b8b4b Iustin Pop
        continue
945 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
946 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
947 6a5b8b4b Iustin Pop
        continue
948 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
949 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
950 6b294c53 Iustin Pop
951 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
952 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
953 6a5b8b4b Iustin Pop
    for node in node_list:
954 a8083063 Iustin Pop
      if not result[node]:
955 74a48621 Iustin Pop
        logging.error("copy of file %s to node %s failed",
956 74a48621 Iustin Pop
                      self._cfg_file, node)
957 a8083063 Iustin Pop
        bad = True
958 a8083063 Iustin Pop
    return not bad
959 a8083063 Iustin Pop
960 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
961 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
962 a8083063 Iustin Pop

963 a8083063 Iustin Pop
    """
964 a8083063 Iustin Pop
    if destination is None:
965 a8083063 Iustin Pop
      destination = self._cfg_file
966 a8083063 Iustin Pop
    self._BumpSerialNo()
967 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
968 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
969 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
970 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
971 a8083063 Iustin Pop
    try:
972 8d14b30d Iustin Pop
      f.write(txt)
973 a8083063 Iustin Pop
      os.fsync(f.fileno())
974 a8083063 Iustin Pop
    finally:
975 a8083063 Iustin Pop
      f.close()
976 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
977 a8083063 Iustin Pop
    os.rename(name, destination)
978 14e15659 Iustin Pop
    self.write_count += 1
979 3d3a04bc Iustin Pop
980 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
981 a8083063 Iustin Pop
    self._DistributeConfig()
982 a8083063 Iustin Pop
983 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
984 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
985 d9a855f1 Michael Hanselmann
      if not self._offline:
986 03d1dba2 Michael Hanselmann
        rpc.RpcRunner.call_write_ssconf_files(self._UnlockedGetNodeList(),
987 03d1dba2 Michael Hanselmann
                                              self._UnlockedGetSsconfValues())
988 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
989 54d1a06e Michael Hanselmann
990 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
991 054596f0 Iustin Pop
    """Return the values needed by ssconf.
992 054596f0 Iustin Pop

993 054596f0 Iustin Pop
    @rtype: dict
994 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
995 054596f0 Iustin Pop
        associated value
996 054596f0 Iustin Pop

997 054596f0 Iustin Pop
    """
998 a3316e4a Iustin Pop
    fn = "\n".join
999 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1000 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1001 a3316e4a Iustin Pop
1002 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
1003 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1004 a3316e4a Iustin Pop
    node_data = fn(node_names)
1005 f56618e0 Iustin Pop
1006 054596f0 Iustin Pop
    cluster = self._config_data.cluster
1007 03d1dba2 Michael Hanselmann
    return {
1008 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1009 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1010 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
1011 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
1012 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1013 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
1014 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
1015 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
1016 8a113c7a Iustin Pop
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1017 03d1dba2 Michael Hanselmann
      }
1018 03d1dba2 Michael Hanselmann
1019 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1020 f6bd6e98 Michael Hanselmann
  def InitConfig(self, version, cluster_config, master_node_config):
1021 a8083063 Iustin Pop
    """Create the initial cluster configuration.
1022 a8083063 Iustin Pop

1023 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
1024 b9eeeb02 Michael Hanselmann
    node, and no instances.
1025 a8083063 Iustin Pop

1026 f6bd6e98 Michael Hanselmann
    @type version: int
1027 f6bd6e98 Michael Hanselmann
    @param version: Configuration version
1028 b9eeeb02 Michael Hanselmann
    @type cluster_config: objects.Cluster
1029 b9eeeb02 Michael Hanselmann
    @param cluster_config: Cluster configuration
1030 b9eeeb02 Michael Hanselmann
    @type master_node_config: objects.Node
1031 b9eeeb02 Michael Hanselmann
    @param master_node_config: Master node configuration
1032 b9eeeb02 Michael Hanselmann

1033 b9eeeb02 Michael Hanselmann
    """
1034 b9eeeb02 Michael Hanselmann
    nodes = {
1035 b9eeeb02 Michael Hanselmann
      master_node_config.name: master_node_config,
1036 b9eeeb02 Michael Hanselmann
      }
1037 b9eeeb02 Michael Hanselmann
1038 f6bd6e98 Michael Hanselmann
    self._config_data = objects.ConfigData(version=version,
1039 f6bd6e98 Michael Hanselmann
                                           cluster=cluster_config,
1040 b9eeeb02 Michael Hanselmann
                                           nodes=nodes,
1041 a8083063 Iustin Pop
                                           instances={},
1042 9d38c6e1 Iustin Pop
                                           serial_no=1)
1043 a8083063 Iustin Pop
    self._WriteConfig()
1044 a8083063 Iustin Pop
1045 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1046 a8083063 Iustin Pop
  def GetVGName(self):
1047 a8083063 Iustin Pop
    """Return the volume group name.
1048 a8083063 Iustin Pop

1049 a8083063 Iustin Pop
    """
1050 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1051 a8083063 Iustin Pop
1052 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1053 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1054 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1055 89ff8e15 Manuel Franceschini

1056 89ff8e15 Manuel Franceschini
    """
1057 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1058 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1059 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1060 89ff8e15 Manuel Franceschini
1061 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1062 a8083063 Iustin Pop
  def GetDefBridge(self):
1063 a8083063 Iustin Pop
    """Return the default bridge.
1064 a8083063 Iustin Pop

1065 a8083063 Iustin Pop
    """
1066 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
1067 a8083063 Iustin Pop
1068 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1069 a8083063 Iustin Pop
  def GetMACPrefix(self):
1070 a8083063 Iustin Pop
    """Return the mac prefix.
1071 a8083063 Iustin Pop

1072 a8083063 Iustin Pop
    """
1073 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1074 62779dd0 Iustin Pop
1075 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1076 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1077 62779dd0 Iustin Pop
    """Returns informations about the cluster
1078 62779dd0 Iustin Pop

1079 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1080 c41eea6e Iustin Pop
    @return: the cluster object
1081 62779dd0 Iustin Pop

1082 62779dd0 Iustin Pop
    """
1083 62779dd0 Iustin Pop
    return self._config_data.cluster
1084 e00fb268 Iustin Pop
1085 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1086 e00fb268 Iustin Pop
  def Update(self, target):
1087 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1088 e00fb268 Iustin Pop

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

1095 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1096 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1097 c41eea6e Iustin Pop
        the cluster
1098 c41eea6e Iustin Pop

1099 e00fb268 Iustin Pop
    """
1100 e00fb268 Iustin Pop
    if self._config_data is None:
1101 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1102 3ecf6786 Iustin Pop
                                   " cannot save.")
1103 f34901f8 Iustin Pop
    update_serial = False
1104 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1105 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1106 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1107 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1108 f34901f8 Iustin Pop
      update_serial = True
1109 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1110 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1111 e00fb268 Iustin Pop
    else:
1112 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1113 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1114 e00fb268 Iustin Pop
    if not test:
1115 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1116 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1117 f34901f8 Iustin Pop
    target.serial_no += 1
1118 f34901f8 Iustin Pop
1119 cff4c037 Iustin Pop
    if update_serial:
1120 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1121 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1122 b989e85d Iustin Pop
1123 e00fb268 Iustin Pop
    self._WriteConfig()