Revision a0c9f010

b/lib/Makefile.am
5 5
pkgpython_PYTHON = __init__.py backend.py cli.py cmdlib.py config.py \
6 6
	objects.py errors.py logger.py ssh.py utils.py rpc.py \
7 7
	bdev.py opcodes.py mcpu.py constants.py \
8
	ssconf.py locking.py luxi.py jqueue.py serializer.py
8
	ssconf.py locking.py luxi.py jqueue.py serializer.py \
9
	bootstrap.py
9 10
python_files = $(pkgpython_PYTHON)
10 11

  
11 12
all-local: _autoconf.py
b/lib/bootstrap.py
1
#
2
#
3

  
4
# Copyright (C) 2006, 2007, 2008 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Functions to bootstrap a new cluster.
23

  
24
"""
25

  
26
import os
27
import os.path
28
import sha
29
import re
30

  
31
from ganeti import rpc
32
from ganeti import ssh
33
from ganeti import utils
34
from ganeti import errors
35
from ganeti import config
36
from ganeti import constants
37
from ganeti import ssconf
38

  
39

  
40
def _InitSSHSetup(node):
41
  """Setup the SSH configuration for the cluster.
42

  
43

  
44
  This generates a dsa keypair for root, adds the pub key to the
45
  permitted hosts and adds the hostkey to its own known hosts.
46

  
47
  Args:
48
    node: the name of this host as a fqdn
49

  
50
  """
51
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
52

  
53
  for name in priv_key, pub_key:
54
    if os.path.exists(name):
55
      utils.CreateBackup(name)
56
    utils.RemoveFile(name)
57

  
58
  result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
59
                         "-f", priv_key,
60
                         "-q", "-N", ""])
61
  if result.failed:
62
    raise errors.OpExecError("Could not generate ssh keypair, error %s" %
63
                             result.output)
64

  
65
  f = open(pub_key, 'r')
66
  try:
67
    utils.AddAuthorizedKey(auth_keys, f.read(8192))
68
  finally:
69
    f.close()
70

  
71

  
72
def _InitGanetiServerSetup(ss):
73
  """Setup the necessary configuration for the initial node daemon.
74

  
75
  This creates the nodepass file containing the shared password for
76
  the cluster and also generates the SSL certificate.
77

  
78
  """
79
  # Create pseudo random password
80
  randpass = sha.new(os.urandom(64)).hexdigest()
81
  # and write it into sstore
82
  ss.SetKey(ss.SS_NODED_PASS, randpass)
83

  
84
  result = utils.RunCmd(["openssl", "req", "-new", "-newkey", "rsa:1024",
85
                         "-days", str(365*5), "-nodes", "-x509",
86
                         "-keyout", constants.SSL_CERT_FILE,
87
                         "-out", constants.SSL_CERT_FILE, "-batch"])
88
  if result.failed:
89
    raise errors.OpExecError("could not generate server ssl cert, command"
90
                             " %s had exitcode %s and error message %s" %
91
                             (result.cmd, result.exit_code, result.output))
92

  
93
  os.chmod(constants.SSL_CERT_FILE, 0400)
94

  
95
  result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"])
96

  
97
  if result.failed:
98
    raise errors.OpExecError("Could not start the node daemon, command %s"
99
                             " had exitcode %s and error %s" %
100
                             (result.cmd, result.exit_code, result.output))
101

  
102

  
103
def InitCluster(cluster_name, hypervisor_type, mac_prefix, def_bridge,
104
                master_netdev, file_storage_dir,
105
                secondary_ip=None,
106
                vg_name=None):
107
  """Initialise the cluster.
