Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 41362e70

History | View | Annotate | Download (21 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 b23c4333 Manuel Franceschini
import re
38 a8083063 Iustin Pop
39 a8083063 Iustin Pop
from ganeti import errors
40 a8083063 Iustin Pop
from ganeti import logger
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 a8083063 Iustin Pop
47 319856a9 Michael Hanselmann
48 a8083063 Iustin Pop
class ConfigWriter:
49 098c0958 Michael Hanselmann
  """The interface to the cluster configuration.
50 a8083063 Iustin Pop

51 098c0958 Michael Hanselmann
  """
52 a8083063 Iustin Pop
  def __init__(self, cfg_file=None, offline=False):
53 14e15659 Iustin Pop
    self.write_count = 0
54 a8083063 Iustin Pop
    self._config_data = None
55 a8083063 Iustin Pop
    self._config_time = None
56 264bb3c5 Michael Hanselmann
    self._config_size = None
57 264bb3c5 Michael Hanselmann
    self._config_inode = None
58 a8083063 Iustin Pop
    self._offline = offline
59 a8083063 Iustin Pop
    if cfg_file is None:
60 a8083063 Iustin Pop
      self._cfg_file = constants.CLUSTER_CONF_FILE
61 a8083063 Iustin Pop
    else:
62 a8083063 Iustin Pop
      self._cfg_file = cfg_file
63 923b1523 Iustin Pop
    self._temporary_ids = set()
64 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
65 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
66 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
67 89e1fc26 Iustin Pop
    # file than after it was modified
68 89e1fc26 Iustin Pop
    self._my_hostname = utils.HostInfo().name
69 a8083063 Iustin Pop
70 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
71 a8083063 Iustin Pop
  @staticmethod
72 a8083063 Iustin Pop
  def IsCluster():
73 a8083063 Iustin Pop
    """Check if the cluster is configured.
74 a8083063 Iustin Pop

75 a8083063 Iustin Pop
    """
76 a8083063 Iustin Pop
    return os.path.exists(constants.CLUSTER_CONF_FILE)
77 a8083063 Iustin Pop
78 a8083063 Iustin Pop
  def GenerateMAC(self):
79 a8083063 Iustin Pop
    """Generate a MAC for an instance.
80 a8083063 Iustin Pop

81 a8083063 Iustin Pop
    This should check the current instances for duplicates.
82 a8083063 Iustin Pop

83 a8083063 Iustin Pop
    """
84 a8083063 Iustin Pop
    self._OpenConfig()
85 a8083063 Iustin Pop
    self._ReleaseLock()
86 a8083063 Iustin Pop
    prefix = self._config_data.cluster.mac_prefix
87 a8083063 Iustin Pop
    all_macs = self._AllMACs()
88 a8083063 Iustin Pop
    retries = 64
89 a8083063 Iustin Pop
    while retries > 0:
90 a8083063 Iustin Pop
      byte1 = random.randrange(0, 256)
91 a8083063 Iustin Pop
      byte2 = random.randrange(0, 256)
92 a8083063 Iustin Pop
      byte3 = random.randrange(0, 256)
93 a8083063 Iustin Pop
      mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
94 a8083063 Iustin Pop
      if mac not in all_macs:
95 a8083063 Iustin Pop
        break
96 a8083063 Iustin Pop
      retries -= 1
97 a8083063 Iustin Pop
    else:
98 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't generate unique MAC")
99 a8083063 Iustin Pop
    return mac
100 a8083063 Iustin Pop
101 1862d460 Alexander Schreiber
  def IsMacInUse(self, mac):
102 1862d460 Alexander Schreiber
    """Predicate: check if the specified MAC is in use in the Ganeti cluster.
103 1862d460 Alexander Schreiber

104 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
105 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
106 1862d460 Alexander Schreiber

107 1862d460 Alexander Schreiber
    """
108 1862d460 Alexander Schreiber
    self._OpenConfig()
109 1862d460 Alexander Schreiber
    self._ReleaseLock()
110 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
111 1862d460 Alexander Schreiber
    return mac in all_macs
112 1862d460 Alexander Schreiber
113 923b1523 Iustin Pop
  def _ComputeAllLVs(self):
114 923b1523 Iustin Pop
    """Compute the list of all LVs.
115 923b1523 Iustin Pop

116 923b1523 Iustin Pop
    """
117 923b1523 Iustin Pop
    self._OpenConfig()
118 923b1523 Iustin Pop
    self._ReleaseLock()
119 923b1523 Iustin Pop
    lvnames = set()
120 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
121 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
122 923b1523 Iustin Pop
      for lv_list in node_data.values():
123 923b1523 Iustin Pop
        lvnames.update(lv_list)
124 923b1523 Iustin Pop
    return lvnames
125 923b1523 Iustin Pop
126 923b1523 Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
127 923b1523 Iustin Pop
    """Generate an unique disk name.
128 923b1523 Iustin Pop

129 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
130 923b1523 Iustin Pop
    duplicates.
131 923b1523 Iustin Pop

132 923b1523 Iustin Pop
    Args:
133 923b1523 Iustin Pop
      - exceptions: a list with some other names which should be checked
134 923b1523 Iustin Pop
                    for uniqueness (used for example when you want to get
135 923b1523 Iustin Pop
                    more than one id at one time without adding each one in
136 923b1523 Iustin Pop
                    turn to the config file
137 923b1523 Iustin Pop

138 923b1523 Iustin Pop
    Returns: the unique id as a string
139 923b1523 Iustin Pop

140 923b1523 Iustin Pop
    """
141 923b1523 Iustin Pop
    existing = set()
142 923b1523 Iustin Pop
    existing.update(self._temporary_ids)
143 923b1523 Iustin Pop
    existing.update(self._ComputeAllLVs())
144 923b1523 Iustin Pop
    existing.update(self._config_data.instances.keys())
145 923b1523 Iustin Pop
    existing.update(self._config_data.nodes.keys())
146 923b1523 Iustin Pop
    if exceptions is not None:
147 923b1523 Iustin Pop
      existing.update(exceptions)
148 923b1523 Iustin Pop
    retries = 64
149 923b1523 Iustin Pop
    while retries > 0:
150 24818e8f Michael Hanselmann
      unique_id = utils.NewUUID()
151 923b1523 Iustin Pop
      if unique_id not in existing and unique_id is not None:
152 923b1523 Iustin Pop
        break
153 923b1523 Iustin Pop
    else:
154 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Not able generate an unique ID"
155 3ecf6786 Iustin Pop
                                      " (last tried ID: %s" % unique_id)
