ganeti-noded 20.6 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#!/usr/bin/python
#

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


"""Ganeti node daemon"""

24 25 26
# functions in this module need to have a given name structure, so:
# pylint: disable-msg=C0103

Iustin Pop's avatar
Iustin Pop committed
27 28
import os
import sys
Guido Trotter's avatar
Guido Trotter committed
29
import SocketServer
Iustin Pop's avatar
Iustin Pop committed
30
import logging
31
import signal
Iustin Pop's avatar
Iustin Pop committed
32 33 34 35 36 37 38

from optparse import OptionParser

from ganeti import backend
from ganeti import constants
from ganeti import objects
from ganeti import errors
39
from ganeti import jstore
40
from ganeti import daemon
41
from ganeti import http
42
from ganeti import utils
Iustin Pop's avatar
Iustin Pop committed
43

44 45
import ganeti.http.server

Iustin Pop's avatar
Iustin Pop committed
46

47 48 49
queue_lock = None


50 51 52 53
def _RequireJobQueueLock(fn):
  """Decorator for job queue manipulating functions.

  """
54 55
  QUEUE_LOCK_TIMEOUT = 10

56 57
  def wrapper(*args, **kwargs):
    # Locking in exclusive, blocking mode because there could be several
58
    # children running at the same time. Waiting up to 10 seconds.
59
    queue_lock.Exclusive(blocking=True, timeout=QUEUE_LOCK_TIMEOUT)
60 61 62 63
    try:
      return fn(*args, **kwargs)
    finally:
      queue_lock.Unlock()
64

65 66 67
  return wrapper


68
class NodeHttpServer(http.server.HttpServer):
69 70 71 72 73
  """The server implementation.

  This class holds all methods exposed over the RPC interface.

  """
74
  def __init__(self, *args, **kwargs):
75
    http.server.HttpServer.__init__(self, *args, **kwargs)
76 77 78
    self.noded_pid = os.getpid()

  def HandleRequest(self, req):
79
    """Handle a request.
Iustin Pop's avatar
Iustin Pop committed
80

81
    """
82
    if req.request_method.upper() != http.HTTP_PUT:
83
      raise http.HttpBadRequest()
84

85
    path = req.request_path
Iustin Pop's avatar
Iustin Pop committed
86 87 88
    if path.startswith("/"):
      path = path[1:]

89 90
    method = getattr(self, "perspective_%s" % path, None)
    if method is None:
91
      raise http.HttpNotFound()
Iustin Pop's avatar
Iustin Pop committed
92

Iustin Pop's avatar
Iustin Pop committed
93
    try:
94
      try:
95
        return method(req.request_body)
96 97 98
      except:
        logging.exception("Error in RPC call")
        raise
99
    except errors.QuitGanetiException, err:
100
      # Tell parent to quit
101
      os.kill(self.noded_pid, signal.SIGTERM)
Iustin Pop's avatar
Iustin Pop committed
102 103 104

  # the new block devices  --------------------------

105 106 107 108 109
  @staticmethod
  def perspective_blockdev_create(params):
    """Create a block device.

    """
110
    bdev_s, size, owner, on_primary, info = params
111
    bdev = objects.Disk.FromDict(bdev_s)
Iustin Pop's avatar
Iustin Pop committed
112 113
    if bdev is None:
      raise ValueError("can't unserialize data!")
114
    return backend.BlockdevCreate(bdev, size, owner, on_primary, info)
Iustin Pop's avatar
Iustin Pop committed
115

116 117 118 119 120
  @staticmethod
  def perspective_blockdev_remove(params):
    """Remove a block device.

    """
Iustin Pop's avatar
Iustin Pop committed
121
    bdev_s = params[0]
122
    bdev = objects.Disk.FromDict(bdev_s)
123
    return backend.BlockdevRemove(bdev)
Iustin Pop's avatar
Iustin Pop committed
124

Iustin Pop's avatar
Iustin Pop committed
125 126 127 128 129 130
  @staticmethod
  def perspective_blockdev_rename(params):
    """Remove a block device.

    """
    devlist = [(objects.Disk.FromDict(ds), uid) for ds, uid in params]