108

  
109
  """
110
  if config.ConfigWriter.IsCluster():
111
    raise errors.OpPrereqError("Cluster is already initialised")
112

  
113
  if hypervisor_type == constants.HT_XEN_HVM31:
114
    if not os.path.exists(constants.VNC_PASSWORD_FILE):
115
      raise errors.OpPrereqError("Please prepare the cluster VNC"
116
                                 "password file %s" %
117
                                 constants.VNC_PASSWORD_FILE)
118

  
119
  hostname = utils.HostInfo()
120

  
121
  if hostname.ip.startswith("127."):
122
    raise errors.OpPrereqError("This host's IP resolves to the private"
123
                               " range (%s). Please fix DNS or %s." %
124
                               (hostname.ip, constants.ETC_HOSTS))
125

  
126
  if not utils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT,
127
                       source=constants.LOCALHOST_IP_ADDRESS):
128
    raise errors.OpPrereqError("Inconsistency: this host's name resolves"
129
                               " to %s,\nbut this ip address does not"
130
                               " belong to this host."
131
                               " Aborting." % hostname.ip)
132

  
133
  clustername = utils.HostInfo(cluster_name)
134

  
135
  if utils.TcpPing(clustername.ip, constants.DEFAULT_NODED_PORT,
136
                   timeout=5):
137
    raise errors.OpPrereqError("Cluster IP already active. Aborting.")
138

  
139
  if secondary_ip:
140
    if not utils.IsValidIP(secondary_ip):
141
      raise errors.OpPrereqError("Invalid secondary ip given")
142
    if (secondary_ip != hostname.ip and
143
        (not utils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
144
                           source=constants.LOCALHOST_IP_ADDRESS))):
145
      raise errors.OpPrereqError("You gave %s as secondary IP,"
146
                                 " but it does not belong to this host." %
147
                                 secondary_ip)
148

  
149
  if vg_name is not None:
150
    # Check if volume group is valid
151
    vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name,
152
                                          constants.MIN_VG_SIZE)
153
    if vgstatus:
154
      raise errors.OpPrereqError("Error: %s\nspecify --no-lvm-storage if"
155
                                 " you are not using lvm" % vgstatus)
156

  
157
  file_storage_dir = os.path.normpath(file_storage_dir)
158

  
159
  if not os.path.isabs(file_storage_dir):
160
    raise errors.OpPrereqError("The file storage directory you passed is"
161
                               " not an absolute path.")
162

  
163
  if not os.path.exists(file_storage_dir):
164
    try:
165
      os.makedirs(file_storage_dir, 0750)
166
    except OSError, err:
167
      raise errors.OpPrereqError("Cannot create file storage directory"
168
                                 " '%s': %s" %
169
                                 (file_storage_dir, err))
170

  
171
  if not os.path.isdir(file_storage_dir):
172
    raise errors.OpPrereqError("The file storage directory '%s' is not"
173
                               " a directory." % file_storage_dir)
174

  
175
  if not re.match("^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$", mac_prefix):
176
    raise errors.OpPrereqError("Invalid mac prefix given '%s'" % mac_prefix)
177

  
178
  if hypervisor_type not in constants.HYPER_TYPES:
179
    raise errors.OpPrereqError("Invalid hypervisor type given '%s'" %
180
                               hypervisor_type)
181

  
182
  result = utils.RunCmd(["ip", "link", "show", "dev", master_netdev])
183
  if result.failed:
184
    raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
185
                               (master_netdev,
186
                                result.output.strip()))
187

  
188
  if not (os.path.isfile(constants.NODE_INITD_SCRIPT) and
189
          os.access(constants.NODE_INITD_SCRIPT, os.X_OK)):
190
    raise errors.OpPrereqError("Init.d script '%s' missing or not"
191
                               " executable." % constants.NODE_INITD_SCRIPT)
192

  
193
  # set up the simple store
194
  ss = ssconf.SimpleStore()
195
  ss.SetKey(ss.SS_HYPERVISOR, hypervisor_type)
196
  ss.SetKey(ss.SS_MASTER_NODE, hostname.name)
197
  ss.SetKey(ss.SS_MASTER_IP, clustername.ip)
198
  ss.SetKey(ss.SS_MASTER_NETDEV, master_netdev)
199
  ss.SetKey(ss.SS_CLUSTER_NAME, clustername.name)
200
  ss.SetKey(ss.SS_FILE_STORAGE_DIR, file_storage_dir)
201
  ss.SetKey(ss.SS_CONFIG_VERSION, constants.CONFIG_VERSION)
202

  
203
  # set up the inter-node password and certificate
204
  _InitGanetiServerSetup(ss)
205

  
206
  # start the master ip
207
  # TODO: Review rpc call from bootstrap
208
  rpc.call_node_start_master(hostname.name)
209

  
210
  # set up ssh config and /etc/hosts
211
  f = open(constants.SSH_HOST_RSA_PUB, 'r')
212
  try:
213
    sshline = f.read()
214
  finally:
215
    f.close()
216
  sshkey = sshline.split(" ")[1]
217

  
218
  utils.AddHostToEtcHosts(hostname.name)
219
  _InitSSHSetup(hostname.name)
220

  
221
  # init of cluster config file
222
  cfg = config.ConfigWriter()
223
  cfg.InitConfig(hostname.name, hostname.ip, secondary_ip, sshkey,
224
                 mac_prefix, vg_name, def_bridge)
225

  
226
  ssh.WriteKnownHostsFile(cfg, ss, constants.SSH_KNOWN_HOSTS_FILE)
b/lib/cmdlib.py
305 305
  return _BuildInstanceHookEnv(**args)
306 306

  
307 307

  
308
def _InitSSHSetup(node):
309
  """Setup the SSH configuration for the cluster.