156 923b1523 Iustin Pop
    self._temporary_ids.add(unique_id)
157 923b1523 Iustin Pop
    return unique_id
158 923b1523 Iustin Pop
159 a8083063 Iustin Pop
  def _AllMACs(self):
160 a8083063 Iustin Pop
    """Return all MACs present in the config.
161 a8083063 Iustin Pop

162 a8083063 Iustin Pop
    """
163 a8083063 Iustin Pop
    self._OpenConfig()
164 a8083063 Iustin Pop
    self._ReleaseLock()
165 a8083063 Iustin Pop
166 a8083063 Iustin Pop
    result = []
167 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
168 a8083063 Iustin Pop
      for nic in instance.nics:
169 a8083063 Iustin Pop
        result.append(nic.mac)
170 a8083063 Iustin Pop
171 a8083063 Iustin Pop
    return result
172 a8083063 Iustin Pop
173 a8083063 Iustin Pop
  def VerifyConfig(self):
174 a8083063 Iustin Pop
    """Stub verify function.
175 a8083063 Iustin Pop
    """
176 a8083063 Iustin Pop
    self._OpenConfig()
177 a8083063 Iustin Pop
    self._ReleaseLock()
178 a8083063 Iustin Pop
179 a8083063 Iustin Pop
    result = []
