Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 23828f1c

History | View | Annotate | Download (29.4 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 5b263ed7 Michael Hanselmann
  if data.version != constants.CONFIG_VERSION:
53 243cdbcc Michael Hanselmann
    raise errors.ConfigurationError("Cluster configuration version"
54 243cdbcc Michael Hanselmann
                                    " mismatch, got %s instead of %s" %
55 5b263ed7 Michael Hanselmann
                                    (data.version,
56 243cdbcc Michael Hanselmann
                                     constants.CONFIG_VERSION))
57 a8083063 Iustin Pop
58 319856a9 Michael Hanselmann
59 a8083063 Iustin Pop
class ConfigWriter:
60 098c0958 Michael Hanselmann
  """The interface to the cluster configuration.
61 a8083063 Iustin Pop

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

86 a8083063 Iustin Pop
    """
87 a8083063 Iustin Pop
    return os.path.exists(constants.CLUSTER_CONF_FILE)
88 a8083063 Iustin Pop
89 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
90 a8083063 Iustin Pop
  def GenerateMAC(self):
91 a8083063 Iustin Pop
    """Generate a MAC for an instance.
92 a8083063 Iustin Pop

93 a8083063 Iustin Pop
    This should check the current instances for duplicates.
94 a8083063 Iustin Pop

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

115 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
116 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
117 1862d460 Alexander Schreiber

118 1862d460 Alexander Schreiber
    """
119 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
120 1862d460 Alexander Schreiber
    return mac in all_macs
121 1862d460 Alexander Schreiber
122 f9518d38 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
123 f9518d38 Iustin Pop
  def GenerateDRBDSecret(self):
124 f9518d38 Iustin Pop
    """Generate a DRBD secret.
125 f9518d38 Iustin Pop

126 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
127 f9518d38 Iustin Pop

128 f9518d38 Iustin Pop
    """
129 f9518d38 Iustin Pop
    all_secrets = self._AllDRBDSecrets()
130 f9518d38 Iustin Pop
    retries = 64
131 f9518d38 Iustin Pop
    while retries > 0:
132 f9518d38 Iustin Pop
      secret = utils.GenerateSecret()
133 f9518d38 Iustin Pop
      if secret not in all_secrets:
134 f9518d38 Iustin Pop
        break
135 f9518d38 Iustin Pop
      retries -= 1
136 f9518d38 Iustin Pop
    else:
137 f9518d38 Iustin Pop
      raise errors.ConfigurationError("Can't generate unique DRBD secret")
138 f9518d38 Iustin Pop
    return secret
139 f9518d38 Iustin Pop
140 923b1523 Iustin Pop
  def _ComputeAllLVs(self):
141 923b1523 Iustin Pop
    """Compute the list of all LVs.
142 923b1523 Iustin Pop

143 923b1523 Iustin Pop
    """
144 923b1523 Iustin Pop
    lvnames = set()
145 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
146 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
147 923b1523 Iustin Pop
      for lv_list in node_data.values():
148 923b1523 Iustin Pop
        lvnames.update(lv_list)
149 923b1523 Iustin Pop
    return lvnames
150 923b1523 Iustin Pop
151 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
152 923b1523 Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
153 923b1523 Iustin Pop
    """Generate an unique disk name.
154 923b1523 Iustin Pop

155 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
156 923b1523 Iustin Pop
    duplicates.
157 923b1523 Iustin Pop

158 923b1523 Iustin Pop
    Args:
159 923b1523 Iustin Pop
      - exceptions: a list with some other names which should be checked
160 923b1523 Iustin Pop
                    for uniqueness (used for example when you want to get
161 923b1523 Iustin Pop
                    more than one id at one time without adding each one in
162 923b1523 Iustin Pop
                    turn to the config file
163 923b1523 Iustin Pop

164 923b1523 Iustin Pop
    Returns: the unique id as a string
165 923b1523 Iustin Pop

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

188 a8083063 Iustin Pop
    """
189 a8083063 Iustin Pop
    result = []
190 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
191 a8083063 Iustin Pop
      for nic in instance.nics:
192 a8083063 Iustin Pop
        result.append(nic.mac)
193 a8083063 Iustin Pop
194 a8083063 Iustin Pop
    return result
195 a8083063 Iustin Pop
196 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
197 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
198 f9518d38 Iustin Pop

199 f9518d38 Iustin Pop
    """
200 f9518d38 Iustin Pop
    def helper(disk, result):
201 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
202 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
203 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
204 f9518d38 Iustin Pop
      if disk.children:
205 f9518d38 Iustin Pop
        for child in disk.children:
206 f9518d38 Iustin Pop
          helper(child, result)
207 f9518d38 Iustin Pop
208 f9518d38 Iustin Pop
    result = []
209 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
210 f9518d38 Iustin Pop
      for disk in instance.disks:
211 f9518d38 Iustin Pop
        helper(disk, result)
212 f9518d38 Iustin Pop
213 f9518d38 Iustin Pop
    return result
214 f9518d38 Iustin Pop
215 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
216 a8083063 Iustin Pop
  def VerifyConfig(self):
217 a8083063 Iustin Pop
    """Stub verify function.
218 a8083063 Iustin Pop
    """
219 a8083063 Iustin Pop
    result = []
220 a8083063 Iustin Pop
    seen_macs = []
221 48ce9fd9 Iustin Pop
    ports = {}
222 a8083063 Iustin Pop
    data = self._config_data
223 a8083063 Iustin Pop
    for instance_name in data.instances:
224 a8083063 Iustin Pop
      instance = data.instances[instance_name]
225 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
226 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
227 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
228 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
229 a8083063 Iustin Pop
        if snode not in data.nodes:
230 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
231 a8083063 Iustin Pop
                        (instance_name, snode))
232 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
233 a8083063 Iustin Pop
        if nic.mac in seen_macs:
234 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
235 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
236 a8083063 Iustin Pop
        else:
237 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
238 48ce9fd9 Iustin Pop
239 48ce9fd9 Iustin Pop
      # gather the drbd ports for duplicate checks
240 48ce9fd9 Iustin Pop
      for dsk in instance.disks:
241 48ce9fd9 Iustin Pop
        if dsk.dev_type in constants.LDS_DRBD:
242 48ce9fd9 Iustin Pop
          tcp_port = dsk.logical_id[2]
243 48ce9fd9 Iustin Pop
          if tcp_port not in ports:
244 48ce9fd9 Iustin Pop
            ports[tcp_port] = []
245 48ce9fd9 Iustin Pop
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
246 48ce9fd9 Iustin Pop
      # gather network port reservation
247 48ce9fd9 Iustin Pop
      net_port = getattr(instance, "network_port", None)
248 48ce9fd9 Iustin Pop
      if net_port is not None:
249 48ce9fd9 Iustin Pop
        if net_port not in ports:
250 48ce9fd9 Iustin Pop
          ports[net_port] = []
251 48ce9fd9 Iustin Pop
        ports[net_port].append((instance.name, "network port"))
252 48ce9fd9 Iustin Pop
253 48ce9fd9 Iustin Pop
    # cluster-wide pool of free ports
254 48ce9fd9 Iustin Pop
    for free_port in self._config_data.cluster.tcpudp_port_pool:
255 48ce9fd9 Iustin Pop
      if free_port not in ports:
256 48ce9fd9 Iustin Pop
        ports[free_port] = []
257 48ce9fd9 Iustin Pop
      ports[free_port].append(("cluster", "port marked as free"))
258 48ce9fd9 Iustin Pop
259 48ce9fd9 Iustin Pop
    # compute tcp/udp duplicate ports
260 48ce9fd9 Iustin Pop
    keys = ports.keys()
261 48ce9fd9 Iustin Pop
    keys.sort()
262 48ce9fd9 Iustin Pop
    for pnum in keys:
263 48ce9fd9 Iustin Pop
      pdata = ports[pnum]
264 48ce9fd9 Iustin Pop
      if len(pdata) > 1:
265 48ce9fd9 Iustin Pop
        txt = ", ".join(["%s/%s" % val for val in pdata])
266 48ce9fd9 Iustin Pop
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
267 48ce9fd9 Iustin Pop
268 48ce9fd9 Iustin Pop
    # highest used tcp port check
269 48ce9fd9 Iustin Pop
    if keys:
270 48ce9fd9 Iustin Pop
      if keys[-1] > self._config_data.cluster.highest_used_port:
271 48ce9fd9 Iustin Pop
        result.append("Highest used port mismatch, saved %s, computed %s" %
272 48ce9fd9 Iustin Pop
                      (self._config_data.cluster.highest_used_port,
273 48ce9fd9 Iustin Pop
                       keys[-1]))
274 48ce9fd9 Iustin Pop
275 a8083063 Iustin Pop
    return result
276 a8083063 Iustin Pop
277 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
278 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
279 a8083063 Iustin Pop

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

282 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
283 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
284 a8083063 Iustin Pop
    node.
285 a8083063 Iustin Pop

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

288 a8083063 Iustin Pop
    """
289 a8083063 Iustin Pop
    if disk.children:
290 a8083063 Iustin Pop
      for child in disk.children:
291 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
292 a8083063 Iustin Pop
293 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
294 a8083063 Iustin Pop
      return
295 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
296 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
297 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
298 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
299 3ecf6786 Iustin Pop
                                        node_name)