310

  
311

  
312
  This generates a dsa keypair for root, adds the pub key to the
313
  permitted hosts and adds the hostkey to its own known hosts.
314

  
315
  Args:
316
    node: the name of this host as a fqdn
317

  
318
  """
319
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
320

  
321
  for name in priv_key, pub_key:
322
    if os.path.exists(name):
323
      utils.CreateBackup(name)
324
    utils.RemoveFile(name)
325

  
326
  result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
327
                         "-f", priv_key,
328
                         "-q", "-N", ""])
329
  if result.failed:
330
    raise errors.OpExecError("Could not generate ssh keypair, error %s" %
331
                             result.output)
332

  
333
  f = open(pub_key, 'r')
334
  try:
335
    utils.AddAuthorizedKey(auth_keys, f.read(8192))
336
  finally:
337
    f.close()
338

  
339

  
340
def _InitGanetiServerSetup(ss):
341
  """Setup the necessary configuration for the initial node daemon.
342

  
343
  This creates the nodepass file containing the shared password for
344
  the cluster and also generates the SSL certificate.
345

  
346
  """
347
  # Create pseudo random password
348
  randpass = sha.new(os.urandom(64)).hexdigest()
349
  # and write it into sstore
350
  ss.SetKey(ss.SS_NODED_PASS, randpass)
351

  
352
  result = utils.RunCmd(["openssl", "req", "-new", "-newkey", "rsa:1024",
353
                         "-days", str(365*5), "-nodes", "-x509",
354
                         "-keyout", constants.SSL_CERT_FILE,
355
                         "-out", constants.SSL_CERT_FILE, "-batch"])
356
  if result.failed:
357
    raise errors.OpExecError("could not generate server ssl cert, command"
358
                             " %s had exitcode %s and error message %s" %
359
                             (result.cmd, result.exit_code, result.output))
360

  
361
  os.chmod(constants.SSL_CERT_FILE, 0400)
362

  
363
  result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"])
364

  
365
  if result.failed:
366
    raise errors.OpExecError("Could not start the node daemon, command %s"
367
                             " had exitcode %s and error %s" %
368
                             (result.cmd, result.exit_code, result.output))
369

  
370

  
371 308
def _CheckInstanceBridgesExist(instance):
372 309
  """Check that the brigdes needed by an instance exist.
373 310

  
......
380 317
                               (brlist, instance.primary_node))
381 318

  
382 319

  
383
class LUInitCluster(LogicalUnit):
384
  """Initialise the cluster.
385

  
386
  """
387
  HPATH = "cluster-init"
388
  HTYPE = constants.HTYPE_CLUSTER
389
  _OP_REQP = ["cluster_name", "hypervisor_type", "mac_prefix",
390
              "def_bridge", "master_netdev", "file_storage_dir"]
391
  REQ_CLUSTER = False
392

  
393
  def BuildHooksEnv(self):
394
    """Build hooks env.
395

  
396
    Notes: Since we don't require a cluster, we must manually add
397
    ourselves in the post-run node list.
398

  
399
    """
400
    env = {"OP_TARGET": self.op.cluster_name}
401
    return env, [], [self.hostname.name]
402

  
403
  def CheckPrereq(self):
404
    """Verify that the passed name is a valid one.
405

  
406
    """
407
    if config.ConfigWriter.IsCluster():
408
      raise errors.OpPrereqError("Cluster is already initialised")
409

  
410
    if self.op.hypervisor_type == constants.HT_XEN_HVM31:
411
      if not os.path.exists(constants.VNC_PASSWORD_FILE):