180 a8083063 Iustin Pop
    seen_macs = []
181 a8083063 Iustin Pop
    data = self._config_data
182 a8083063 Iustin Pop
    for instance_name in data.instances:
183 a8083063 Iustin Pop
      instance = data.instances[instance_name]
184 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
185 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
186 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
187 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
188 a8083063 Iustin Pop
        if snode not in data.nodes:
189 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
190 a8083063 Iustin Pop
                        (instance_name, snode))
191 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
192 a8083063 Iustin Pop
        if nic.mac in seen_macs:
193 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
194 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
195 a8083063 Iustin Pop
        else:
196 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
197 a8083063 Iustin Pop
    return result
198 a8083063 Iustin Pop
199 a8083063 Iustin Pop
  def SetDiskID(self, disk, node_name):
200 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
201 a8083063 Iustin Pop

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

204 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
205 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
206 a8083063 Iustin Pop
    node.
207 a8083063 Iustin Pop

208 a8083063 Iustin Pop
    """
209 a8083063 Iustin Pop
    if disk.children:
210 a8083063 Iustin Pop
      for child in disk.children:
211 a8083063 Iustin Pop
        self.SetDiskID(child, node_name)
212 a8083063 Iustin Pop
213 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
214 a8083063 Iustin Pop
      return
215 a1f445d3 Iustin Pop
    if disk.dev_type in constants.LDS_DRBD:
216 a8083063 Iustin Pop
      pnode, snode, port = disk.logical_id
217 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
218 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
219 3ecf6786 Iustin Pop
                                        node_name)
220 a8083063 Iustin Pop
      pnode_info = self.GetNodeInfo(pnode)
221 a8083063 Iustin Pop
      snode_info = self.GetNodeInfo(snode)
222 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
223 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
224 a8083063 Iustin Pop
                                        " for %s" % str(disk))
225 a8083063 Iustin Pop
      if pnode == node_name:
226 a8083063 Iustin Pop
        disk.physical_id = (pnode_info.secondary_ip, port,
227 a8083063 Iustin Pop
                            snode_info.secondary_ip, port)
228 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
229 a8083063 Iustin Pop
        disk.physical_id = (snode_info.secondary_ip, port,
230 a8083063 Iustin Pop
                            pnode_info.secondary_ip, port)
231 a8083063 Iustin Pop
    else:
232 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
233 a8083063 Iustin Pop
    return
234 a8083063 Iustin Pop
235 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
236 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
237 b2fddf63 Iustin Pop

238 b2fddf63 Iustin Pop
    """
239 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
240 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
241 264bb3c5 Michael Hanselmann
242 264bb3c5 Michael Hanselmann
    self._OpenConfig()
243 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
244 264bb3c5 Michael Hanselmann
    self._WriteConfig()
245 264bb3c5 Michael Hanselmann
246 b2fddf63 Iustin Pop
  def GetPortList(self):
247 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
248 264bb3c5 Michael Hanselmann

249 264bb3c5 Michael Hanselmann
    """
250 264bb3c5 Michael Hanselmann
    self._OpenConfig()
251 264bb3c5 Michael Hanselmann
    self._ReleaseLock()
252 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
253 264bb3c5 Michael Hanselmann
254 a8083063 Iustin Pop
  def AllocatePort(self):
255 a8083063 Iustin Pop
    """Allocate a port.
256 a8083063 Iustin Pop

257 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
258 b2fddf63 Iustin Pop
    default port range (and in this case we increase
259 b2fddf63 Iustin Pop
    highest_used_port).
260 a8083063 Iustin Pop