300 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
301 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
302 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
303 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
304 a8083063 Iustin Pop
                                        " for %s" % str(disk))
305 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
306 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
307 a8083063 Iustin Pop
      if pnode == node_name:
308 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
309 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
310 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
311 a8083063 Iustin Pop
    else:
312 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
313 a8083063 Iustin Pop
    return
314 a8083063 Iustin Pop
315 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
316 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
317 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
318 f78ede4e Guido Trotter

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

321 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
322 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
323 f78ede4e Guido Trotter
    node.
324 f78ede4e Guido Trotter

325 f78ede4e Guido Trotter
    """
326 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
327 f78ede4e Guido Trotter
328 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
329 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
330 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
331 b2fddf63 Iustin Pop

332 b2fddf63 Iustin Pop
    """
333 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
334 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
335 264bb3c5 Michael Hanselmann
336 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
337 264bb3c5 Michael Hanselmann
    self._WriteConfig()
338 264bb3c5 Michael Hanselmann
339 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
340 b2fddf63 Iustin Pop
  def GetPortList(self):
341 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
342 264bb3c5 Michael Hanselmann

343 264bb3c5 Michael Hanselmann
    """
344 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
345 264bb3c5 Michael Hanselmann
346 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
347 a8083063 Iustin Pop
  def AllocatePort(self):
348 a8083063 Iustin Pop
    """Allocate a port.