131
    return backend.BlockdevRename(devlist)
Iustin Pop's avatar
Iustin Pop committed
132

133 134 135 136 137
  @staticmethod
  def perspective_blockdev_assemble(params):
    """Assemble a block device.

    """
138
    bdev_s, owner, on_primary = params
139
    bdev = objects.Disk.FromDict(bdev_s)
Iustin Pop's avatar
Iustin Pop committed
140 141
    if bdev is None:
      raise ValueError("can't unserialize data!")
142
    return backend.BlockdevAssemble(bdev, owner, on_primary)
Iustin Pop's avatar
Iustin Pop committed
143

144 145 146 147 148
  @staticmethod
  def perspective_blockdev_shutdown(params):
    """Shutdown a block device.

    """
Iustin Pop's avatar
Iustin Pop committed
149
    bdev_s = params[0]
150
    bdev = objects.Disk.FromDict(bdev_s)
Iustin Pop's avatar
Iustin Pop committed
151 152
    if bdev is None:
      raise ValueError("can't unserialize data!")
153
    return backend.BlockdevShutdown(bdev)
Iustin Pop's avatar
Iustin Pop committed
154

155
  @staticmethod
156
  def perspective_blockdev_addchildren(params):
157 158 159 160 161 162
    """Add a child to a mirror device.

    Note: this is only valid for mirror devices. It's the caller's duty
    to send a correct disk, otherwise we raise an error.

    """
Iustin Pop's avatar
Iustin Pop committed
163
    bdev_s, ndev_s = params
164
    bdev = objects.Disk.FromDict(bdev_s)
165 166
    ndevs = [objects.Disk.FromDict(disk_s) for disk_s in ndev_s]
    if bdev is None or ndevs.count(None) > 0:
Iustin Pop's avatar
Iustin Pop committed
167
      raise ValueError("can't unserialize data!")
168
    return backend.BlockdevAddchildren(bdev, ndevs)
Iustin Pop's avatar
Iustin Pop committed
169

170
  @staticmethod
171
  def perspective_blockdev_removechildren(params):
172 173 174 175 176 177
    """Remove a child from a mirror device.

    This is only valid for mirror devices, of course. It's the callers
    duty to send a correct disk, otherwise we raise an error.

    """
Iustin Pop's avatar
Iustin Pop committed
178
    bdev_s, ndev_s = params
179
    bdev = objects.Disk.FromDict(bdev_s)
180 181
    ndevs = [objects.Disk.FromDict(disk_s) for disk_s in ndev_s]
    if bdev is None or ndevs.count(None) > 0:
Iustin Pop's avatar
Iustin Pop committed
182
      raise ValueError("can't unserialize data!")
183
    return backend.BlockdevRemovechildren(bdev, ndevs)
Iustin Pop's avatar
Iustin Pop committed
184

185 186 187 188 189
  @staticmethod
  def perspective_blockdev_getmirrorstatus(params):
    """Return the mirror status for a list of disks.

    """
190
    disks = [objects.Disk.FromDict(dsk_s)
Iustin Pop's avatar
Iustin Pop committed
191
            for dsk_s in params]
192
    return backend.BlockdevGetmirrorstatus(disks)
Iustin Pop's avatar
Iustin Pop committed
193

194 195 196 197 198 199 200
  @staticmethod
  def perspective_blockdev_find(params):
    """Expose the FindBlockDevice functionality for a disk.

    This will try to find but not activate a disk.

    """
201
    disk = objects.Disk.FromDict(params[0])
202
    return backend.BlockdevFind(disk)
Iustin Pop's avatar
Iustin Pop committed
203

204 205 206 207 208 209 210 211 212
  @staticmethod
  def perspective_blockdev_snapshot(params):
    """Create a snapshot device.

    Note that this is only valid for LVM disks, if we get passed
    something else we raise an exception. The snapshot device can be
    remove by calling the generic block device remove call.

    """
213
    cfbd = objects.Disk.FromDict(params[0])
214
    return backend.BlockdevSnapshot(cfbd)