261 a8083063 Iustin Pop
    """
262 a8083063 Iustin Pop
    self._OpenConfig()
263 a8083063 Iustin Pop
264 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
265 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
266 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
267 264bb3c5 Michael Hanselmann
    else:
268 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
269 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
270 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
271 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
272 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
273 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
274 a8083063 Iustin Pop
275 a8083063 Iustin Pop
    self._WriteConfig()
276 a8083063 Iustin Pop
    return port
277 a8083063 Iustin Pop
278 a8083063 Iustin Pop
  def GetHostKey(self):
279 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
280 a8083063 Iustin Pop

281 a8083063 Iustin Pop
    Args: None
282 a8083063 Iustin Pop

283 a8083063 Iustin Pop
    Returns: rsa hostkey
284 a8083063 Iustin Pop
    """
285 a8083063 Iustin Pop
    self._OpenConfig()
286 a8083063 Iustin Pop
    self._ReleaseLock()
287 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
288 a8083063 Iustin Pop
289 a8083063 Iustin Pop
  def AddInstance(self, instance):
290 a8083063 Iustin Pop
    """Add an instance to the config.
291 a8083063 Iustin Pop

292 a8083063 Iustin Pop
    This should be used after creating a new instance.
293 a8083063 Iustin Pop

294 a8083063 Iustin Pop
    Args:
295 a8083063 Iustin Pop
      instance: the instance object
296 a8083063 Iustin Pop
    """
297 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
298 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
299 a8083063 Iustin Pop
300 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
301 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
302 e00fb268 Iustin Pop
      logger.Info("Instance '%s' DISK_LAYOUT: %s" % (instance.name, all_lvs))
303 923b1523 Iustin Pop
304 a8083063 Iustin Pop
    self._OpenConfig()
305 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
306 a8083063 Iustin Pop
    self._WriteConfig()
307 a8083063 Iustin Pop
308 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
309 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
310 a8083063 Iustin Pop

311 a8083063 Iustin Pop
    """
312 6a408fb2 Iustin Pop
    if status not in ("up", "down"):
313 6a408fb2 Iustin Pop
      raise errors.ProgrammerError("Invalid status '%s' passed to"
314 6a408fb2 Iustin Pop
                                   " ConfigWriter._SetInstanceStatus()" %
315 6a408fb2 Iustin Pop
                                   status)
316 a8083063 Iustin Pop
    self._OpenConfig()
317 a8083063 Iustin Pop
318 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
319 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
320 3ecf6786 Iustin Pop
                                      instance_name)
321 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
322 455a3445 Iustin Pop
    if instance.status != status:
323 455a3445 Iustin Pop
      instance.status = status
324 455a3445 Iustin Pop
      self._WriteConfig()
325 a8083063 Iustin Pop
326 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
327 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
328 6a408fb2 Iustin Pop

329 6a408fb2 Iustin Pop
    """
330 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
331 6a408fb2 Iustin Pop
332 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
333 a8083063 Iustin Pop
    """Remove the instance from the configuration.
334 a8083063 Iustin Pop

335 a8083063 Iustin Pop
    """
336 a8083063 Iustin Pop
    self._OpenConfig()
337 a8083063 Iustin Pop
338 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
339 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
340 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
341 a8083063 Iustin Pop
    self._WriteConfig()
342 a8083063 Iustin Pop
343 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
344 fc95f88f Iustin Pop
    """Rename an instance.
345 fc95f88f Iustin Pop

346 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
347 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
348 fc95f88f Iustin Pop
    rename.
349 fc95f88f Iustin Pop

350 fc95f88f Iustin Pop
    """
351 fc95f88f Iustin Pop
    self._OpenConfig()
352 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
353 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
354 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
355 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
356 fc95f88f Iustin Pop
    inst.name = new_name
357 b23c4333 Manuel Franceschini
358 b23c4333 Manuel Franceschini
    for disk in inst.disks:
359 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
360 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
361 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
362 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
363 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
364 b23c4333 Manuel Franceschini
                                                           inst.name,
365 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
366 b23c4333 Manuel Franceschini
367 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
368 fc95f88f Iustin Pop
    self._WriteConfig()
369 fc95f88f Iustin Pop
370 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
371 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
372 a8083063 Iustin Pop

