Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ e1e75d00

History | View | Annotate | Download (40.3 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 e7d81ba0 Iustin Pop
    self._temporary_macs = set()
83 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
84 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
85 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
86 89e1fc26 Iustin Pop
    # file than after it was modified
87 89e1fc26 Iustin Pop
    self._my_hostname = utils.HostInfo().name
88 3c7f6c44 Iustin Pop
    self._last_cluster_serial = -1
89 3d3a04bc Iustin Pop
    self._OpenConfig()
90 a8083063 Iustin Pop
91 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
92 a8083063 Iustin Pop
  @staticmethod
93 a8083063 Iustin Pop
  def IsCluster():
94 a8083063 Iustin Pop
    """Check if the cluster is configured.
95 a8083063 Iustin Pop

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

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

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

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

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

137 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
138 f9518d38 Iustin Pop

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

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

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

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

174 c41eea6e Iustin Pop
    @rtype: string
175 c41eea6e Iustin Pop
    @return: the unique id
176 923b1523 Iustin Pop

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

199 c41eea6e Iustin Pop
    @rtype: list
200 c41eea6e Iustin Pop
    @return: the list of all MACs
201 c41eea6e Iustin Pop

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

213 c41eea6e Iustin Pop
    @rtype: list
214 c41eea6e Iustin Pop
    @return: the list of all DRBD secrets
215 c41eea6e Iustin Pop

216 f9518d38 Iustin Pop
    """
217 f9518d38 Iustin Pop
    def helper(disk, result):
218 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
219 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
220 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
221 f9518d38 Iustin Pop
      if disk.children:
222 f9518d38 Iustin Pop
        for child in disk.children:
223 f9518d38 Iustin Pop
          helper(child, result)
224 f9518d38 Iustin Pop
225 f9518d38 Iustin Pop
    result = []
226 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
227 f9518d38 Iustin Pop
      for disk in instance.disks:
228 f9518d38 Iustin Pop
        helper(disk, result)
229 f9518d38 Iustin Pop
230 f9518d38 Iustin Pop
    return result
231 f9518d38 Iustin Pop
232 4b98ac29 Iustin Pop
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
233 4b98ac29 Iustin Pop
    """Compute duplicate disk IDs
234 4b98ac29 Iustin Pop

235 4b98ac29 Iustin Pop
    @type disk: L{objects.Disk}
236 4b98ac29 Iustin Pop
    @param disk: the disk at which to start searching
237 4b98ac29 Iustin Pop
    @type l_ids: list
238 4b98ac29 Iustin Pop
    @param l_ids: list of current logical ids
239 4b98ac29 Iustin Pop
    @type p_ids: list
240 4b98ac29 Iustin Pop
    @param p_ids: list of current physical ids
241 4b98ac29 Iustin Pop
    @rtype: list
242 4b98ac29 Iustin Pop
    @return: a list of error messages
243 4b98ac29 Iustin Pop

244 4b98ac29 Iustin Pop
    """
245 4b98ac29 Iustin Pop
    result = []
246 25ae22e4 Iustin Pop
    if disk.logical_id is not None:
247 25ae22e4 Iustin Pop
      if disk.logical_id in l_ids:
248 25ae22e4 Iustin Pop
        result.append("duplicate logical id %s" % str(disk.logical_id))
249 25ae22e4 Iustin Pop
      else:
250 25ae22e4 Iustin Pop
        l_ids.append(disk.logical_id)
251 25ae22e4 Iustin Pop
    if disk.physical_id is not None:
252 25ae22e4 Iustin Pop
      if disk.physical_id in p_ids:
253 25ae22e4 Iustin Pop
        result.append("duplicate physical id %s" % str(disk.physical_id))
254 25ae22e4 Iustin Pop
      else:
255 25ae22e4 Iustin Pop
        p_ids.append(disk.physical_id)
256 4b98ac29 Iustin Pop
257 4b98ac29 Iustin Pop
    if disk.children:
258 4b98ac29 Iustin Pop
      for child in disk.children:
259 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
260 4b98ac29 Iustin Pop
    return result
261 4b98ac29 Iustin Pop
262 4a89c54a Iustin Pop
  def _UnlockedVerifyConfig(self):
263 a8efbb40 Iustin Pop
    """Verify function.
264 a8efbb40 Iustin Pop

265 4a89c54a Iustin Pop
    @rtype: list
266 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
267 4a89c54a Iustin Pop
        configuration errors
268 4a89c54a Iustin Pop

269 a8083063 Iustin Pop
    """
270 a8083063 Iustin Pop
    result = []
271 a8083063 Iustin Pop
    seen_macs = []
272 48ce9fd9 Iustin Pop
    ports = {}
273 a8083063 Iustin Pop
    data = self._config_data
274 4b98ac29 Iustin Pop
    seen_lids = []
275 4b98ac29 Iustin Pop
    seen_pids = []
276 a8083063 Iustin Pop
    for instance_name in data.instances:
277 a8083063 Iustin Pop
      instance = data.instances[instance_name]
278 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
279 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
280 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
281 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
282 a8083063 Iustin Pop
        if snode not in data.nodes:
283 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
284 a8083063 Iustin Pop
                        (instance_name, snode))
285 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
286 a8083063 Iustin Pop
        if nic.mac in seen_macs:
287 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
288 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
289 a8083063 Iustin Pop
        else:
290 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
291 48ce9fd9 Iustin Pop
292 48ce9fd9 Iustin Pop
      # gather the drbd ports for duplicate checks
293 48ce9fd9 Iustin Pop
      for dsk in instance.disks:
294 48ce9fd9 Iustin Pop
        if dsk.dev_type in constants.LDS_DRBD:
295 48ce9fd9 Iustin Pop
          tcp_port = dsk.logical_id[2]
296 48ce9fd9 Iustin Pop
          if tcp_port not in ports:
297 48ce9fd9 Iustin Pop
            ports[tcp_port] = []