412
        raise errors.OpPrereqError("Please prepare the cluster VNC"
413
                                   "password file %s" %
414
                                   constants.VNC_PASSWORD_FILE)
415

  
416
    self.hostname = hostname = utils.HostInfo()
417

  
418
    if hostname.ip.startswith("127."):
419
      raise errors.OpPrereqError("This host's IP resolves to the private"
420
                                 " range (%s). Please fix DNS or %s." %
421
                                 (hostname.ip, constants.ETC_HOSTS))
422

  
423
    if not utils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT,
424
                         source=constants.LOCALHOST_IP_ADDRESS):
425
      raise errors.OpPrereqError("Inconsistency: this host's name resolves"
426
                                 " to %s,\nbut this ip address does not"
427
                                 " belong to this host."
428
                                 " Aborting." % hostname.ip)
429

  
430
    self.clustername = clustername = utils.HostInfo(self.op.cluster_name)
431

  
432
    if utils.TcpPing(clustername.ip, constants.DEFAULT_NODED_PORT,
433
                     timeout=5):
434
      raise errors.OpPrereqError("Cluster IP already active. Aborting.")
435

  
436
    secondary_ip = getattr(self.op, "secondary_ip", None)
437
    if secondary_ip and not utils.IsValidIP(secondary_ip):
438
      raise errors.OpPrereqError("Invalid secondary ip given")
439
    if (secondary_ip and
440
        secondary_ip != hostname.ip and
441
        (not utils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
442
                           source=constants.LOCALHOST_IP_ADDRESS))):
443
      raise errors.OpPrereqError("You gave %s as secondary IP,"
444
                                 " but it does not belong to this host." %
445
                                 secondary_ip)
446
    self.secondary_ip = secondary_ip
447

  
448
    if not hasattr(self.op, "vg_name"):
449
      self.op.vg_name = None
450
    # if vg_name not None, checks if volume group is valid
451
    if self.op.vg_name:
452
      vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name,
453
                                            constants.MIN_VG_SIZE)
454
      if vgstatus:
455
        raise errors.OpPrereqError("Error: %s\nspecify --no-lvm-storage if"
456
                                   " you are not using lvm" % vgstatus)
457

  
458
    self.op.file_storage_dir = os.path.normpath(self.op.file_storage_dir)
459

  
460
    if not os.path.isabs(self.op.file_storage_dir):
461
      raise errors.OpPrereqError("The file storage directory you have is"
462
                                 " not an absolute path.")
463

  
464
    if not os.path.exists(self.op.file_storage_dir):
465
      try:
466
        os.makedirs(self.op.file_storage_dir, 0750)
467
      except OSError, err:
468
        raise errors.OpPrereqError("Cannot create file storage directory"
469
                                   " '%s': %s" %
470
                                   (self.op.file_storage_dir, err))
471

  
472
    if not os.path.isdir(self.op.file_storage_dir):
473
      raise errors.OpPrereqError("The file storage directory '%s' is not"
474
                                 " a directory." % self.op.file_storage_dir)
475

  
476
    if not re.match("^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$",
477
                    self.op.mac_prefix):
478
      raise errors.OpPrereqError("Invalid mac prefix given '%s'" %
479
                                 self.op.mac_prefix)
480

  
481
    if self.op.hypervisor_type not in constants.HYPER_TYPES:
482
      raise errors.OpPrereqError("Invalid hypervisor type given '%s'" %
483
                                 self.op.hypervisor_type)
484

  
485
    result = utils.RunCmd(["ip", "link", "show", "dev", self.op.master_netdev])
486
    if result.failed:
487
      raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
488
                                 (self.op.master_netdev,
489
                                  result.output.strip()))
490

  
491
    if not (os.path.isfile(constants.NODE_INITD_SCRIPT) and
492
            os.access(constants.NODE_INITD_SCRIPT, os.X_OK)):
493
      raise errors.OpPrereqError("Init.d script '%s' missing or not"
494
                                 " executable." % constants.NODE_INITD_SCRIPT)
495

  
496
  def Exec(self, feedback_fn):
497
    """Initialize the cluster.
498

  
499
    """
500
    clustername = self.clustername
501
    hostname = self.hostname
502

  
503
    # set up the simple store
504
    self.sstore = ss = ssconf.SimpleStore()
505
    ss.SetKey(ss.SS_HYPERVISOR, self.op.hypervisor_type)