373 a8083063 Iustin Pop
    """
374 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
375 a8083063 Iustin Pop
376 a8083063 Iustin Pop
  def GetInstanceList(self):
377 a8083063 Iustin Pop
    """Get the list of instances.
378 a8083063 Iustin Pop

379 a8083063 Iustin Pop
    Returns:
380 a8083063 Iustin Pop
      array of instances, ex. ['instance2.example.com','instance1.example.com']
381 a8083063 Iustin Pop
      these contains all the instances, also the ones in Admin_down state
382 a8083063 Iustin Pop

383 a8083063 Iustin Pop
    """
384 a8083063 Iustin Pop
    self._OpenConfig()
385 a8083063 Iustin Pop
    self._ReleaseLock()
386 a8083063 Iustin Pop
387 a8083063 Iustin Pop
    return self._config_data.instances.keys()
388 a8083063 Iustin Pop
389 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
390 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
391 a8083063 Iustin Pop

392 a8083063 Iustin Pop
    """
393 a8083063 Iustin Pop
    self._OpenConfig()
394 a8083063 Iustin Pop
    self._ReleaseLock()
395 a8083063 Iustin Pop
396 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
397 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
398 a8083063 Iustin Pop
399 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
400 a8083063 Iustin Pop
    """Returns informations about an instance.
401 a8083063 Iustin Pop

402 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
403 a8083063 Iustin Pop
    an instance are taken from the live systems.
404 a8083063 Iustin Pop

405 a8083063 Iustin Pop
    Args:
406 a8083063 Iustin Pop
      instance: name of the instance, ex instance1.example.com
407 a8083063 Iustin Pop

408 a8083063 Iustin Pop
    Returns:
409 a8083063 Iustin Pop
      the instance object
410 a8083063 Iustin Pop

411 a8083063 Iustin Pop
    """
412 a8083063 Iustin Pop
    self._OpenConfig()
413 a8083063 Iustin Pop
    self._ReleaseLock()
414 a8083063 Iustin Pop
415 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
416 a8083063 Iustin Pop
      return None
417 a8083063 Iustin Pop
418 a8083063 Iustin Pop
    return self._config_data.instances[instance_name]
419 a8083063 Iustin Pop
420 a8083063 Iustin Pop
  def AddNode(self, node):
421 a8083063 Iustin Pop
    """Add a node to the configuration.
422 a8083063 Iustin Pop

423 a8083063 Iustin Pop
    Args:
424 a8083063 Iustin Pop
      node: an object.Node instance
425 a8083063 Iustin Pop

426 a8083063 Iustin Pop
    """
427 a8083063 Iustin Pop
    self._OpenConfig()
428 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
429 a8083063 Iustin Pop
    self._WriteConfig()
430 a8083063 Iustin Pop
431 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
432 a8083063 Iustin Pop
    """Remove a node from the configuration.
433 a8083063 Iustin Pop

434 a8083063 Iustin Pop
    """
435 a8083063 Iustin Pop
    self._OpenConfig()
436 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
437 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
438 a8083063 Iustin Pop
439 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
440 a8083063 Iustin Pop
    self._WriteConfig()
441 a8083063 Iustin Pop
442 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
443 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
444 a8083063 Iustin Pop

445 a8083063 Iustin Pop
    """
446 a8083063 Iustin Pop
    self._OpenConfig()
447 a8083063 Iustin Pop
    self._ReleaseLock()
448 a8083063 Iustin Pop
449 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
450 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
451 a8083063 Iustin Pop
452 a8083063 Iustin Pop
  def GetNodeInfo(self, node_name):
453 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
454 a8083063 Iustin Pop

455 a8083063 Iustin Pop
    Args: node: nodename (tuple) of the node
456 a8083063 Iustin Pop

457 a8083063 Iustin Pop
    Returns: the node object
458 a8083063 Iustin Pop

459 a8083063 Iustin Pop
    """
460 a8083063 Iustin Pop
    self._OpenConfig()