298 48ce9fd9 Iustin Pop
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
299 48ce9fd9 Iustin Pop
      # gather network port reservation
300 48ce9fd9 Iustin Pop
      net_port = getattr(instance, "network_port", None)
301 48ce9fd9 Iustin Pop
      if net_port is not None:
302 48ce9fd9 Iustin Pop
        if net_port not in ports:
303 48ce9fd9 Iustin Pop
          ports[net_port] = []
304 48ce9fd9 Iustin Pop
        ports[net_port].append((instance.name, "network port"))
305 48ce9fd9 Iustin Pop
306 332d0e37 Iustin Pop
      # instance disk verify
307 332d0e37 Iustin Pop
      for idx, disk in enumerate(instance.disks):
308 332d0e37 Iustin Pop
        result.extend(["instance '%s' disk %d error: %s" %
309 332d0e37 Iustin Pop
                       (instance.name, idx, msg) for msg in disk.Verify()])
310 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
311 332d0e37 Iustin Pop
312 48ce9fd9 Iustin Pop
    # cluster-wide pool of free ports
313 a8efbb40 Iustin Pop
    for free_port in data.cluster.tcpudp_port_pool:
314 48ce9fd9 Iustin Pop
      if free_port not in ports:
315 48ce9fd9 Iustin Pop
        ports[free_port] = []
316 48ce9fd9 Iustin Pop
      ports[free_port].append(("cluster", "port marked as free"))
317 48ce9fd9 Iustin Pop
318 48ce9fd9 Iustin Pop
    # compute tcp/udp duplicate ports
319 48ce9fd9 Iustin Pop
    keys = ports.keys()
320 48ce9fd9 Iustin Pop
    keys.sort()
321 48ce9fd9 Iustin Pop
    for pnum in keys:
322 48ce9fd9 Iustin Pop
      pdata = ports[pnum]
323 48ce9fd9 Iustin Pop
      if len(pdata) > 1:
324 48ce9fd9 Iustin Pop
        txt = ", ".join(["%s/%s" % val for val in pdata])
325 48ce9fd9 Iustin Pop
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
326 48ce9fd9 Iustin Pop
327 48ce9fd9 Iustin Pop
    # highest used tcp port check
328 48ce9fd9 Iustin Pop
    if keys:
329 a8efbb40 Iustin Pop
      if keys[-1] > data.cluster.highest_used_port:
330 48ce9fd9 Iustin Pop
        result.append("Highest used port mismatch, saved %s, computed %s" %
331 a8efbb40 Iustin Pop
                      (data.cluster.highest_used_port, keys[-1]))
332 a8efbb40 Iustin Pop
333 3a26773f Iustin Pop
    if not data.nodes[data.cluster.master_node].master_candidate:
334 3a26773f Iustin Pop
      result.append("Master node is not a master candidate")
335 3a26773f Iustin Pop
336 4a89c54a Iustin Pop
    # master candidate checks
337 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
338 ec0292f1 Iustin Pop
    if mc_now < mc_max:
339 ec0292f1 Iustin Pop
      result.append("Not enough master candidates: actual %d, target %d" %
340 ec0292f1 Iustin Pop
                    (mc_now, mc_max))
341 48ce9fd9 Iustin Pop
342 5bf07049 Iustin Pop
    # node checks
343 5bf07049 Iustin Pop
    for node in data.nodes.values():
344 5bf07049 Iustin Pop
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
345 5bf07049 Iustin Pop
        result.append("Node %s state is invalid: master_candidate=%s,"
346 5bf07049 Iustin Pop
                      " drain=%s, offline=%s" %
347 5bf07049 Iustin Pop
                      (node.name, node.master_candidate, node.drain,
348 5bf07049 Iustin Pop
                       node.offline))
349 5bf07049 Iustin Pop
350 4a89c54a Iustin Pop
    # drbd minors check
351 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
352 4a89c54a Iustin Pop
    for node, minor, instance_a, instance_b in duplicates:
353 4a89c54a Iustin Pop
      result.append("DRBD minor %d on node %s is assigned twice to instances"
354 4a89c54a Iustin Pop
                    " %s and %s" % (minor, node, instance_a, instance_b))
355 4a89c54a Iustin Pop
356 a8083063 Iustin Pop
    return result
357 a8083063 Iustin Pop
358 4a89c54a Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
359 4a89c54a Iustin Pop
  def VerifyConfig(self):
360 4a89c54a Iustin Pop
    """Verify function.
361 4a89c54a Iustin Pop

362 4a89c54a Iustin Pop
    This is just a wrapper over L{_UnlockedVerifyConfig}.
363 4a89c54a Iustin Pop

364 4a89c54a Iustin Pop
    @rtype: list
365 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
366 4a89c54a Iustin Pop
        configuration errors
367 4a89c54a Iustin Pop

368 4a89c54a Iustin Pop
    """
369 4a89c54a Iustin Pop
    return self._UnlockedVerifyConfig()
370 4a89c54a Iustin Pop
371 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
372 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
373 a8083063 Iustin Pop

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

376 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
377 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
378 a8083063 Iustin Pop
    node.
379 a8083063 Iustin Pop

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

382 a8083063 Iustin Pop
    """
383 a8083063 Iustin Pop
    if disk.children:
384 a8083063 Iustin Pop
      for child in disk.children:
385 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
386 a8083063 Iustin Pop
387 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
388 a8083063 Iustin Pop
      return
389 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
390 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
391 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
392 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
393 3ecf6786 Iustin Pop
                                        node_name)
394 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
395 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
396 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
397 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
398 a8083063 Iustin Pop
                                        " for %s" % str(disk))
399 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
400 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
401 a8083063 Iustin Pop
      if pnode == node_name:
402 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
403 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
404 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
405 a8083063 Iustin Pop
    else:
406 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
407 a8083063 Iustin Pop
    return
408 a8083063 Iustin Pop
409 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
410 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
411 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
412 f78ede4e Guido Trotter

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

415 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
416 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
417 f78ede4e Guido Trotter
    node.
418 f78ede4e Guido Trotter

419 f78ede4e Guido Trotter
    """