506
    ss.SetKey(ss.SS_MASTER_NODE, hostname.name)
507
    ss.SetKey(ss.SS_MASTER_IP, clustername.ip)
508
    ss.SetKey(ss.SS_MASTER_NETDEV, self.op.master_netdev)
509
    ss.SetKey(ss.SS_CLUSTER_NAME, clustername.name)
510
    ss.SetKey(ss.SS_FILE_STORAGE_DIR, self.op.file_storage_dir)
511
    ss.SetKey(ss.SS_CONFIG_VERSION, constants.CONFIG_VERSION)
512

  
513
    # set up the inter-node password and certificate
514
    _InitGanetiServerSetup(ss)
515

  
516
    # start the master ip
517
    rpc.call_node_start_master(hostname.name)
518

  
519
    # set up ssh config and /etc/hosts
520
    f = open(constants.SSH_HOST_RSA_PUB, 'r')
521
    try:
522
      sshline = f.read()
523
    finally:
524
      f.close()
525
    sshkey = sshline.split(" ")[1]
526

  
527
    utils.AddHostToEtcHosts(hostname.name)
528
    _InitSSHSetup(hostname.name)
529

  
530
    # init of cluster config file
531
    self.cfg = cfgw = config.ConfigWriter()
532
    cfgw.InitConfig(hostname.name, hostname.ip, self.secondary_ip,
533
                    sshkey, self.op.mac_prefix,
534
                    self.op.vg_name, self.op.def_bridge)
535

  
536
    ssh.WriteKnownHostsFile(cfgw, ss, constants.SSH_KNOWN_HOSTS_FILE)
537

  
538

  
539 320
class LUDestroyCluster(NoHooksLU):
540 321
  """Logical unit for destroying the cluster.
541 322

  
b/lib/mcpu.py
43 43
  """Object which runs OpCodes"""
44 44
  DISPATCH_TABLE = {
45 45
    # Cluster
46
    opcodes.OpInitCluster: cmdlib.LUInitCluster,
47 46
    opcodes.OpDestroyCluster: cmdlib.LUDestroyCluster,
48 47
    opcodes.OpQueryClusterInfo: cmdlib.LUQueryClusterInfo,
49 48
    opcodes.OpClusterCopyFile: cmdlib.LUClusterCopyFile,
b/lib/opcodes.py
159 159
    return op
160 160

  
161 161

  
162
class OpInitCluster(OpCode):
163
  """Initialise the cluster."""
164
  OP_ID = "OP_CLUSTER_INIT"
165
  __slots__ = ["cluster_name", "secondary_ip", "hypervisor_type",
166
               "vg_name", "mac_prefix", "def_bridge", "master_netdev",
167
               "file_storage_dir"]
168

  
169

  
170 162
class OpDestroyCluster(OpCode):
171 163
  """Destroy the cluster."""
172 164
  OP_ID = "OP_CLUSTER_DESTROY"
b/scripts/gnt-cluster
28 28
from ganeti import constants
29 29
from ganeti import errors
30 30
from ganeti import utils
31
from ganeti import bootstrap
31 32

  
32 33

  
33 34
def InitCluster(opts, args):
......
46 47
  if opts.lvm_storage and not opts.vg_name:
47 48
    vg_name = constants.DEFAULT_VG
48 49

  
49
  op = opcodes.OpInitCluster(cluster_name=args[0],
50
                             secondary_ip=opts.secondary_ip,
51
                             hypervisor_type=opts.hypervisor_type,
52
                             vg_name=vg_name,
53
                             mac_prefix=opts.mac_prefix,
54
                             def_bridge=opts.def_bridge,
55
                             master_netdev=opts.master_netdev,
56
                             file_storage_dir=opts.file_storage_dir)
57
  SubmitOpCode(op)
50
  bootstrap.InitCluster(cluster_name=args[0],
51
                        secondary_ip=opts.secondary_ip,
52
                        hypervisor_type=opts.hypervisor_type,
53
                        vg_name=vg_name,
54
                        mac_prefix=opts.mac_prefix,
55
                        def_bridge=opts.def_bridge,
56
                        master_netdev=opts.master_netdev,
57
                        file_storage_dir=opts.file_storage_dir)
58 58
  return 0
59 59

  
60 60

  

Also available in: Unified diff