461 a8083063 Iustin Pop
    self._ReleaseLock()
462 a8083063 Iustin Pop
463 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
464 a8083063 Iustin Pop
      return None
465 a8083063 Iustin Pop
466 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
467 a8083063 Iustin Pop
468 a8083063 Iustin Pop
  def GetNodeList(self):
469 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
470 a8083063 Iustin Pop

471 a8083063 Iustin Pop
    """
472 a8083063 Iustin Pop
    self._OpenConfig()
473 a8083063 Iustin Pop
    self._ReleaseLock()
474 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
475 a8083063 Iustin Pop
476 a8083063 Iustin Pop
  def DumpConfig(self):
477 a8083063 Iustin Pop
    """Return the entire configuration of the cluster.
478 a8083063 Iustin Pop
    """
479 a8083063 Iustin Pop
    self._OpenConfig()
480 a8083063 Iustin Pop
    self._ReleaseLock()
481 a8083063 Iustin Pop
    return self._config_data
482 a8083063 Iustin Pop
483 a8083063 Iustin Pop
  def _BumpSerialNo(self):
484 a8083063 Iustin Pop
    """Bump up the serial number of the config.
485 a8083063 Iustin Pop

486 a8083063 Iustin Pop
    """
487 a8083063 Iustin Pop
    self._config_data.cluster.serial_no += 1
488 a8083063 Iustin Pop
489 a8083063 Iustin Pop
  def _OpenConfig(self):
490 a8083063 Iustin Pop
    """Read the config data from disk.
491 a8083063 Iustin Pop

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

496 a8083063 Iustin Pop
    """
497 a8083063 Iustin Pop
    try:
498 a8083063 Iustin Pop
      st = os.stat(self._cfg_file)
499 a8083063 Iustin Pop
    except OSError, err:
500 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't stat config file: %s" % err)
501 a8083063 Iustin Pop
    if (self._config_data is not None and
502 a8083063 Iustin Pop
        self._config_time is not None and
503 264bb3c5 Michael Hanselmann
        self._config_time == st.st_mtime and
504 264bb3c5 Michael Hanselmann
        self._config_size == st.st_size and
505 264bb3c5 Michael Hanselmann
        self._config_inode == st.st_ino):
506 a8083063 Iustin Pop
      # data is current, so skip loading of config file
507 a8083063 Iustin Pop
      return
508 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
509 a8083063 Iustin Pop
    try:
510 a8083063 Iustin Pop
      try:
511 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
512 a8083063 Iustin Pop
      except Exception, err:
513 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
514 a8083063 Iustin Pop
    finally:
515 a8083063 Iustin Pop
      f.close()
516 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
517 a8083063 Iustin Pop
        not hasattr(data.cluster, 'config_version')):
518 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
519 3ecf6786 Iustin Pop
                                      " (missing cluster.config_version)")
520 a8083063 Iustin Pop
    if data.cluster.config_version != constants.CONFIG_VERSION:
521 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Cluster configuration version"
522 3ecf6786 Iustin Pop
                                      " mismatch, got %s instead of %s" %
523 3ecf6786 Iustin Pop
                                      (data.cluster.config_version,
524 3ecf6786 Iustin Pop
                                       constants.CONFIG_VERSION))
525 a8083063 Iustin Pop
    self._config_data = data
526 a8083063 Iustin Pop
    self._config_time = st.st_mtime
527 264bb3c5 Michael Hanselmann
    self._config_size = st.st_size
528 264bb3c5 Michael Hanselmann
    self._config_inode = st.st_ino
529 a8083063 Iustin Pop
530 a8083063 Iustin Pop
  def _ReleaseLock(self):
531 a8083063 Iustin Pop
    """xxxx
532 a8083063 Iustin Pop
    """
533 a8083063 Iustin Pop
534 a8083063 Iustin Pop
  def _DistributeConfig(self):
535 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
536 a8083063 Iustin Pop

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

540 a8083063 Iustin Pop
    """