420 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
421 f78ede4e Guido Trotter
422 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
423 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
424 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
425 b2fddf63 Iustin Pop

426 b2fddf63 Iustin Pop
    """
427 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
428 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
429 264bb3c5 Michael Hanselmann
430 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
431 264bb3c5 Michael Hanselmann
    self._WriteConfig()
432 264bb3c5 Michael Hanselmann
433 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
434 b2fddf63 Iustin Pop
  def GetPortList(self):
435 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
436 264bb3c5 Michael Hanselmann

437 264bb3c5 Michael Hanselmann
    """
438 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
439 264bb3c5 Michael Hanselmann
440 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
441 a8083063 Iustin Pop
  def AllocatePort(self):
442 a8083063 Iustin Pop
    """Allocate a port.
443 a8083063 Iustin Pop

444 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
445 b2fddf63 Iustin Pop
    default port range (and in this case we increase
446 b2fddf63 Iustin Pop
    highest_used_port).
447 a8083063 Iustin Pop

448 a8083063 Iustin Pop
    """
449 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
450 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
451 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
452 264bb3c5 Michael Hanselmann
    else:
453 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
454 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
455 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
456 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
457 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
458 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
459 a8083063 Iustin Pop
460 a8083063 Iustin Pop
    self._WriteConfig()
461 a8083063 Iustin Pop
    return port
462 a8083063 Iustin Pop
463 6d2e83d5 Iustin Pop
  def _UnlockedComputeDRBDMap(self):
464 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
465 a81c53c9 Iustin Pop

466 4a89c54a Iustin Pop
    @rtype: (dict, list)
467 c41eea6e Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
468 c41eea6e Iustin Pop
        the returned dict will have all the nodes in it (even if with
469 4a89c54a Iustin Pop
        an empty list), and a list of duplicates; if the duplicates
470 4a89c54a Iustin Pop
        list is not empty, the configuration is corrupted and its caller
471 4a89c54a Iustin Pop
        should raise an exception
472 a81c53c9 Iustin Pop

473 a81c53c9 Iustin Pop
    """
474 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
475 4a89c54a Iustin Pop
      duplicates = []
476 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
477 f9518d38 Iustin Pop
        nodeA, nodeB, dummy, minorA, minorB = disk.logical_id[:5]
478 a81c53c9 Iustin Pop
        for node, port in ((nodeA, minorA), (nodeB, minorB)):
479 4a89c54a Iustin Pop
          assert node in used, ("Node '%s' of instance '%s' not found"
480 4a89c54a Iustin Pop
                                " in node list" % (node, instance_name))
481 a81c53c9 Iustin Pop
          if port in used[node]:
482 4a89c54a Iustin Pop
            duplicates.append((node, port, instance_name, used[node][port]))
483 4a89c54a Iustin Pop
          else:
484 4a89c54a Iustin Pop
            used[node][port] = instance_name
485 a81c53c9 Iustin Pop
      if disk.children:
486 a81c53c9 Iustin Pop
        for child in disk.children:
487 4a89c54a Iustin Pop
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
488 4a89c54a Iustin Pop
      return duplicates
489 a81c53c9 Iustin Pop
490 4a89c54a Iustin Pop
    duplicates = []
491 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
492 79b26a7a Iustin Pop
    for instance in self._config_data.instances.itervalues():
493 79b26a7a Iustin Pop
      for disk in instance.disks:
494 79b26a7a Iustin Pop
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
495 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
496 79b26a7a Iustin Pop
      if minor in my_dict[node] and my_dict[node][minor] != instance:
497 4a89c54a Iustin Pop
        duplicates.append((node, minor, instance, my_dict[node][minor]))
498 4a89c54a Iustin Pop
      else:
499 4a89c54a Iustin Pop
        my_dict[node][minor] = instance
500 4a89c54a Iustin Pop
    return my_dict, duplicates
501 a81c53c9 Iustin Pop
502 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
503 6d2e83d5 Iustin Pop
  def ComputeDRBDMap(self):
504 6d2e83d5 Iustin Pop
    """Compute the used DRBD minor/nodes.
505 6d2e83d5 Iustin Pop

506 6d2e83d5 Iustin Pop
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
507 6d2e83d5 Iustin Pop

508 6d2e83d5 Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
509 6d2e83d5 Iustin Pop
        the returned dict will have all the nodes in it (even if with
510 6d2e83d5 Iustin Pop
        an empty list).
511 6d2e83d5 Iustin Pop

512 6d2e83d5 Iustin Pop
    """
513 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
514 4a89c54a Iustin Pop
    if duplicates:
515 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
516 4a89c54a Iustin Pop
                                      str(duplicates))
517 4a89c54a Iustin Pop
    return d_map
518 6d2e83d5 Iustin Pop
519 6d2e83d5 Iustin Pop
  @locking.ssynchronized(_config_lock)
520 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
521 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
522 a81c53c9 Iustin Pop

523 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
524 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
525 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
526 a81c53c9 Iustin Pop
    order as the passed nodes.
527 a81c53c9 Iustin Pop

528 32388e6d Iustin Pop
    @type instance: string
529 32388e6d Iustin Pop
    @param instance: the instance for which we allocate minors
530 32388e6d Iustin Pop

531 a81c53c9 Iustin Pop
    """
532 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
533 4a89c54a Iustin Pop
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
534 32388e6d Iustin Pop
535 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
536 4a89c54a Iustin Pop
    if duplicates:
537 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
538 4a89c54a Iustin Pop
                                      str(duplicates))
539 a81c53c9 Iustin Pop
    result = []
540 a81c53c9 Iustin Pop
    for nname in nodes:
541 a81c53c9 Iustin Pop
      ndata = d_map[nname]