349 a8083063 Iustin Pop

350 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
351 b2fddf63 Iustin Pop
    default port range (and in this case we increase
352 b2fddf63 Iustin Pop
    highest_used_port).
353 a8083063 Iustin Pop

354 a8083063 Iustin Pop
    """
355 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
356 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
357 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
358 264bb3c5 Michael Hanselmann
    else:
359 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
360 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
361 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
362 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
363 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
364 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
365 a8083063 Iustin Pop
366 a8083063 Iustin Pop
    self._WriteConfig()
367 a8083063 Iustin Pop
    return port
368 a8083063 Iustin Pop
369 a81c53c9 Iustin Pop
  def _ComputeDRBDMap(self, instance):
370 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
371 a81c53c9 Iustin Pop

372 a81c53c9 Iustin Pop
    Return: dictionary of node_name: dict of minor: instance_name. The
373 a81c53c9 Iustin Pop
    returned dict will have all the nodes in it (even if with an empty
374 a81c53c9 Iustin Pop
    list).
375 a81c53c9 Iustin Pop

376 a81c53c9 Iustin Pop
    """
377 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
378 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
379 f9518d38 Iustin Pop
        nodeA, nodeB, dummy, minorA, minorB = disk.logical_id[:5]
380 a81c53c9 Iustin Pop
        for node, port in ((nodeA, minorA), (nodeB, minorB)):
381 a81c53c9 Iustin Pop
          assert node in used, "Instance node not found in node list"
382 a81c53c9 Iustin Pop
          if port in used[node]:
383 a81c53c9 Iustin Pop
            raise errors.ProgrammerError("DRBD minor already used:"
384 a81c53c9 Iustin Pop
                                         " %s/%s, %s/%s" %
385 a81c53c9 Iustin Pop
                                         (node, port, instance_name,
386 a81c53c9 Iustin Pop
                                          used[node][port]))
387 a81c53c9 Iustin Pop
388 a81c53c9 Iustin Pop
          used[node][port] = instance_name
389 a81c53c9 Iustin Pop
      if disk.children:
390 a81c53c9 Iustin Pop
        for child in disk.children:
391 a81c53c9 Iustin Pop
          _AppendUsedPorts(instance_name, child, used)