541 a8083063 Iustin Pop
    if self._offline:
542 a8083063 Iustin Pop
      return True
543 a8083063 Iustin Pop
    bad = False
544 a8083063 Iustin Pop
    nodelist = self.GetNodeList()
545 89e1fc26 Iustin Pop
    myhostname = self._my_hostname
546 a8083063 Iustin Pop
547 41362e70 Guido Trotter
    nodelist.remove(myhostname)
548 a8083063 Iustin Pop
549 41362e70 Guido Trotter
    result = rpc.call_upload_file(nodelist, self._cfg_file)
550 41362e70 Guido Trotter
    for node in nodelist:
551 a8083063 Iustin Pop
      if not result[node]:
552 a8083063 Iustin Pop
        logger.Error("copy of file %s to node %s failed" %
553 a8083063 Iustin Pop
                     (self._cfg_file, node))
554 a8083063 Iustin Pop
        bad = True
555 a8083063 Iustin Pop
    return not bad
556 a8083063 Iustin Pop
557 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
558 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
559 a8083063 Iustin Pop

560 a8083063 Iustin Pop
    """
561 a8083063 Iustin Pop
    if destination is None:
562 a8083063 Iustin Pop
      destination = self._cfg_file
563 a8083063 Iustin Pop
    self._BumpSerialNo()
564 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
565 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
566 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
567 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
568 a8083063 Iustin Pop
    try:
569 8d14b30d Iustin Pop
      f.write(txt)
570 a8083063 Iustin Pop
      os.fsync(f.fileno())
571 a8083063 Iustin Pop
    finally:
572 a8083063 Iustin Pop
      f.close()
573 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
574 a8083063 Iustin Pop
    os.rename(name, destination)
575 14e15659 Iustin Pop
    self.write_count += 1
576 fee9556c Iustin Pop
    # re-set our cache as not to re-read the config file
577 fee9556c Iustin Pop
    try:
578 fee9556c Iustin Pop
      st = os.stat(destination)
579 fee9556c Iustin Pop
    except OSError, err:
580 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't stat config file: %s" % err)
581 fee9556c Iustin Pop
    self._config_time = st.st_mtime
582 fee9556c Iustin Pop
    self._config_size = st.st_size
583 fee9556c Iustin Pop
    self._config_inode = st.st_ino
584 fee9556c Iustin Pop
    # and redistribute the config file
585 a8083063 Iustin Pop
    self._DistributeConfig()
586 a8083063 Iustin Pop
587 a8083063 Iustin Pop
  def InitConfig(self, node, primary_ip, secondary_ip,
588 5fcdc80d Iustin Pop
                 hostkeypub, mac_prefix, vg_name, def_bridge):
589 a8083063 Iustin Pop
    """Create the initial cluster configuration.
590 a8083063 Iustin Pop

591 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
592 a8083063 Iustin Pop
    node, and no instances or operating systmes.
593 a8083063 Iustin Pop

594 a8083063 Iustin Pop
    Args:
595 a8083063 Iustin Pop
      node: the nodename of the initial node
596 a8083063 Iustin Pop
      primary_ip: the IP address of the current host
597 a8083063 Iustin Pop
      secondary_ip: the secondary IP of the current host or None
598 a8083063 Iustin Pop
      hostkeypub: the public hostkey of this host
599 a8083063 Iustin Pop