542 a81c53c9 Iustin Pop
      if not ndata:
543 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
544 a81c53c9 Iustin Pop
        result.append(0)
545 a81c53c9 Iustin Pop
        ndata[0] = instance
546 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
547 a81c53c9 Iustin Pop
        continue
548 a81c53c9 Iustin Pop
      keys = ndata.keys()
549 a81c53c9 Iustin Pop
      keys.sort()
550 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
551 a81c53c9 Iustin Pop
      if ffree is None:
552 a81c53c9 Iustin Pop
        # return the next minor
553 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
554 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
555 a81c53c9 Iustin Pop
      else:
556 a81c53c9 Iustin Pop
        minor = ffree
557 4a89c54a Iustin Pop
      # double-check minor against current instances
558 4a89c54a Iustin Pop
      assert minor not in d_map[nname], \
559 4a89c54a Iustin Pop
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
560 4a89c54a Iustin Pop
              " already allocated to instance %s" %
561 4a89c54a Iustin Pop
              (minor, nname, d_map[nname][minor]))
562 a81c53c9 Iustin Pop
      ndata[minor] = instance
563 4a89c54a Iustin Pop
      # double-check minor against reservation
564 4a89c54a Iustin Pop
      r_key = (nname, minor)
565 4a89c54a Iustin Pop
      assert r_key not in self._temporary_drbds, \
566 4a89c54a Iustin Pop
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
567 4a89c54a Iustin Pop
              " reserved for instance %s" %
568 4a89c54a Iustin Pop
              (minor, nname, self._temporary_drbds[r_key]))
569 4a89c54a Iustin Pop
      self._temporary_drbds[r_key] = instance
570 4a89c54a Iustin Pop
      result.append(minor)
571 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
572 a81c53c9 Iustin Pop
                  nodes, result)
573 a81c53c9 Iustin Pop
    return result
574 a81c53c9 Iustin Pop
575 61cf6b5e Iustin Pop
  def _UnlockedReleaseDRBDMinors(self, instance):
576 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
577 a81c53c9 Iustin Pop

578 a81c53c9 Iustin Pop
    @type instance: string
579 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
580 a81c53c9 Iustin Pop
                     released
581 a81c53c9 Iustin Pop

582 a81c53c9 Iustin Pop
    """
583 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
584 32388e6d Iustin Pop
           "Invalid argument passed to ReleaseDRBDMinors"
585 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
586 a81c53c9 Iustin Pop
      if name == instance:
587 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
588 a81c53c9 Iustin Pop
589 61cf6b5e Iustin Pop
  @locking.ssynchronized(_config_lock)
590 61cf6b5e Iustin Pop
  def ReleaseDRBDMinors(self, instance):
591 61cf6b5e Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
592 61cf6b5e Iustin Pop

593 61cf6b5e Iustin Pop
    This should be called on the error paths, on the success paths
594 61cf6b5e Iustin Pop
    it's automatically called by the ConfigWriter add and update
595 61cf6b5e Iustin Pop
    functions.
596 61cf6b5e Iustin Pop

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

599 61cf6b5e Iustin Pop
    @type instance: string
600 61cf6b5e Iustin Pop
    @param instance: the instance for which temporary minors should be
601 61cf6b5e Iustin Pop
                     released
602 61cf6b5e Iustin Pop

603 61cf6b5e Iustin Pop
    """
604 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance)
605 61cf6b5e Iustin Pop
606 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
607 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
608 4a8b186a Michael Hanselmann
    """Get the configuration version.
609 4a8b186a Michael Hanselmann

610 4a8b186a Michael Hanselmann
    @return: Config version
611 4a8b186a Michael Hanselmann

612 4a8b186a Michael Hanselmann
    """
613 4a8b186a Michael Hanselmann
    return self._config_data.version
614 4a8b186a Michael Hanselmann
615 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
616 4a8b186a Michael Hanselmann
  def GetClusterName(self):
617 4a8b186a Michael Hanselmann
    """Get cluster name.
618 4a8b186a Michael Hanselmann

619 4a8b186a Michael Hanselmann
    @return: Cluster name
620 4a8b186a Michael Hanselmann

621 4a8b186a Michael Hanselmann
    """
622 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
623 4a8b186a Michael Hanselmann
624 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
625 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
626 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
627 4a8b186a Michael Hanselmann

628 4a8b186a Michael Hanselmann
    @return: Master hostname
629 4a8b186a Michael Hanselmann

630 4a8b186a Michael Hanselmann
    """
631 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
632 4a8b186a Michael Hanselmann
633 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
634 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
635 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
636 4a8b186a Michael Hanselmann

637 4a8b186a Michael Hanselmann
    @return: Master IP
638 4a8b186a Michael Hanselmann

639 4a8b186a Michael Hanselmann
    """
640 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
641 4a8b186a Michael Hanselmann
642 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
643 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
644 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
645 4a8b186a Michael Hanselmann

646 4a8b186a Michael Hanselmann
    """
647 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
648 4a8b186a Michael Hanselmann
649 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
650 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
651 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
652 4a8b186a Michael Hanselmann

653 4a8b186a Michael Hanselmann
    """
654 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
655 4a8b186a Michael Hanselmann
656 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
657 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
658 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
659 4a8b186a Michael Hanselmann

660 4a8b186a Michael Hanselmann
    """
661 64272529 Iustin Pop
    return self._config_data.cluster.default_hypervisor
662 4a8b186a Michael Hanselmann
663 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
664 a8083063 Iustin Pop
  def GetHostKey(self):
665 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
666 a8083063 Iustin Pop

667 c41eea6e Iustin Pop
    @rtype: string
668 c41eea6e Iustin Pop
    @return: the rsa hostkey
669 a8083063 Iustin Pop

670 a8083063 Iustin Pop
    """
671 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
672 a8083063 Iustin Pop
673 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
674 a8083063 Iustin Pop
  def AddInstance(self, instance):