392 a81c53c9 Iustin Pop
393 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
394 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
395 a81c53c9 Iustin Pop
      my_dict[node][minor] = instance
396 a81c53c9 Iustin Pop
    for instance in self._config_data.instances.itervalues():
397 a81c53c9 Iustin Pop
      for disk in instance.disks:
398 a81c53c9 Iustin Pop
        _AppendUsedPorts(instance.name, disk, my_dict)
399 a81c53c9 Iustin Pop
    return my_dict
400 a81c53c9 Iustin Pop
401 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
402 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
403 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
404 a81c53c9 Iustin Pop

405 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
406 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
407 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
408 a81c53c9 Iustin Pop
    order as the passed nodes.
409 a81c53c9 Iustin Pop

410 a81c53c9 Iustin Pop
    """
411 a81c53c9 Iustin Pop
    d_map = self._ComputeDRBDMap(instance)
412 a81c53c9 Iustin Pop
    result = []
413 a81c53c9 Iustin Pop
    for nname in nodes:
414 a81c53c9 Iustin Pop
      ndata = d_map[nname]
415 a81c53c9 Iustin Pop
      if not ndata:
416 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
417 a81c53c9 Iustin Pop
        result.append(0)
418 a81c53c9 Iustin Pop
        ndata[0] = instance
419 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
420 a81c53c9 Iustin Pop
        continue
421 a81c53c9 Iustin Pop
      keys = ndata.keys()
422 a81c53c9 Iustin Pop
      keys.sort()
423 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
424 a81c53c9 Iustin Pop
      if ffree is None:
425 a81c53c9 Iustin Pop
        # return the next minor
426 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
427 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
428 a81c53c9 Iustin Pop
      else:
429 a81c53c9 Iustin Pop
        minor = ffree
430 a81c53c9 Iustin Pop
      result.append(minor)
431 a81c53c9 Iustin Pop
      ndata[minor] = instance
432 a81c53c9 Iustin Pop
      assert (nname, minor) not in self._temporary_drbds, \
433 a81c53c9 Iustin Pop
             "Attempt to reuse reserved DRBD minor"
434 a81c53c9 Iustin Pop
      self._temporary_drbds[(nname, minor)] = instance
435 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
436 a81c53c9 Iustin Pop
                  nodes, result)
437 a81c53c9 Iustin Pop
    return result
438 a81c53c9 Iustin Pop
439 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
440 a81c53c9 Iustin Pop
  def ReleaseDRBDMinors(self, instance):
441 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
442 a81c53c9 Iustin Pop

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

446 a81c53c9 Iustin Pop
    @type instance: string
447 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
448 a81c53c9 Iustin Pop
                     released
449 a81c53c9 Iustin Pop

450 a81c53c9 Iustin Pop
    """
451 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
452 a81c53c9 Iustin Pop
      if name == instance:
453 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
454 a81c53c9 Iustin Pop
455 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
456 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
457 4a8b186a Michael Hanselmann
    """Get the configuration version.
458 4a8b186a Michael Hanselmann

459 4a8b186a Michael Hanselmann
    @return: Config version
460 4a8b186a Michael Hanselmann

461 4a8b186a Michael Hanselmann
    """
462 4a8b186a Michael Hanselmann
    return self._config_data.version
463 4a8b186a Michael Hanselmann
464 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
465 4a8b186a Michael Hanselmann
  def GetClusterName(self):
466 4a8b186a Michael Hanselmann
    """Get cluster name.
467 4a8b186a Michael Hanselmann

468 4a8b186a Michael Hanselmann
    @return: Cluster name
469 4a8b186a Michael Hanselmann

470 4a8b186a Michael Hanselmann
    """
471 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
472 4a8b186a Michael Hanselmann
473 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
474 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
475 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
476 4a8b186a Michael Hanselmann

477 4a8b186a Michael Hanselmann
    @return: Master hostname
478 4a8b186a Michael Hanselmann

479 4a8b186a Michael Hanselmann
    """
480 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
481 4a8b186a Michael Hanselmann
482 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
483 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
484 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
485 4a8b186a Michael Hanselmann

486 4a8b186a Michael Hanselmann
    @return: Master IP
487 4a8b186a Michael Hanselmann

488 4a8b186a Michael Hanselmann
    """
489 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
490 4a8b186a Michael Hanselmann
491 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
492 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
493 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
494 4a8b186a Michael Hanselmann

