Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ ec0292f1

History | View | Annotate | Download (33.6 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 a8efbb40 Iustin Pop
    """Verify function.
218 a8efbb40 Iustin Pop

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

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

290 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
291 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
292 a8083063 Iustin Pop
    node.
293 a8083063 Iustin Pop

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

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

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

329 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
330 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
331 f78ede4e Guido Trotter
    node.
332 f78ede4e Guido Trotter

333 f78ede4e Guido Trotter
    """
334 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
335 f78ede4e Guido Trotter
336 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
337 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
338 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
339 b2fddf63 Iustin Pop

340 b2fddf63 Iustin Pop
    """
341 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
342 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
343 264bb3c5 Michael Hanselmann
344 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
345 264bb3c5 Michael Hanselmann
    self._WriteConfig()
346 264bb3c5 Michael Hanselmann
347 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
348 b2fddf63 Iustin Pop
  def GetPortList(self):
349 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
350 264bb3c5 Michael Hanselmann

351 264bb3c5 Michael Hanselmann
    """
352 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
353 264bb3c5 Michael Hanselmann
354 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
355 a8083063 Iustin Pop
  def AllocatePort(self):
356 a8083063 Iustin Pop
    """Allocate a port.
357 a8083063 Iustin Pop

358 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
359 b2fddf63 Iustin Pop
    default port range (and in this case we increase
360 b2fddf63 Iustin Pop
    highest_used_port).
361 a8083063 Iustin Pop

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

380 a81c53c9 Iustin Pop
    Return: dictionary of node_name: dict of minor: instance_name. The
381 a81c53c9 Iustin Pop
    returned dict will have all the nodes in it (even if with an empty
382 a81c53c9 Iustin Pop
    list).
383 a81c53c9 Iustin Pop

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

413 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
414 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
415 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
416 a81c53c9 Iustin Pop
    order as the passed nodes.
417 a81c53c9 Iustin Pop

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

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

454 a81c53c9 Iustin Pop
    @type instance: string
455 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
456 a81c53c9 Iustin Pop
                     released
457 a81c53c9 Iustin Pop

458 a81c53c9 Iustin Pop
    """
459 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
460 a81c53c9 Iustin Pop
      if name == instance:
461 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
462 a81c53c9 Iustin Pop
463 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
464 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
465 4a8b186a Michael Hanselmann
    """Get the configuration version.
466 4a8b186a Michael Hanselmann

467 4a8b186a Michael Hanselmann
    @return: Config version
468 4a8b186a Michael Hanselmann

469 4a8b186a Michael Hanselmann
    """
470 4a8b186a Michael Hanselmann
    return self._config_data.version
471 4a8b186a Michael Hanselmann
472 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
473 4a8b186a Michael Hanselmann
  def GetClusterName(self):
474 4a8b186a Michael Hanselmann
    """Get cluster name.
475 4a8b186a Michael Hanselmann

476 4a8b186a Michael Hanselmann
    @return: Cluster name
477 4a8b186a Michael Hanselmann

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

485 4a8b186a Michael Hanselmann
    @return: Master hostname
486 4a8b186a Michael Hanselmann

487 4a8b186a Michael Hanselmann
    """
488 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
489 4a8b186a Michael Hanselmann
490 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
491 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
492 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
493 4a8b186a Michael Hanselmann

494 4a8b186a Michael Hanselmann
    @return: Master IP
495 4a8b186a Michael Hanselmann

496 4a8b186a Michael Hanselmann
    """
497 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
498 4a8b186a Michael Hanselmann
499 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
500 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
501 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
502 4a8b186a Michael Hanselmann

503 4a8b186a Michael Hanselmann
    """
504 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
505 4a8b186a Michael Hanselmann
506 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
507 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
508 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
509 4a8b186a Michael Hanselmann

510 4a8b186a Michael Hanselmann
    """
511 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
512 4a8b186a Michael Hanselmann
513 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
514 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
515 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
516 4a8b186a Michael Hanselmann

517 4a8b186a Michael Hanselmann
    """
518 64272529 Iustin Pop
    return self._config_data.cluster.default_hypervisor
519 4a8b186a Michael Hanselmann
520 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
521 a8083063 Iustin Pop
  def GetHostKey(self):
522 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
523 a8083063 Iustin Pop

524 a8083063 Iustin Pop
    Args: None
525 a8083063 Iustin Pop

526 a8083063 Iustin Pop
    Returns: rsa hostkey
527 a8083063 Iustin Pop
    """
528 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
529 a8083063 Iustin Pop
530 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
531 a8083063 Iustin Pop
  def AddInstance(self, instance):
532 a8083063 Iustin Pop
    """Add an instance to the config.
533 a8083063 Iustin Pop

534 a8083063 Iustin Pop
    This should be used after creating a new instance.
535 a8083063 Iustin Pop

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

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

572 6a408fb2 Iustin Pop
    """
573 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
574 6a408fb2 Iustin Pop
575 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
576 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
577 a8083063 Iustin Pop
    """Remove the instance from the configuration.
578 a8083063 Iustin Pop

579 a8083063 Iustin Pop
    """
580 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
581 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
582 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
583 a8083063 Iustin Pop
    self._WriteConfig()
584 a8083063 Iustin Pop
585 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
586 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
587 fc95f88f Iustin Pop
    """Rename an instance.
588 fc95f88f Iustin Pop

589 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
590 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
591 fc95f88f Iustin Pop
    rename.
592 fc95f88f Iustin Pop

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

616 a8083063 Iustin Pop
    """
617 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
618 a8083063 Iustin Pop
619 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
620 94bbfece Iustin Pop
    """Get the list of instances.
621 94bbfece Iustin Pop

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

624 94bbfece Iustin Pop
    """
625 94bbfece Iustin Pop
    return self._config_data.instances.keys()
626 94bbfece Iustin Pop
627 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
628 a8083063 Iustin Pop
  def GetInstanceList(self):
629 a8083063 Iustin Pop
    """Get the list of instances.
630 a8083063 Iustin Pop

631 a8083063 Iustin Pop
    Returns:
632 a8083063 Iustin Pop
      array of instances, ex. ['instance2.example.com','instance1.example.com']
633 a8083063 Iustin Pop
      these contains all the instances, also the ones in Admin_down state
634 a8083063 Iustin Pop

635 a8083063 Iustin Pop
    """
636 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
637 a8083063 Iustin Pop
638 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
639 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
640 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
641 a8083063 Iustin Pop

642 a8083063 Iustin Pop
    """
643 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
644 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
645 a8083063 Iustin Pop
646 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
647 94bbfece Iustin Pop
    """Returns informations about an instance.
648 94bbfece Iustin Pop

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

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

661 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
662 a8083063 Iustin Pop
    an instance are taken from the live systems.
663 a8083063 Iustin Pop

664 a8083063 Iustin Pop
    Args:
665 a8083063 Iustin Pop
      instance: name of the instance, ex instance1.example.com
666 a8083063 Iustin Pop

667 a8083063 Iustin Pop
    Returns:
668 a8083063 Iustin Pop
      the instance object
669 a8083063 Iustin Pop

670 a8083063 Iustin Pop
    """
671 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
672 a8083063 Iustin Pop
673 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
674 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
675 0b2de758 Iustin Pop
    """Get the configuration of all instances.
676 0b2de758 Iustin Pop

677 0b2de758 Iustin Pop
    @rtype: dict
678 0b2de758 Iustin Pop
    @returns: dict of (instance, instance_info), where instance_info is what
679 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
680 0b2de758 Iustin Pop

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

690 a8083063 Iustin Pop
    Args:
691 a8083063 Iustin Pop
      node: an object.Node instance
692 a8083063 Iustin Pop

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

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

719 a8083063 Iustin Pop
    """
720 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
721 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
722 a8083063 Iustin Pop
723 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
724 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
725 a8083063 Iustin Pop

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

728 a8083063 Iustin Pop
    Args: node: nodename (tuple) of the node
729 a8083063 Iustin Pop

730 a8083063 Iustin Pop
    Returns: the node object
731 a8083063 Iustin Pop

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

743 f78ede4e Guido Trotter
    Args: node: nodename (tuple) of the node
744 f78ede4e Guido Trotter

745 f78ede4e Guido Trotter
    Returns: the node object
746 f78ede4e Guido Trotter

747 f78ede4e Guido Trotter
    """
748 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
749 f78ede4e Guido Trotter
750 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
751 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
752 a8083063 Iustin Pop

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

755 a8083063 Iustin Pop
    """
756 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
757 a8083063 Iustin Pop
758 f78ede4e Guido Trotter
759 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
760 f78ede4e Guido Trotter
  def GetNodeList(self):
761 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
762 f78ede4e Guido Trotter

763 f78ede4e Guido Trotter
    """
764 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
765 f78ede4e Guido Trotter
766 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
767 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
768 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
769 d65e5776 Iustin Pop

770 d65e5776 Iustin Pop
    @rtype: dict
771 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
772 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
773 d65e5776 Iustin Pop

774 d65e5776 Iustin Pop
    """
775 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
776 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
777 d65e5776 Iustin Pop
    return my_dict
778 d65e5776 Iustin Pop
779 ec0292f1 Iustin Pop
  def _UnlockedGetMasterCandidateStats(self):
780 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
781 ec0292f1 Iustin Pop

782 ec0292f1 Iustin Pop
    @rtype: tuple
783 ec0292f1 Iustin Pop
    @return: tuple of (current, desired and possible)
784 ec0292f1 Iustin Pop

785 ec0292f1 Iustin Pop
    """
786 ec0292f1 Iustin Pop
    mc_now = mc_max = 0
787 ec0292f1 Iustin Pop
    for node in self._config_data.nodes.itervalues():
788 ec0292f1 Iustin Pop
      if not node.offline:
789 ec0292f1 Iustin Pop
        mc_max += 1
790 ec0292f1 Iustin Pop
      if node.master_candidate:
791 ec0292f1 Iustin Pop
        mc_now += 1
792 ec0292f1 Iustin Pop
    mc_max = min(mc_max, self._config_data.cluster.candidate_pool_size)
793 ec0292f1 Iustin Pop
    return (mc_now, mc_max)
794 ec0292f1 Iustin Pop
795 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
796 ec0292f1 Iustin Pop
  def GetMasterCandidateStats(self):
797 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
798 ec0292f1 Iustin Pop

799 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
800 ec0292f1 Iustin Pop

801 ec0292f1 Iustin Pop
    @rtype: tuple
802 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
803 ec0292f1 Iustin Pop

804 ec0292f1 Iustin Pop
    """
805 ec0292f1 Iustin Pop
    return self._UnlockedGetMasterCandidateStats()
806 ec0292f1 Iustin Pop
807 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
808 ec0292f1 Iustin Pop
  def MaintainCandidatePool(self):
809 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
810 ec0292f1 Iustin Pop

811 ec0292f1 Iustin Pop
    @rtype: list
812 ec0292f1 Iustin Pop
    @return: list with the adjusted node names
813 ec0292f1 Iustin Pop

814 ec0292f1 Iustin Pop
    """
815 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
816 ec0292f1 Iustin Pop
    mod_list = []
817 ec0292f1 Iustin Pop
    if mc_now < mc_max:
818 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
819 ec0292f1 Iustin Pop
      random.shuffle(node_list)
820 ec0292f1 Iustin Pop
      for name in node_list:
821 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
822 ec0292f1 Iustin Pop
          break
823 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
824 ec0292f1 Iustin Pop
        if node.master_candidate or node.offline:
825 ec0292f1 Iustin Pop
          continue
826 ec0292f1 Iustin Pop
        mod_list.append(node.name)
827 ec0292f1 Iustin Pop
        node.master_candidate = True
828 ec0292f1 Iustin Pop
        node.serial_no += 1
829 ec0292f1 Iustin Pop
        mc_now += 1
830 ec0292f1 Iustin Pop
      if mc_now != mc_max:
831 ec0292f1 Iustin Pop
        # this should not happen
832 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
833 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
834 ec0292f1 Iustin Pop
      if mod_list:
835 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
836 ec0292f1 Iustin Pop
        self._WriteConfig()
837 ec0292f1 Iustin Pop
838 ec0292f1 Iustin Pop
    return mod_list
839 ec0292f1 Iustin Pop
840 a8083063 Iustin Pop
  def _BumpSerialNo(self):
841 a8083063 Iustin Pop
    """Bump up the serial number of the config.
842 a8083063 Iustin Pop

843 a8083063 Iustin Pop
    """
844 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
845 a8083063 Iustin Pop
846 a8083063 Iustin Pop
  def _OpenConfig(self):
847 a8083063 Iustin Pop
    """Read the config data from disk.
848 a8083063 Iustin Pop

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

853 a8083063 Iustin Pop
    """
854 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
855 a8083063 Iustin Pop
    try:
856 a8083063 Iustin Pop
      try:
857 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
858 a8083063 Iustin Pop
      except Exception, err:
859 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
860 a8083063 Iustin Pop
    finally:
861 a8083063 Iustin Pop
      f.close()
862 5b263ed7 Michael Hanselmann
863 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
864 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
865 5b263ed7 Michael Hanselmann
866 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
867 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
868 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
869 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
870 a8083063 Iustin Pop
    self._config_data = data
871 0779e3aa Iustin Pop
    # init the last serial as -1 so that the next write will cause
872 0779e3aa Iustin Pop
    # ssconf update
873 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
874 a8083063 Iustin Pop
875 a8083063 Iustin Pop
  def _DistributeConfig(self):
876 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
877 a8083063 Iustin Pop

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

881 a8083063 Iustin Pop
    """
882 a8083063 Iustin Pop
    if self._offline:
883 a8083063 Iustin Pop
      return True
884 a8083063 Iustin Pop
    bad = False
885 a8083063 Iustin Pop
886 6a5b8b4b Iustin Pop
    node_list = []
887 6a5b8b4b Iustin Pop
    addr_list = []
888 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
889 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
890 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
891 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
892 6b294c53 Iustin Pop
    # in between
893 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
894 6a5b8b4b Iustin Pop
      if node_name == myhostname:
895 6a5b8b4b Iustin Pop
        continue
896 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
897 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
898 6a5b8b4b Iustin Pop
        continue
899 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
900 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
901 6b294c53 Iustin Pop
902 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
903 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
904 6a5b8b4b Iustin Pop
    for node in node_list:
905 a8083063 Iustin Pop
      if not result[node]:
906 74a48621 Iustin Pop
        logging.error("copy of file %s to node %s failed",
907 74a48621 Iustin Pop
                      self._cfg_file, node)
908 a8083063 Iustin Pop
        bad = True
909 a8083063 Iustin Pop
    return not bad
910 a8083063 Iustin Pop
911 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
912 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
913 a8083063 Iustin Pop

914 a8083063 Iustin Pop
    """
915 a8083063 Iustin Pop
    if destination is None:
916 a8083063 Iustin Pop
      destination = self._cfg_file
917 a8083063 Iustin Pop
    self._BumpSerialNo()
918 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
919 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
920 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
921 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
922 a8083063 Iustin Pop
    try:
923 8d14b30d Iustin Pop
      f.write(txt)
924 a8083063 Iustin Pop
      os.fsync(f.fileno())
925 a8083063 Iustin Pop
    finally:
926 a8083063 Iustin Pop
      f.close()
927 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
928 a8083063 Iustin Pop
    os.rename(name, destination)
929 14e15659 Iustin Pop
    self.write_count += 1
930 3d3a04bc Iustin Pop
931 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
932 a8083063 Iustin Pop
    self._DistributeConfig()
933 a8083063 Iustin Pop
934 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
935 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
936 d9a855f1 Michael Hanselmann
      if not self._offline:
937 03d1dba2 Michael Hanselmann
        rpc.RpcRunner.call_write_ssconf_files(self._UnlockedGetNodeList(),
938 03d1dba2 Michael Hanselmann
                                              self._UnlockedGetSsconfValues())
939 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
940 54d1a06e Michael Hanselmann
941 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
942 054596f0 Iustin Pop
    """Return the values needed by ssconf.
943 054596f0 Iustin Pop

944 054596f0 Iustin Pop
    @rtype: dict
945 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
946 054596f0 Iustin Pop
        associated value
947 054596f0 Iustin Pop

948 054596f0 Iustin Pop
    """
949 a3316e4a Iustin Pop
    fn = "\n".join
950 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
951 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
952 a3316e4a Iustin Pop
953 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
954 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
955 a3316e4a Iustin Pop
    node_data = fn(node_names)
956 f56618e0 Iustin Pop
957 054596f0 Iustin Pop
    cluster = self._config_data.cluster
958 03d1dba2 Michael Hanselmann
    return {
959 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
960 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
961 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
962 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
963 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
964 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
965 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
966 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
967 03d1dba2 Michael Hanselmann
      }
968 03d1dba2 Michael Hanselmann
969 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
970 f6bd6e98 Michael Hanselmann
  def InitConfig(self, version, cluster_config, master_node_config):
971 a8083063 Iustin Pop
    """Create the initial cluster configuration.
972 a8083063 Iustin Pop

973 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
974 b9eeeb02 Michael Hanselmann
    node, and no instances.
975 a8083063 Iustin Pop

976 f6bd6e98 Michael Hanselmann
    @type version: int
977 f6bd6e98 Michael Hanselmann
    @param version: Configuration version
978 b9eeeb02 Michael Hanselmann
    @type cluster_config: objects.Cluster
979 b9eeeb02 Michael Hanselmann
    @param cluster_config: Cluster configuration
980 b9eeeb02 Michael Hanselmann
    @type master_node_config: objects.Node
981 b9eeeb02 Michael Hanselmann
    @param master_node_config: Master node configuration
982 b9eeeb02 Michael Hanselmann

983 b9eeeb02 Michael Hanselmann
    """
984 b9eeeb02 Michael Hanselmann
    nodes = {
985 b9eeeb02 Michael Hanselmann
      master_node_config.name: master_node_config,
986 b9eeeb02 Michael Hanselmann
      }
987 b9eeeb02 Michael Hanselmann
988 f6bd6e98 Michael Hanselmann
    self._config_data = objects.ConfigData(version=version,
989 f6bd6e98 Michael Hanselmann
                                           cluster=cluster_config,
990 b9eeeb02 Michael Hanselmann
                                           nodes=nodes,
991 a8083063 Iustin Pop
                                           instances={},
992 9d38c6e1 Iustin Pop
                                           serial_no=1)
993 a8083063 Iustin Pop
    self._WriteConfig()
994 a8083063 Iustin Pop
995 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
996 a8083063 Iustin Pop
  def GetVGName(self):
997 a8083063 Iustin Pop
    """Return the volume group name.
998 a8083063 Iustin Pop

999 a8083063 Iustin Pop
    """
1000 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1001 a8083063 Iustin Pop
1002 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1003 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1004 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1005 89ff8e15 Manuel Franceschini

1006 89ff8e15 Manuel Franceschini
    """
1007 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1008 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1009 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1010 89ff8e15 Manuel Franceschini
1011 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1012 a8083063 Iustin Pop
  def GetDefBridge(self):
1013 a8083063 Iustin Pop
    """Return the default bridge.
1014 a8083063 Iustin Pop

1015 a8083063 Iustin Pop
    """
1016 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
1017 a8083063 Iustin Pop
1018 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1019 a8083063 Iustin Pop
  def GetMACPrefix(self):
1020 a8083063 Iustin Pop
    """Return the mac prefix.
1021 a8083063 Iustin Pop

1022 a8083063 Iustin Pop
    """
1023 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1024 62779dd0 Iustin Pop
1025 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1026 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1027 62779dd0 Iustin Pop
    """Returns informations about the cluster
1028 62779dd0 Iustin Pop

1029 62779dd0 Iustin Pop
    Returns:
1030 62779dd0 Iustin Pop
      the cluster object
1031 62779dd0 Iustin Pop

1032 62779dd0 Iustin Pop
    """
1033 62779dd0 Iustin Pop
    return self._config_data.cluster
1034 e00fb268 Iustin Pop
1035 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1036 e00fb268 Iustin Pop
  def Update(self, target):
1037 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1038 e00fb268 Iustin Pop

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

1045 e00fb268 Iustin Pop
    """
1046 e00fb268 Iustin Pop
    if self._config_data is None:
1047 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1048 3ecf6786 Iustin Pop
                                   " cannot save.")
1049 f34901f8 Iustin Pop
    update_serial = False
1050 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1051 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1052 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1053 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1054 f34901f8 Iustin Pop
      update_serial = True
1055 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1056 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1057 e00fb268 Iustin Pop
    else:
1058 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1059 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1060 e00fb268 Iustin Pop
    if not test:
1061 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1062 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1063 f34901f8 Iustin Pop
    target.serial_no += 1
1064 f34901f8 Iustin Pop
1065 cff4c037 Iustin Pop
    if update_serial:
1066 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1067 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1068 b989e85d Iustin Pop
1069 e00fb268 Iustin Pop
    self._WriteConfig()