Iustin Pop's avatar
Iustin Pop committed
215

216 217 218 219 220 221 222
  @staticmethod
  def perspective_blockdev_grow(params):
    """Grow a stack of devices.

    """
    cfbd = objects.Disk.FromDict(params[0])
    amount = params[1]
223
    return backend.BlockdevGrow(cfbd, amount)
224

225 226 227 228 229
  @staticmethod
  def perspective_blockdev_close(params):
    """Closes the given block devices.

    """
230
    disks = [objects.Disk.FromDict(cf) for cf in params[1]]
231
    return backend.BlockdevClose(params[0], disks)
232

233 234 235 236 237 238 239 240
  @staticmethod
  def perspective_blockdev_getsize(params):
    """Compute the sizes of the given block devices.

    """
    disks = [objects.Disk.FromDict(cf) for cf in params[0]]
    return backend.BlockdevGetsize(disks)

Iustin Pop's avatar
Iustin Pop committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
  # blockdev/drbd specific methods ----------

  @staticmethod
  def perspective_drbd_disconnect_net(params):
    """Disconnects the network connection of drbd disks.

    Note that this is only valid for drbd disks, so the members of the
    disk list must all be drbd devices.

    """
    nodes_ip, disks = params
    disks = [objects.Disk.FromDict(cf) for cf in disks]
    return backend.DrbdDisconnectNet(nodes_ip, disks)

  @staticmethod
  def perspective_drbd_attach_net(params):
    """Attaches the network connection of drbd disks.

    Note that this is only valid for drbd disks, so the members of the
    disk list must all be drbd devices.

    """
    nodes_ip, disks, instance_name, multimaster = params
    disks = [objects.Disk.FromDict(cf) for cf in disks]
265 266
    return backend.DrbdAttachNet(nodes_ip, disks,
                                     instance_name, multimaster)
Iustin Pop's avatar
Iustin Pop committed
267 268 269 270 271 272 273 274 275 276 277 278 279

  @staticmethod
  def perspective_drbd_wait_sync(params):
    """Wait until DRBD disks are synched.

    Note that this is only valid for drbd disks, so the members of the
    disk list must all be drbd devices.

    """
    nodes_ip, disks = params
    disks = [objects.Disk.FromDict(cf) for cf in disks]
    return backend.DrbdWaitSync(nodes_ip, disks)

Iustin Pop's avatar
Iustin Pop committed
280 281
  # export/import  --------------------------

282 283 284 285 286
  @staticmethod
  def perspective_snapshot_export(params):
    """Export a given snapshot.

    """
287
    disk = objects.Disk.FromDict(params[0])
Iustin Pop's avatar
Iustin Pop committed
288
    dest_node = params[1]
289
    instance = objects.Instance.FromDict(params[2])
290
    cluster_name = params[3]
291 292 293
    dev_idx = params[4]
    return backend.ExportSnapshot(disk, dest_node, instance,
                                  cluster_name, dev_idx)
294 295 296 297

  @staticmethod
  def perspective_finalize_export(params):
    """Expose the finalize export functionality.
Iustin Pop's avatar
Iustin Pop committed
298

299
    """
300 301
    instance = objects.Instance.FromDict(params[0])
    snap_disks = [objects.Disk.FromDict(str_data)
Iustin Pop's avatar
Iustin Pop committed
302 303 304
                  for str_data in params[1]]
    return backend.FinalizeExport(instance, snap_disks)

305 306 307 308 309 310 311 312 313 314
  @staticmethod
  def perspective_export_info(params):
    """Query information about an existing export on this node.

    The given path may not contain an export, in which case we return
    None.

    """
    path = params[0]
    einfo = backend.ExportInfo(path)
Iustin Pop's avatar
Iustin Pop committed
315 316 317 318
    if einfo is None:
      return einfo
    return einfo.Dumps()

319 320 321 322 323 324 325 326 327
  @staticmethod
  def perspective_export_list(params):
    """List the available exports on this node.

    Note that as opposed to export_info, which may query data about an
    export in any path, this only queries the standard Ganeti path
    (constants.EXPORT_DIR).

    """