495 4a8b186a Michael Hanselmann
    """
496 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
497 4a8b186a Michael Hanselmann
498 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
499 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
500 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
501 4a8b186a Michael Hanselmann

502 4a8b186a Michael Hanselmann
    """
503 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
504 4a8b186a Michael Hanselmann
505 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
506 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
507 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
508 4a8b186a Michael Hanselmann

509 4a8b186a Michael Hanselmann
    """
510 4a8b186a Michael Hanselmann
    return self._config_data.cluster.hypervisor
511 4a8b186a Michael Hanselmann
512 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
513 a8083063 Iustin Pop
  def GetHostKey(self):
514 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
515 a8083063 Iustin Pop

516 a8083063 Iustin Pop
    Args: None
517 a8083063 Iustin Pop

518 a8083063 Iustin Pop
    Returns: rsa hostkey
519 a8083063 Iustin Pop
    """
520 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
521 a8083063 Iustin Pop
522 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
523 a8083063 Iustin Pop
  def AddInstance(self, instance):
524 a8083063 Iustin Pop
    """Add an instance to the config.
525 a8083063 Iustin Pop

526 a8083063 Iustin Pop
    This should be used after creating a new instance.
527 a8083063 Iustin Pop

528 a8083063 Iustin Pop
    Args:
529 a8083063 Iustin Pop
      instance: the instance object
530 a8083063 Iustin Pop
    """
531 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
532 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
533 a8083063 Iustin Pop
534 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
535 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
536 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
537 923b1523 Iustin Pop
538 b989e85d Iustin Pop
    instance.serial_no = 1
539 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
540 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
541 a8083063 Iustin Pop
    self._WriteConfig()
542 a8083063 Iustin Pop
543 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
544 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
545 a8083063 Iustin Pop

546 a8083063 Iustin Pop
    """
547 6a408fb2 Iustin Pop
    if status not in ("up", "down"):
548 6a408fb2 Iustin Pop
      raise errors.ProgrammerError("Invalid status '%s' passed to"
549 6a408fb2 Iustin Pop
                                   " ConfigWriter._SetInstanceStatus()" %
550 6a408fb2 Iustin Pop
                                   status)
551 a8083063 Iustin Pop
552 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
553 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
554 3ecf6786 Iustin Pop
                                      instance_name)
555 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
556 455a3445 Iustin Pop
    if instance.status != status:
557 455a3445 Iustin Pop
      instance.status = status
558 b989e85d Iustin Pop
      instance.serial_no += 1
559 455a3445 Iustin Pop
      self._WriteConfig()
560 a8083063 Iustin Pop
561 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
562 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
563 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
564 6a408fb2 Iustin Pop

565 6a408fb2 Iustin Pop
    """
566 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
567 6a408fb2 Iustin Pop
568 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
569 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
570 a8083063 Iustin Pop
    """Remove the instance from the configuration.
571 a8083063 Iustin Pop

572 a8083063 Iustin Pop
    """
573 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
574 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
575 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
576 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
577 a8083063 Iustin Pop
    self._WriteConfig()
578 a8083063 Iustin Pop
579 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
580 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
581 fc95f88f Iustin Pop
    """Rename an instance.
582 fc95f88f Iustin Pop

583 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
584 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
585 fc95f88f Iustin Pop
    rename.
586 fc95f88f Iustin Pop

587 fc95f88f Iustin Pop
    """
588 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
589 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
590 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
591 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
592 fc95f88f Iustin Pop
    inst.name = new_name
593 b23c4333 Manuel Franceschini
594 b23c4333 Manuel Franceschini
    for disk in inst.disks:
595 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
596 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
597 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
598 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
599 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
600 b23c4333 Manuel Franceschini
                                                           inst.name,
601 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
602 b23c4333 Manuel Franceschini
603 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
604 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
605 fc95f88f Iustin Pop
    self._WriteConfig()
606 fc95f88f Iustin Pop
607 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
608 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
609 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
610 a8083063 Iustin Pop

611 a8083063 Iustin Pop
    """
612 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
613 a8083063 Iustin Pop
614 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
615 94bbfece Iustin Pop
    """Get the list of instances.
616 94bbfece Iustin Pop

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

619 94bbfece Iustin Pop
    """
620 94bbfece Iustin Pop
    return self._config_data.instances.keys()
621 94bbfece Iustin Pop
622 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
623 a8083063 Iustin Pop
  def GetInstanceList(self):
624 a8083063 Iustin Pop
    """Get the list of instances.