675 a8083063 Iustin Pop
    """Add an instance to the config.
676 a8083063 Iustin Pop

677 a8083063 Iustin Pop
    This should be used after creating a new instance.
678 a8083063 Iustin Pop

679 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
680 c41eea6e Iustin Pop
    @param instance: the instance object
681 c41eea6e Iustin Pop

682 a8083063 Iustin Pop
    """
683 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
684 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
685 a8083063 Iustin Pop
686 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
687 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
688 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
689 923b1523 Iustin Pop
690 e4640214 Guido Trotter
    all_macs = self._AllMACs()
691 e4640214 Guido Trotter
    for nic in instance.nics:
692 e4640214 Guido Trotter
      if nic.mac in all_macs:
693 e4640214 Guido Trotter
        raise errors.ConfigurationError("Cannot add instance %s:"
694 e4640214 Guido Trotter
          " MAC address '%s' already in use." % (instance.name, nic.mac))
695 e4640214 Guido Trotter
696 b989e85d Iustin Pop
    instance.serial_no = 1
697 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
698 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
699 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance.name)
700 e7d81ba0 Iustin Pop
    for nic in instance.nics:
701 e7d81ba0 Iustin Pop
      self._temporary_macs.discard(nic.mac)
702 a8083063 Iustin Pop
    self._WriteConfig()
703 a8083063 Iustin Pop
704 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
705 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
706 a8083063 Iustin Pop

707 a8083063 Iustin Pop
    """
708 0d68c45d Iustin Pop
    assert isinstance(status, bool), \
709 0d68c45d Iustin Pop
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
710 a8083063 Iustin Pop
711 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
712 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
713 3ecf6786 Iustin Pop
                                      instance_name)
714 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
715 0d68c45d Iustin Pop
    if instance.admin_up != status:
716 0d68c45d Iustin Pop
      instance.admin_up = status
717 b989e85d Iustin Pop
      instance.serial_no += 1
718 455a3445 Iustin Pop
      self._WriteConfig()
719 a8083063 Iustin Pop
720 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
721 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
722 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
723 6a408fb2 Iustin Pop

724 6a408fb2 Iustin Pop
    """
725 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, True)
726 6a408fb2 Iustin Pop
727 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
728 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
729 a8083063 Iustin Pop
    """Remove the instance from the configuration.
730 a8083063 Iustin Pop

731 a8083063 Iustin Pop
    """
732 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
733 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
734 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
735 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
736 a8083063 Iustin Pop
    self._WriteConfig()
737 a8083063 Iustin Pop
738 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
739 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
740 fc95f88f Iustin Pop
    """Rename an instance.
741 fc95f88f Iustin Pop

742 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
743 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
744 fc95f88f Iustin Pop
    rename.
745 fc95f88f Iustin Pop

746 fc95f88f Iustin Pop
    """
747 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
748 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
749 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
750 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
751 fc95f88f Iustin Pop
    inst.name = new_name
752 b23c4333 Manuel Franceschini
753 b23c4333 Manuel Franceschini
    for disk in inst.disks:
754 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
755 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
756 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
757 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
758 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
759 b23c4333 Manuel Franceschini
                                                           inst.name,
760 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
761 b23c4333 Manuel Franceschini
762 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
763 fc95f88f Iustin Pop
    self._WriteConfig()
764 fc95f88f Iustin Pop
765 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
766 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
767 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
768 a8083063 Iustin Pop

769 a8083063 Iustin Pop
    """
770 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, False)
771 a8083063 Iustin Pop
772 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
773 94bbfece Iustin Pop
    """Get the list of instances.
774 94bbfece Iustin Pop

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

777 94bbfece Iustin Pop
    """
778 94bbfece Iustin Pop
    return self._config_data.instances.keys()
779 94bbfece Iustin Pop
780 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
781 a8083063 Iustin Pop
  def GetInstanceList(self):
782 a8083063 Iustin Pop
    """Get the list of instances.
783 a8083063 Iustin Pop

784 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
785 c41eea6e Iustin Pop
        'instance1.example.com']
786 a8083063 Iustin Pop

787 a8083063 Iustin Pop
    """
788 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
789 a8083063 Iustin Pop
790 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
791 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
792 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
793 a8083063 Iustin Pop

794 a8083063 Iustin Pop
    """
795 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
796 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
797 a8083063 Iustin Pop
798 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
799 94bbfece Iustin Pop
    """Returns informations about an instance.
800 94bbfece Iustin Pop

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

803 94bbfece Iustin Pop
    """
804 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
805 94bbfece Iustin Pop
      return None
806 94bbfece Iustin Pop
807 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
808 94bbfece Iustin Pop
809 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
810 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
811 a8083063 Iustin Pop
    """Returns informations about an instance.
812 a8083063 Iustin Pop

813 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
814 a8083063 Iustin Pop
    an instance are taken from the live systems.
815 a8083063 Iustin Pop

816 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
817 c41eea6e Iustin Pop
        I{instance1.example.com}
818 a8083063 Iustin Pop

819 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
820 c41eea6e Iustin Pop
    @return: the instance object
821 a8083063 Iustin Pop

822 a8083063 Iustin Pop
    """
823 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
824 a8083063 Iustin Pop
825 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
826 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
827 0b2de758 Iustin Pop
    """Get the configuration of all instances.
828 0b2de758 Iustin Pop

829 0b2de758 Iustin Pop
    @rtype: dict
830 5fcc718f Iustin Pop
    @return: dict of (instance, instance_info), where instance_info is what
831 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
832 0b2de758 Iustin Pop

833 0b2de758 Iustin Pop
    """
834 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
835 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
836 0b2de758 Iustin Pop
    return my_dict
837 0b2de758 Iustin Pop
838 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
839 a8083063 Iustin Pop
  def AddNode(self, node):