Iustin Pop's avatar
Iustin Pop committed
328 329
    return backend.ListExports()

330 331 332 333 334
  @staticmethod
  def perspective_export_remove(params):
    """Remove an export.

    """
Iustin Pop's avatar
Iustin Pop committed
335 336 337 338 339
    export = params[0]
    return backend.RemoveExport(export)

  # volume  --------------------------

340 341 342 343 344
  @staticmethod
  def perspective_volume_list(params):
    """Query the list of logical volumes in a given volume group.

    """
Iustin Pop's avatar
Iustin Pop committed
345 346 347
    vgname = params[0]
    return backend.GetVolumeList(vgname)

348 349 350 351 352
  @staticmethod
  def perspective_vg_list(params):
    """Query the list of volume groups.

    """
Iustin Pop's avatar
Iustin Pop committed
353 354 355 356
    return backend.ListVolumeGroups()

  # bridge  --------------------------

357 358 359 360 361
  @staticmethod
  def perspective_bridges_exist(params):
    """Check if all bridges given exist on this node.

    """
Iustin Pop's avatar
Iustin Pop committed
362 363 364 365 366
    bridges_list = params[0]
    return backend.BridgesExist(bridges_list)

  # instance  --------------------------

367 368 369 370 371
  @staticmethod
  def perspective_instance_os_add(params):
    """Install an OS on a given instance.

    """
372
    inst_s = params[0]
373
    inst = objects.Instance.FromDict(inst_s)
374
    return backend.InstanceOsAdd(inst)
Iustin Pop's avatar
Iustin Pop committed
375

376 377 378 379 380
  @staticmethod
  def perspective_instance_run_rename(params):
    """Runs the OS rename script for an instance.

    """
381
    inst_s, old_name = params
382
    inst = objects.Instance.FromDict(inst_s)
383
    return backend.RunRenameInstance(inst, old_name)
384

385 386 387 388 389
  @staticmethod
  def perspective_instance_os_import(params):
    """Run the import function of an OS onto a given instance.

    """
390
    inst_s, src_node, src_images, cluster_name = params
391
    inst = objects.Instance.FromDict(inst_s)
392 393
    return backend.ImportOSIntoInstance(inst, src_node, src_images,
                                        cluster_name)
Iustin Pop's avatar
Iustin Pop committed
394

395 396 397 398 399
  @staticmethod
  def perspective_instance_shutdown(params):
    """Shutdown an instance.

    """
400
    instance = objects.Instance.FromDict(params[0])
401
    return backend.InstanceShutdown(instance)
Iustin Pop's avatar
Iustin Pop committed
402

403 404 405 406 407
  @staticmethod
  def perspective_instance_start(params):
    """Start an instance.

    """
408
    instance = objects.Instance.FromDict(params[0])
409
    return backend.StartInstance(instance)
Iustin Pop's avatar
Iustin Pop committed
410

411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
  @staticmethod
  def perspective_migration_info(params):
    """Gather information about an instance to be migrated.

    """
    instance = objects.Instance.FromDict(params[0])
    return backend.MigrationInfo(instance)

  @staticmethod
  def perspective_accept_instance(params):
    """Prepare the node to accept an instance.

    """
    instance, info, target = params
    instance = objects.Instance.FromDict(instance)
    return backend.AcceptInstance(instance, info, target)

  @staticmethod
  def perspective_finalize_migration(params):
    """Finalize the instance migration.

    """
    instance, info, success = params
    instance = objects.Instance.FromDict(instance)
    return backend.FinalizeMigration(instance, info, success)

437 438 439 440 441 442
  @staticmethod
  def perspective_instance_migrate(params):
    """Migrates an instance.

    """
    instance, target, live = params
443
    instance = objects.Instance.FromDict(instance)
444 445
    return backend.MigrateInstance(instance, target, live)

446 447 448 449 450 451 452
  @staticmethod
  def perspective_instance_reboot(params):
    """Reboot an instance.

    """
    instance = objects.Instance.FromDict(params[0])
    reboot_type = params[1]