625 a8083063 Iustin Pop

626 a8083063 Iustin Pop
    Returns:
627 a8083063 Iustin Pop
      array of instances, ex. ['instance2.example.com','instance1.example.com']
628 a8083063 Iustin Pop
      these contains all the instances, also the ones in Admin_down state
629 a8083063 Iustin Pop

630 a8083063 Iustin Pop
    """
631 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
632 a8083063 Iustin Pop
633 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
634 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
635 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
636 a8083063 Iustin Pop

637 a8083063 Iustin Pop
    """
638 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
639 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
640 a8083063 Iustin Pop
641 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
642 94bbfece Iustin Pop
    """Returns informations about an instance.
643 94bbfece Iustin Pop

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

646 94bbfece Iustin Pop
    """
647 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
648 94bbfece Iustin Pop
      return None
649 94bbfece Iustin Pop
650 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
651 94bbfece Iustin Pop
652 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
653 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
654 a8083063 Iustin Pop
    """Returns informations about an instance.
655 a8083063 Iustin Pop

656 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
657 a8083063 Iustin Pop
    an instance are taken from the live systems.
658 a8083063 Iustin Pop

659 a8083063 Iustin Pop
    Args:
660 a8083063 Iustin Pop
      instance: name of the instance, ex instance1.example.com
661 a8083063 Iustin Pop

662 a8083063 Iustin Pop
    Returns:
663 a8083063 Iustin Pop
      the instance object
664 a8083063 Iustin Pop

665 a8083063 Iustin Pop
    """
666 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
667 a8083063 Iustin Pop
668 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
669 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
670 0b2de758 Iustin Pop
    """Get the configuration of all instances.
671 0b2de758 Iustin Pop

672 0b2de758 Iustin Pop
    @rtype: dict
673 0b2de758 Iustin Pop
    @returns: dict of (instance, instance_info), where instance_info is what
674 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
675 0b2de758 Iustin Pop

676 0b2de758 Iustin Pop
    """
677 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
678 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
679 0b2de758 Iustin Pop
    return my_dict
680 0b2de758 Iustin Pop
681 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
682 a8083063 Iustin Pop
  def AddNode(self, node):
683 a8083063 Iustin Pop
    """Add a node to the configuration.
684 a8083063 Iustin Pop

685 a8083063 Iustin Pop
    Args:
686 a8083063 Iustin Pop
      node: an object.Node instance
687 a8083063 Iustin Pop

688 a8083063 Iustin Pop
    """
689 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
690 d8470559 Michael Hanselmann
691 b989e85d Iustin Pop
    node.serial_no = 1
692 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
693 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
694 a8083063 Iustin Pop
    self._WriteConfig()
695 a8083063 Iustin Pop
696 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
697 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
698 a8083063 Iustin Pop
    """Remove a node from the configuration.
699 a8083063 Iustin Pop

700 a8083063 Iustin Pop
    """
701 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
702 d8470559 Michael Hanselmann
703 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
704 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
705 a8083063 Iustin Pop
706 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
707 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
708 a8083063 Iustin Pop
    self._WriteConfig()
709 a8083063 Iustin Pop
710 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
711 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
712 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
713 a8083063 Iustin Pop

714 a8083063 Iustin Pop
    """
715 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
716 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
717 a8083063 Iustin Pop
718 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
719 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
720 a8083063 Iustin Pop

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

723 a8083063 Iustin Pop
    Args: node: nodename (tuple) of the node
724 a8083063 Iustin Pop

725 a8083063 Iustin Pop
    Returns: the node object
726 a8083063 Iustin Pop

727 a8083063 Iustin Pop
    """
728 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
729 a8083063 Iustin Pop
      return None
730 a8083063 Iustin Pop
731 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
732 a8083063 Iustin Pop
733 f78ede4e Guido Trotter
734 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
735 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
736 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
737 f78ede4e Guido Trotter

738 f78ede4e Guido Trotter
    Args: node: nodename (tuple) of the node
739 f78ede4e Guido Trotter

740 f78ede4e Guido Trotter
    Returns: the node object
741 f78ede4e Guido Trotter

742 f78ede4e Guido Trotter
    """
743 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
744 f78ede4e Guido Trotter
745 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
746 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
747 a8083063 Iustin Pop

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

750 a8083063 Iustin Pop
    """