600 264bb3c5 Michael Hanselmann
    """
601 a8083063 Iustin Pop
    hu_port = constants.FIRST_DRBD_PORT - 1
602 a8083063 Iustin Pop
    globalconfig = objects.Cluster(config_version=constants.CONFIG_VERSION,
603 330cda58 Michael Hanselmann
                                   serial_no=1,
604 a8083063 Iustin Pop
                                   rsahostkeypub=hostkeypub,
605 a8083063 Iustin Pop
                                   highest_used_port=hu_port,
606 a8083063 Iustin Pop
                                   mac_prefix=mac_prefix,
607 a8083063 Iustin Pop
                                   volume_group_name=vg_name,
608 b2fddf63 Iustin Pop
                                   default_bridge=def_bridge,
609 b2fddf63 Iustin Pop
                                   tcpudp_port_pool=set())
610 a8083063 Iustin Pop
    if secondary_ip is None:
611 a8083063 Iustin Pop
      secondary_ip = primary_ip
612 a8083063 Iustin Pop
    nodeconfig = objects.Node(name=node, primary_ip=primary_ip,
613 a8083063 Iustin Pop
                              secondary_ip=secondary_ip)
614 a8083063 Iustin Pop
615 a8083063 Iustin Pop
    self._config_data = objects.ConfigData(nodes={node: nodeconfig},
616 a8083063 Iustin Pop
                                           instances={},
617 b2fddf63 Iustin Pop
                                           cluster=globalconfig)
618 a8083063 Iustin Pop
    self._WriteConfig()
619 a8083063 Iustin Pop
620 a8083063 Iustin Pop
  def GetVGName(self):
621 a8083063 Iustin Pop
    """Return the volume group name.
622 a8083063 Iustin Pop

623 a8083063 Iustin Pop
    """
624 a8083063 Iustin Pop
    self._OpenConfig()
625 a8083063 Iustin Pop
    self._ReleaseLock()
626 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
627 a8083063 Iustin Pop
628 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
629 89ff8e15 Manuel Franceschini
    """Set the volume group name.
630 89ff8e15 Manuel Franceschini

631 89ff8e15 Manuel Franceschini
    """
632 89ff8e15 Manuel Franceschini
    self._OpenConfig()
633 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
634 89ff8e15 Manuel Franceschini
    self._WriteConfig()
635 89ff8e15 Manuel Franceschini
636 a8083063 Iustin Pop
  def GetDefBridge(self):
637 a8083063 Iustin Pop
    """Return the default bridge.
638 a8083063 Iustin Pop

639 a8083063 Iustin Pop
    """
640 a8083063 Iustin Pop
    self._OpenConfig()
641 a8083063 Iustin Pop
    self._ReleaseLock()
642 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
643 a8083063 Iustin Pop
644 a8083063 Iustin Pop
  def GetMACPrefix(self):
645 a8083063 Iustin Pop
    """Return the mac prefix.
646 a8083063 Iustin Pop

647 a8083063 Iustin Pop
    """
648 a8083063 Iustin Pop
    self._OpenConfig()
649 a8083063 Iustin Pop
    self._ReleaseLock()
650 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
651 62779dd0 Iustin Pop
652 62779dd0 Iustin Pop
  def GetClusterInfo(self):
653 62779dd0 Iustin Pop
    """Returns informations about the cluster
654 62779dd0 Iustin Pop

655 62779dd0 Iustin Pop
    Returns:
656 62779dd0 Iustin Pop
      the cluster object
657 62779dd0 Iustin Pop

658 62779dd0 Iustin Pop
    """
659 62779dd0 Iustin Pop
    self._OpenConfig()
660 62779dd0 Iustin Pop
    self._ReleaseLock()
661 62779dd0 Iustin Pop
662 62779dd0 Iustin Pop
    return self._config_data.cluster
663 e00fb268 Iustin Pop
664 e00fb268 Iustin Pop
  def Update(self, target):
665 e00fb268 Iustin Pop
    """Notify function to be called after updates.
666 e00fb268 Iustin Pop

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

673 e00fb268 Iustin Pop
    """
674 e00fb268 Iustin Pop
    if self._config_data is None:
675 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
676 3ecf6786 Iustin Pop
                                   " cannot save.")
677 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
678 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
679 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
680 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
681 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
682 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
683 e00fb268 Iustin Pop
    else:
684 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
685 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
686 e00fb268 Iustin Pop
    if not test:
687 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
688 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
689 e00fb268 Iustin Pop
    self._WriteConfig()