453
    return backend.InstanceReboot(instance, reboot_type)
454

455 456 457 458 459
  @staticmethod
  def perspective_instance_info(params):
    """Query instance information.

    """
Iustin Pop's avatar
Iustin Pop committed
460
    return backend.GetInstanceInfo(params[0], params[1])
Iustin Pop's avatar
Iustin Pop committed
461

462 463 464 465 466 467 468 469
  @staticmethod
  def perspective_instance_migratable(params):
    """Query whether the specified instance can be migrated.

    """
    instance = objects.Instance.FromDict(params[0])
    return backend.GetInstanceMigratable(instance)

470 471 472 473 474
  @staticmethod
  def perspective_all_instances_info(params):
    """Query information about all instances.

    """
475
    return backend.GetAllInstancesInfo(params[0])
Iustin Pop's avatar
Iustin Pop committed
476

477 478 479 480 481
  @staticmethod
  def perspective_instance_list(params):
    """Query the list of running instances.

    """
482
    return backend.GetInstanceList(params[0])
Iustin Pop's avatar
Iustin Pop committed
483 484 485

  # node --------------------------

486 487 488 489 490
  @staticmethod
  def perspective_node_tcp_ping(params):
    """Do a TcpPing on the remote node.

    """
491 492
    return utils.TcpPing(params[1], params[2], timeout=params[3],
                         live_port_needed=params[4], source=params[0])
493

494 495 496 497 498 499 500
  @staticmethod
  def perspective_node_has_ip_address(params):
    """Checks if a node has the given ip address.

    """
    return utils.OwnIpAddress(params[0])

501 502 503 504 505
  @staticmethod
  def perspective_node_info(params):
    """Query node information.

    """
506 507
    vgname, hypervisor_type = params
    return backend.GetNodeInfo(vgname, hypervisor_type)
Iustin Pop's avatar
Iustin Pop committed
508

509 510 511 512 513
  @staticmethod
  def perspective_node_add(params):
    """Complete the registration of this node in the cluster.

    """
Iustin Pop's avatar
Iustin Pop committed
514 515 516
    return backend.AddNode(params[0], params[1], params[2],
                           params[3], params[4], params[5])

517 518 519 520 521
  @staticmethod
  def perspective_node_verify(params):
    """Run a verify sequence on this node.

    """
522
    return backend.VerifyNode(params[0], params[1])
Iustin Pop's avatar
Iustin Pop committed
523

524 525 526 527 528
  @staticmethod
  def perspective_node_start_master(params):
    """Promote this node to master status.

    """
529
    return backend.StartMaster(params[0], params[1])
Iustin Pop's avatar
Iustin Pop committed
530

531 532 533 534 535
  @staticmethod
  def perspective_node_stop_master(params):
    """Demote this node from master status.

    """
536
    return backend.StopMaster(params[0])
Iustin Pop's avatar
Iustin Pop committed
537

538 539 540 541 542
  @staticmethod
  def perspective_node_leave_cluster(params):
    """Cleanup after leaving a cluster.

    """
Iustin Pop's avatar
Iustin Pop committed
543 544
    return backend.LeaveCluster()

545 546 547 548 549
  @staticmethod
  def perspective_node_volumes(params):
    """Query the list of all logical volume groups.

    """
550 551
    return backend.NodeVolumes()

552 553 554 555 556 557 558 559
  @staticmethod
  def perspective_node_demote_from_mc(params):
    """Demote a node from the master candidate role.

    """
    return backend.DemoteFromMC()


Iustin Pop's avatar
Iustin Pop committed
560 561
  # cluster --------------------------

562 563 564 565 566
  @staticmethod
  def perspective_version(params):
    """Query version information.

    """
Iustin Pop's avatar
Iustin Pop committed
567 568
    return constants.PROTOCOL_VERSION

569 570 571 572 573 574 575 576
  @staticmethod
  def perspective_upload_file(params):
    """Upload a file.

    Note that the backend implementation imposes strict rules on which
    files are accepted.

    """