751 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
752 a8083063 Iustin Pop
753 f78ede4e Guido Trotter
754 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
755 f78ede4e Guido Trotter
  def GetNodeList(self):
756 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
757 f78ede4e Guido Trotter

758 f78ede4e Guido Trotter
    """
759 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
760 f78ede4e Guido Trotter
761 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
762 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
763 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
764 d65e5776 Iustin Pop

765 d65e5776 Iustin Pop
    @rtype: dict
766 d65e5776 Iustin Pop
    @returns: dict of (node, node_info), where node_info is what
767 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
768 d65e5776 Iustin Pop

769 d65e5776 Iustin Pop
    """
770 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
771 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
772 d65e5776 Iustin Pop
    return my_dict
773 d65e5776 Iustin Pop
774 a8083063 Iustin Pop
  def _BumpSerialNo(self):
775 a8083063 Iustin Pop
    """Bump up the serial number of the config.
776 a8083063 Iustin Pop

777 a8083063 Iustin Pop
    """
778 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
779 a8083063 Iustin Pop
780 a8083063 Iustin Pop
  def _OpenConfig(self):
781 a8083063 Iustin Pop
    """Read the config data from disk.
782 a8083063 Iustin Pop

783 a8083063 Iustin Pop
    In case we already have configuration data and the config file has
784 a8083063 Iustin Pop
    the same mtime as when we read it, we skip the parsing of the
785 a8083063 Iustin Pop
    file, since de-serialisation could be slow.
786 a8083063 Iustin Pop

787 a8083063 Iustin Pop
    """
788 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
789 a8083063 Iustin Pop
    try:
790 a8083063 Iustin Pop
      try:
791 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
792 a8083063 Iustin Pop
      except Exception, err:
793 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
794 a8083063 Iustin Pop
    finally:
795 a8083063 Iustin Pop
      f.close()
796 5b263ed7 Michael Hanselmann
797 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
798 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
799 5b263ed7 Michael Hanselmann
800 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
801 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
802 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
803 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
804 a8083063 Iustin Pop
    self._config_data = data
805 a8083063 Iustin Pop
806 a8083063 Iustin Pop
  def _DistributeConfig(self):
807 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
808 a8083063 Iustin Pop

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

812 a8083063 Iustin Pop
    """
813 a8083063 Iustin Pop
    if self._offline:
814 a8083063 Iustin Pop
      return True
815 a8083063 Iustin Pop
    bad = False
816 f78ede4e Guido Trotter
    nodelist = self._UnlockedGetNodeList()
817 89e1fc26 Iustin Pop
    myhostname = self._my_hostname
818 a8083063 Iustin Pop
819 9ff994da Guido Trotter
    try:
820 9ff994da Guido Trotter
      nodelist.remove(myhostname)
821 9ff994da Guido Trotter
    except ValueError:
822 9ff994da Guido Trotter
      pass
823 a8083063 Iustin Pop
824 72737a7f Iustin Pop
    result = rpc.RpcRunner.call_upload_file(nodelist, self._cfg_file)
825 41362e70 Guido Trotter
    for node in nodelist:
826 a8083063 Iustin Pop
      if not result[node]:
827 74a48621 Iustin Pop
        logging.error("copy of file %s to node %s failed",
828 74a48621 Iustin Pop
                      self._cfg_file, node)
829 a8083063 Iustin Pop
        bad = True
830 a8083063 Iustin Pop
    return not bad
831 a8083063 Iustin Pop
832 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
833 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
834 a8083063 Iustin Pop

835 a8083063 Iustin Pop
    """
836 a8083063 Iustin Pop
    if destination is None:
837 a8083063 Iustin Pop
      destination = self._cfg_file
838 a8083063 Iustin Pop
    self._BumpSerialNo()
839 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
840 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
841 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
842 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
843 a8083063 Iustin Pop
    try:
844 8d14b30d Iustin Pop
      f.write(txt)
845 a8083063 Iustin Pop
      os.fsync(f.fileno())
846 a8083063 Iustin Pop
    finally:
847 a8083063 Iustin Pop
      f.close()
848 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
849 a8083063 Iustin Pop
    os.rename(name, destination)
850 14e15659 Iustin Pop
    self.write_count += 1
851 3d3a04bc Iustin Pop
852 fee9556c Iustin Pop
    # and redistribute the config file