840 a8083063 Iustin Pop
    """Add a node to the configuration.
841 a8083063 Iustin Pop

842 c41eea6e Iustin Pop
    @type node: L{objects.Node}
843 c41eea6e Iustin Pop
    @param node: a Node instance
844 a8083063 Iustin Pop

845 a8083063 Iustin Pop
    """
846 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
847 d8470559 Michael Hanselmann
848 b989e85d Iustin Pop
    node.serial_no = 1
849 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
850 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
851 a8083063 Iustin Pop
    self._WriteConfig()
852 a8083063 Iustin Pop
853 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
854 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
855 a8083063 Iustin Pop
    """Remove a node from the configuration.
856 a8083063 Iustin Pop

857 a8083063 Iustin Pop
    """
858 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
859 d8470559 Michael Hanselmann
860 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
861 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
862 a8083063 Iustin Pop
863 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
864 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
865 a8083063 Iustin Pop
    self._WriteConfig()
866 a8083063 Iustin Pop
867 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
868 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
869 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
870 a8083063 Iustin Pop

871 a8083063 Iustin Pop
    """
872 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
873 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
874 a8083063 Iustin Pop
875 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
876 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
877 a8083063 Iustin Pop

878 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
879 c41eea6e Iustin Pop
    held.
880 f78ede4e Guido Trotter

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

883 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
884 c41eea6e Iustin Pop
    @return: the node object
885 a8083063 Iustin Pop

886 a8083063 Iustin Pop
    """
887 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
888 a8083063 Iustin Pop
      return None
889 a8083063 Iustin Pop
890 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
891 a8083063 Iustin Pop
892 f78ede4e Guido Trotter
893 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
894 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
895 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
896 f78ede4e Guido Trotter

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

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

901 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
902 c41eea6e Iustin Pop
    @return: the node object
903 f78ede4e Guido Trotter

904 f78ede4e Guido Trotter
    """
905 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
906 f78ede4e Guido Trotter
907 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
908 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
909 a8083063 Iustin Pop

910 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
911 c41eea6e Iustin Pop
    held.
912 c41eea6e Iustin Pop

913 c41eea6e Iustin Pop
    @rtype: list
914 f78ede4e Guido Trotter

915 a8083063 Iustin Pop
    """
916 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
917 a8083063 Iustin Pop
918 f78ede4e Guido Trotter
919 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
920 f78ede4e Guido Trotter
  def GetNodeList(self):
921 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
922 f78ede4e Guido Trotter

923 f78ede4e Guido Trotter
    """
924 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
925 f78ede4e Guido Trotter
926 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
927 94a02bb5 Iustin Pop
  def GetOnlineNodeList(self):
928 94a02bb5 Iustin Pop
    """Return the list of nodes which are online.
929 94a02bb5 Iustin Pop

930 94a02bb5 Iustin Pop
    """
931 94a02bb5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
932 94a02bb5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
933 94a02bb5 Iustin Pop
    return [node.name for node in all_nodes if not node.offline]
934 94a02bb5 Iustin Pop
935 94a02bb5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
936 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
937 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
938 d65e5776 Iustin Pop

939 d65e5776 Iustin Pop
    @rtype: dict
940 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
941 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
942 d65e5776 Iustin Pop

943 d65e5776 Iustin Pop
    """
944 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
945 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
946 d65e5776 Iustin Pop
    return my_dict
947 d65e5776 Iustin Pop
948 ec0292f1 Iustin Pop
  def _UnlockedGetMasterCandidateStats(self):
949 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
950 ec0292f1 Iustin Pop

951 ec0292f1 Iustin Pop
    @rtype: tuple
952 ec0292f1 Iustin Pop
    @return: tuple of (current, desired and possible)
953 ec0292f1 Iustin Pop

954 ec0292f1 Iustin Pop
    """
955 ec0292f1 Iustin Pop
    mc_now = mc_max = 0
956 ec0292f1 Iustin Pop
    for node in self._config_data.nodes.itervalues():
957 5bf07049 Iustin Pop
      if not (node.offline or node.drained):
958 ec0292f1 Iustin Pop
        mc_max += 1
959 ec0292f1 Iustin Pop
      if node.master_candidate:
960 ec0292f1 Iustin Pop
        mc_now += 1
961 ec0292f1 Iustin Pop
    mc_max = min(mc_max, self._config_data.cluster.candidate_pool_size)
962 ec0292f1 Iustin Pop
    return (mc_now, mc_max)
963 ec0292f1 Iustin Pop
964 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
965 ec0292f1 Iustin Pop
  def GetMasterCandidateStats(self):
966 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
967 ec0292f1 Iustin Pop

968 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
969 ec0292f1 Iustin Pop

970 ec0292f1 Iustin Pop
    @rtype: tuple
971 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
972 ec0292f1 Iustin Pop

973 ec0292f1 Iustin Pop
    """
974 ec0292f1 Iustin Pop
    return self._UnlockedGetMasterCandidateStats()
975 ec0292f1 Iustin Pop
976 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
977 ec0292f1 Iustin Pop
  def MaintainCandidatePool(self):
978 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
979 ec0292f1 Iustin Pop

980 ec0292f1 Iustin Pop
    @rtype: list
981 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
982 ec0292f1 Iustin Pop

983 ec0292f1 Iustin Pop
    """
984 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
985 ec0292f1 Iustin Pop
    mod_list = []
986 ec0292f1 Iustin Pop
    if mc_now < mc_max:
987 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
988 ec0292f1 Iustin Pop
      random.shuffle(node_list)
989 ec0292f1 Iustin Pop
      for name in node_list:
990 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
991 ec0292f1 Iustin Pop
          break
992 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
993 5bf07049 Iustin Pop
        if node.master_candidate or node.offline or node.drained:
994 ec0292f1 Iustin Pop
          continue
995 ee513a66 Iustin Pop
        mod_list.append(node)
996 ec0292f1 Iustin Pop
        node.master_candidate = True
997 ec0292f1 Iustin Pop
        node.serial_no += 1
998 ec0292f1 Iustin Pop
        mc_now += 1
999 ec0292f1 Iustin Pop
      if mc_now != mc_max:
1000 ec0292f1 Iustin Pop
        # this should not happen
1001 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1002 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1003 ec0292f1 Iustin Pop
      if mod_list:
1004 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
1005 ec0292f1 Iustin Pop
        self._WriteConfig()
1006 ec0292f1 Iustin Pop
1007 ec0292f1 Iustin Pop
    return mod_list
1008 ec0292f1 Iustin Pop
1009 a8083063 Iustin Pop
  def _BumpSerialNo(self):
1010 a8083063 Iustin Pop
    """Bump up the serial number of the config.