Iustin Pop's avatar
Iustin Pop committed
577 578
    return backend.UploadFile(*params)

579 580 581 582 583 584
  @staticmethod
  def perspective_master_info(params):
    """Query master information.

    """
    return backend.GetMasterInfo()
Iustin Pop's avatar
Iustin Pop committed
585

586 587 588 589 590
  @staticmethod
  def perspective_write_ssconf_files(params):
    """Write ssconf files.

    """
591 592
    (values,) = params
    return backend.WriteSsconfFiles(values)
593

Iustin Pop's avatar
Iustin Pop committed
594 595
  # os -----------------------

596 597 598 599 600
  @staticmethod
  def perspective_os_diagnose(params):
    """Query detailed information about existing OSes.

    """
601
    return [os_obj.ToDict() for os_obj in backend.DiagnoseOS()]
Iustin Pop's avatar
Iustin Pop committed
602

603 604 605 606 607
  @staticmethod
  def perspective_os_get(params):
    """Query information about a given OS.

    """
Iustin Pop's avatar
Iustin Pop committed
608 609
    name = params[0]
    try:
610
      os_obj = backend.OSFromDisk(name)
Iustin Pop's avatar
Iustin Pop committed
611
    except errors.InvalidOS, err:
612 613
      os_obj = objects.OS.FromInvalidOS(err)
    return os_obj.ToDict()
Iustin Pop's avatar
Iustin Pop committed
614 615 616

  # hooks -----------------------

617 618 619 620 621
  @staticmethod
  def perspective_hooks_runner(params):
    """Run hook scripts.

    """
Iustin Pop's avatar
Iustin Pop committed
622 623 624 625
    hpath, phase, env = params
    hr = backend.HooksRunner()
    return hr.RunHooks(hpath, phase, env)

626 627 628 629 630 631 632 633 634 635 636
  # iallocator -----------------

  @staticmethod
  def perspective_iallocator_runner(params):
    """Run an iallocator script.

    """
    name, idata = params
    iar = backend.IAllocatorRunner()
    return iar.Run(name, idata)

637 638 639 640 641 642 643 644 645 646
  # test -----------------------

  @staticmethod
  def perspective_test_delay(params):
    """Run test delay.

    """
    duration = params[0]
    return utils.TestDelay(duration)

647 648
  # file storage ---------------

649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
  @staticmethod
  def perspective_file_storage_dir_create(params):
    """Create the file storage directory.

    """
    file_storage_dir = params[0]
    return backend.CreateFileStorageDir(file_storage_dir)

  @staticmethod
  def perspective_file_storage_dir_remove(params):
    """Remove the file storage directory.

    """
    file_storage_dir = params[0]
    return backend.RemoveFileStorageDir(file_storage_dir)

  @staticmethod
  def perspective_file_storage_dir_rename(params):
    """Rename the file storage directory.

    """
    old_file_storage_dir = params[0]
    new_file_storage_dir = params[1]
    return backend.RenameFileStorageDir(old_file_storage_dir,
                                        new_file_storage_dir)

675 676
  # jobs ------------------------

677
  @staticmethod
678
  @_RequireJobQueueLock
679 680 681 682 683
  def perspective_jobqueue_update(params):
    """Update job queue.

    """
    (file_name, content) = params
684
    return backend.JobQueueUpdate(file_name, content)
685 686

  @staticmethod
687
  @_RequireJobQueueLock
688 689 690 691 692 693
  def perspective_jobqueue_purge(params):
    """Purge job queue.

    """
    return backend.JobQueuePurge()

694 695 696 697 698 699
  @staticmethod
  @_RequireJobQueueLock
  def perspective_jobqueue_rename(params):
    """Rename a job queue file.

    """
700 701
    # TODO: What if a file fails to rename?
    return [backend.JobQueueRename(old, new) for old, new in params]
702

703 704 705 706 707 708 709 710 711
  @staticmethod
  def perspective_jobqueue_set_drain(params):
    """Set/unset the queue drain flag.

    """
    drain_flag = params[0]
    return backend.JobQueueSetDrainFlag(drain_flag)