853 a8083063 Iustin Pop
    self._DistributeConfig()
854 a8083063 Iustin Pop
855 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
856 f6bd6e98 Michael Hanselmann
  def InitConfig(self, version, cluster_config, master_node_config):
857 a8083063 Iustin Pop
    """Create the initial cluster configuration.
858 a8083063 Iustin Pop

859 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
860 b9eeeb02 Michael Hanselmann
    node, and no instances.
861 a8083063 Iustin Pop

862 f6bd6e98 Michael Hanselmann
    @type version: int
863 f6bd6e98 Michael Hanselmann
    @param version: Configuration version
864 b9eeeb02 Michael Hanselmann
    @type cluster_config: objects.Cluster
865 b9eeeb02 Michael Hanselmann
    @param cluster_config: Cluster configuration
866 b9eeeb02 Michael Hanselmann
    @type master_node_config: objects.Node
867 b9eeeb02 Michael Hanselmann
    @param master_node_config: Master node configuration
868 b9eeeb02 Michael Hanselmann

869 b9eeeb02 Michael Hanselmann
    """
870 b9eeeb02 Michael Hanselmann
    nodes = {
871 b9eeeb02 Michael Hanselmann
      master_node_config.name: master_node_config,
872 b9eeeb02 Michael Hanselmann
      }
873 b9eeeb02 Michael Hanselmann
874 f6bd6e98 Michael Hanselmann
    self._config_data = objects.ConfigData(version=version,
875 f6bd6e98 Michael Hanselmann
                                           cluster=cluster_config,
876 b9eeeb02 Michael Hanselmann
                                           nodes=nodes,
877 a8083063 Iustin Pop
                                           instances={},
878 9d38c6e1 Iustin Pop
                                           serial_no=1)
879 a8083063 Iustin Pop
    self._WriteConfig()
880 a8083063 Iustin Pop
881 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
882 a8083063 Iustin Pop
  def GetVGName(self):
883 a8083063 Iustin Pop
    """Return the volume group name.
884 a8083063 Iustin Pop

885 a8083063 Iustin Pop
    """
886 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
887 a8083063 Iustin Pop
888 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
889 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
890 89ff8e15 Manuel Franceschini
    """Set the volume group name.
891 89ff8e15 Manuel Franceschini

892 89ff8e15 Manuel Franceschini
    """
893 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
894 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
895 89ff8e15 Manuel Franceschini
    self._WriteConfig()
896 89ff8e15 Manuel Franceschini
897 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
898 a8083063 Iustin Pop
  def GetDefBridge(self):
899 a8083063 Iustin Pop
    """Return the default bridge.
900 a8083063 Iustin Pop

901 a8083063 Iustin Pop
    """
902 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
903 a8083063 Iustin Pop
904 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
905 a8083063 Iustin Pop
  def GetMACPrefix(self):
906 a8083063 Iustin Pop
    """Return the mac prefix.
907 a8083063 Iustin Pop

908 a8083063 Iustin Pop
    """
909 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
910 62779dd0 Iustin Pop
911 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
912 62779dd0 Iustin Pop
  def GetClusterInfo(self):
913 62779dd0 Iustin Pop
    """Returns informations about the cluster
914 62779dd0 Iustin Pop

915 62779dd0 Iustin Pop
    Returns:
916 62779dd0 Iustin Pop
      the cluster object
917 62779dd0 Iustin Pop

918 62779dd0 Iustin Pop
    """
919 62779dd0 Iustin Pop
    return self._config_data.cluster
920 e00fb268 Iustin Pop
921 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
922 e00fb268 Iustin Pop
  def Update(self, target):
923 e00fb268 Iustin Pop
    """Notify function to be called after updates.
924 e00fb268 Iustin Pop

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

931 e00fb268 Iustin Pop
    """
932 e00fb268 Iustin Pop
    if self._config_data is None:
933 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
934 3ecf6786 Iustin Pop
                                   " cannot save.")
935 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
936 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
937 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
938 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
939 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
940 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
941 e00fb268 Iustin Pop
    else:
942 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
943 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
944 e00fb268 Iustin Pop
    if not test:
945 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
946 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
947 b989e85d Iustin Pop
    target.serial_no += 1
948 b989e85d Iustin Pop
949 e00fb268 Iustin Pop
    self._WriteConfig()