1011 a8083063 Iustin Pop

1012 a8083063 Iustin Pop
    """
1013 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
1014 a8083063 Iustin Pop
1015 a8083063 Iustin Pop
  def _OpenConfig(self):
1016 a8083063 Iustin Pop
    """Read the config data from disk.
1017 a8083063 Iustin Pop

1018 a8083063 Iustin Pop
    """
1019 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
1020 a8083063 Iustin Pop
    try:
1021 a8083063 Iustin Pop
      try:
1022 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
1023 a8083063 Iustin Pop
      except Exception, err:
1024 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
1025 a8083063 Iustin Pop
    finally:
1026 a8083063 Iustin Pop
      f.close()
1027 5b263ed7 Michael Hanselmann
1028 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
1029 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
1030 5b263ed7 Michael Hanselmann
1031 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
1032 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
1033 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
1034 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
1035 a8083063 Iustin Pop
    self._config_data = data
1036 3c7f6c44 Iustin Pop
    # reset the last serial as -1 so that the next write will cause
1037 0779e3aa Iustin Pop
    # ssconf update
1038 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
1039 a8083063 Iustin Pop
1040 a8083063 Iustin Pop
  def _DistributeConfig(self):
1041 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
1042 a8083063 Iustin Pop

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

1046 a8083063 Iustin Pop
    """
1047 a8083063 Iustin Pop
    if self._offline:
1048 a8083063 Iustin Pop
      return True
1049 a8083063 Iustin Pop
    bad = False
1050 a8083063 Iustin Pop
1051 6a5b8b4b Iustin Pop
    node_list = []
1052 6a5b8b4b Iustin Pop
    addr_list = []
1053 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
1054 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
1055 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
1056 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
1057 6b294c53 Iustin Pop
    # in between
1058 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
1059 6a5b8b4b Iustin Pop
      if node_name == myhostname:
1060 6a5b8b4b Iustin Pop
        continue
1061 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
1062 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
1063 6a5b8b4b Iustin Pop
        continue
1064 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
1065 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
1066 6b294c53 Iustin Pop
1067 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1068 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
1069 1b54fc6c Guido Trotter
    for to_node, to_result in result.items():
1070 1b54fc6c Guido Trotter
      msg = to_result.RemoteFailMsg()
1071 1b54fc6c Guido Trotter
      if msg:
1072 1b54fc6c Guido Trotter
        msg = ("Copy of file %s to node %s failed: %s" %
1073 dd7db360 Iustin Pop
               (self._cfg_file, to_node, msg))
1074 1b54fc6c Guido Trotter
        logging.error(msg)
1075 a8083063 Iustin Pop
        bad = True
1076 a8083063 Iustin Pop
    return not bad
1077 a8083063 Iustin Pop
1078 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
1079 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
1080 a8083063 Iustin Pop

1081 a8083063 Iustin Pop
    """
1082 4a89c54a Iustin Pop
    config_errors = self._UnlockedVerifyConfig()
1083 4a89c54a Iustin Pop
    if config_errors:
1084 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Configuration data is not"
1085 4a89c54a Iustin Pop
                                      " consistent: %s" %
1086 4a89c54a Iustin Pop
                                      (", ".join(config_errors)))
1087 a8083063 Iustin Pop
    if destination is None:
1088 a8083063 Iustin Pop
      destination = self._cfg_file
1089 a8083063 Iustin Pop
    self._BumpSerialNo()
1090 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
1091 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
1092 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
1093 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
1094 a8083063 Iustin Pop
    try:
1095 8d14b30d Iustin Pop
      f.write(txt)
1096 a8083063 Iustin Pop
      os.fsync(f.fileno())
1097 a8083063 Iustin Pop
    finally:
1098 a8083063 Iustin Pop
      f.close()
1099 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
1100 a8083063 Iustin Pop
    os.rename(name, destination)
1101 14e15659 Iustin Pop
    self.write_count += 1
1102 3d3a04bc Iustin Pop
1103 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
1104 a8083063 Iustin Pop
    self._DistributeConfig()
1105 a8083063 Iustin Pop
1106 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
1107 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
1108 d9a855f1 Michael Hanselmann
      if not self._offline:
1109 e1e75d00 Iustin Pop
        result = rpc.RpcRunner.call_write_ssconf_files(\
1110 e1e75d00 Iustin Pop
          self._UnlockedGetNodeList(),
1111 e1e75d00 Iustin Pop
          self._UnlockedGetSsconfValues())
1112 e1e75d00 Iustin Pop
        for nname, nresu in result.items():
1113 e1e75d00 Iustin Pop
          msg = nresu.RemoteFailMsg()
1114 e1e75d00 Iustin Pop
          if msg:
1115 e1e75d00 Iustin Pop
            logging.warning("Error while uploading ssconf files to"
1116 e1e75d00 Iustin Pop
                            " node %s: %s", nname, msg)
1117 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
1118 54d1a06e Michael Hanselmann
1119 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
1120 054596f0 Iustin Pop
    """Return the values needed by ssconf.
1121 054596f0 Iustin Pop

1122 054596f0 Iustin Pop
    @rtype: dict
1123 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
1124 054596f0 Iustin Pop
        associated value