712 713 714 715 716 717 718 719 720 721
  # hypervisor ---------------

  @staticmethod
  def perspective_hypervisor_validate_params(params):
    """Validate the hypervisor parameters.

    """
    (hvname, hvparams) = params
    return backend.ValidateHVParams(hvname, hvparams)

Iustin Pop's avatar
Iustin Pop committed
722 723 724 725

def ParseOptions():
  """Parse the command line options.

Iustin Pop's avatar
Iustin Pop committed
726
  @return: (options, args) as from OptionParser.parse_args()
Iustin Pop's avatar
Iustin Pop committed
727 728 729

  """
  parser = OptionParser(description="Ganeti node daemon",
730
                        usage="%prog [-f] [-d] [-b ADDRESS]",
Iustin Pop's avatar
Iustin Pop committed
731 732 733 734 735 736 737 738 739
                        version="%%prog (ganeti) %s" %
                        constants.RELEASE_VERSION)

  parser.add_option("-f", "--foreground", dest="fork",
                    help="Don't detach from the current terminal",
                    default=True, action="store_false")
  parser.add_option("-d", "--debug", dest="debug",
                    help="Enable some debug messages",
                    default=False, action="store_true")
740 741 742 743
  parser.add_option("-b", "--bind", dest="bind_address",
                    help="Bind address",
                    default="", metavar="ADDRESS")

Iustin Pop's avatar
Iustin Pop committed
744 745 746 747 748
  options, args = parser.parse_args()
  return options, args


def main():
749 750 751
  """Main function for the node daemon.

  """
752 753
  global queue_lock

Iustin Pop's avatar
Iustin Pop committed
754
  options, args = ParseOptions()
Iustin Pop's avatar
Iustin Pop committed
755 756 757 758

  if options.fork:
    utils.CloseFDs()

Iustin Pop's avatar
Iustin Pop committed
759 760 761
  for fname in (constants.SSL_CERT_FILE,):
    if not os.path.isfile(fname):
      print "config %s not there, will not run." % fname
762
      sys.exit(constants.EXIT_NOTCLUSTER)
Iustin Pop's avatar
Iustin Pop committed
763

764
  port = utils.GetNodeDaemonPort()
Iustin Pop's avatar
Iustin Pop committed
765

766 767 768 769
  dirs = [(val, constants.RUN_DIRS_MODE) for val in constants.SUB_RUN_DIRS]
  dirs.append((constants.LOG_OS_DIR, 0750))
  dirs.append((constants.LOCK_DIR, 1777))
  utils.EnsureDirs(dirs)
770

Iustin Pop's avatar
Iustin Pop committed
771 772
  # become a daemon
  if options.fork:
773
    utils.Daemonize(logfile=constants.LOG_NODESERVER)
Iustin Pop's avatar
Iustin Pop committed
774

775
  utils.WritePidFile(constants.NODED_PID)
776
  try:
Iustin Pop's avatar
Iustin Pop committed
777 778
    utils.SetupLogging(logfile=constants.LOG_NODESERVER, debug=options.debug,
                       stderr_logging=not options.fork)
779
    logging.info("ganeti node daemon startup")
780

781 782 783 784
    # Read SSL certificate
    ssl_params = http.HttpSslParams(ssl_key_path=constants.SSL_CERT_FILE,
                                    ssl_cert_path=constants.SSL_CERT_FILE)

785 786
    # Prepare job queue
    queue_lock = jstore.InitAndVerifyQueue(must_lock=False)
Iustin Pop's avatar
Iustin Pop committed
787

788
    mainloop = daemon.Mainloop()
789
    server = NodeHttpServer(mainloop, options.bind_address, port,
790
                            ssl_params=ssl_params, ssl_verify_peer=True)
791 792 793 794 795
    server.Start()
    try:
      mainloop.Run()
    finally:
      server.Stop()
796
  finally:
797
    utils.RemovePidFile(constants.NODED_PID)
798

Iustin Pop's avatar
Iustin Pop committed
799

800
if __name__ == '__main__':
Iustin Pop's avatar
Iustin Pop committed
801
  main()