1125 054596f0 Iustin Pop

1126 054596f0 Iustin Pop
    """
1127 a3316e4a Iustin Pop
    fn = "\n".join
1128 81a49123 Iustin Pop
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1129 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1130 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1131 a3316e4a Iustin Pop
1132 81a49123 Iustin Pop
    instance_data = fn(instance_names)
1133 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
1134 81a49123 Iustin Pop
    on_data = fn(node.name for node in node_info if not node.offline)
1135 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1136 a3316e4a Iustin Pop
    node_data = fn(node_names)
1137 f56618e0 Iustin Pop
1138 054596f0 Iustin Pop
    cluster = self._config_data.cluster
1139 5d60b3bd Iustin Pop
    cluster_tags = fn(cluster.GetTags())
1140 03d1dba2 Michael Hanselmann
    return {
1141 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1142 5d60b3bd Iustin Pop
      constants.SS_CLUSTER_TAGS: cluster_tags,
1143 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1144 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
1145 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
1146 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1147 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
1148 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
1149 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
1150 81a49123 Iustin Pop
      constants.SS_ONLINE_NODES: on_data,
1151 81a49123 Iustin Pop
      constants.SS_INSTANCE_LIST: instance_data,
1152 8a113c7a Iustin Pop
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1153 03d1dba2 Michael Hanselmann
      }
1154 03d1dba2 Michael Hanselmann
1155 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1156 f6bd6e98 Michael Hanselmann
  def InitConfig(self, version, cluster_config, master_node_config):
1157 a8083063 Iustin Pop
    """Create the initial cluster configuration.
1158 a8083063 Iustin Pop

1159 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
1160 b9eeeb02 Michael Hanselmann
    node, and no instances.
1161 a8083063 Iustin Pop

1162 f6bd6e98 Michael Hanselmann
    @type version: int
1163 f6bd6e98 Michael Hanselmann
    @param version: Configuration version
1164 b9eeeb02 Michael Hanselmann
    @type cluster_config: objects.Cluster
1165 b9eeeb02 Michael Hanselmann
    @param cluster_config: Cluster configuration
1166 b9eeeb02 Michael Hanselmann
    @type master_node_config: objects.Node
1167 b9eeeb02 Michael Hanselmann
    @param master_node_config: Master node configuration
1168 b9eeeb02 Michael Hanselmann

1169 b9eeeb02 Michael Hanselmann
    """
1170 b9eeeb02 Michael Hanselmann
    nodes = {
1171 b9eeeb02 Michael Hanselmann
      master_node_config.name: master_node_config,
1172 b9eeeb02 Michael Hanselmann
      }
1173 b9eeeb02 Michael Hanselmann
1174 f6bd6e98 Michael Hanselmann
    self._config_data = objects.ConfigData(version=version,
1175 f6bd6e98 Michael Hanselmann
                                           cluster=cluster_config,
1176 b9eeeb02 Michael Hanselmann
                                           nodes=nodes,
1177 a8083063 Iustin Pop
                                           instances={},
1178 9d38c6e1 Iustin Pop
                                           serial_no=1)
1179 a8083063 Iustin Pop
    self._WriteConfig()
1180 a8083063 Iustin Pop
1181 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1182 a8083063 Iustin Pop
  def GetVGName(self):
1183 a8083063 Iustin Pop
    """Return the volume group name.
1184 a8083063 Iustin Pop

1185 a8083063 Iustin Pop
    """
1186 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1187 a8083063 Iustin Pop
1188 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1189 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1190 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1191 89ff8e15 Manuel Franceschini

1192 89ff8e15 Manuel Franceschini
    """
1193 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1194 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1195 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1196 89ff8e15 Manuel Franceschini
1197 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1198 a8083063 Iustin Pop
  def GetMACPrefix(self):
1199 a8083063 Iustin Pop
    """Return the mac prefix.
1200 a8083063 Iustin Pop

1201 a8083063 Iustin Pop
    """
1202 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1203 62779dd0 Iustin Pop
1204 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1205 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1206 62779dd0 Iustin Pop
    """Returns informations about the cluster
1207 62779dd0 Iustin Pop

1208 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1209 c41eea6e Iustin Pop
    @return: the cluster object
1210 62779dd0 Iustin Pop

1211 62779dd0 Iustin Pop
    """
1212 62779dd0 Iustin Pop
    return self._config_data.cluster
1213 e00fb268 Iustin Pop
1214 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1215 e00fb268 Iustin Pop
  def Update(self, target):
1216 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1217 e00fb268 Iustin Pop

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

1224 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1225 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1226 c41eea6e Iustin Pop
        the cluster
1227 c41eea6e Iustin Pop

1228 e00fb268 Iustin Pop
    """
1229 e00fb268 Iustin Pop
    if self._config_data is None:
1230 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1231 3ecf6786 Iustin Pop
                                   " cannot save.")
1232 f34901f8 Iustin Pop
    update_serial = False
1233 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1234 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1235 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1236 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1237 f34901f8 Iustin Pop
      update_serial = True
1238 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1239 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1240 e00fb268 Iustin Pop
    else:
1241 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1242 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1243 e00fb268 Iustin Pop
    if not test:
1244 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1245 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1246 f34901f8 Iustin Pop
    target.serial_no += 1
1247 f34901f8 Iustin Pop
1248 cff4c037 Iustin Pop
    if update_serial:
1249 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1250 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1251 b989e85d Iustin Pop
1252 61cf6b5e Iustin Pop
    if isinstance(target, objects.Instance):
1253 61cf6b5e Iustin Pop
      self._UnlockedReleaseDRBDMinors(target.name)
1254 e7d81ba0 Iustin Pop
      for nic in target.nics:
1255 e7d81ba0 Iustin Pop
        self._temporary_macs.discard(nic.mac)
1256 61cf6b5e Iustin Pop
1257 e00fb268 Iustin Pop
    self._WriteConfig()