ganeti-noded 21.2 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
29
import os
import sys
import traceback
Guido Trotter's avatar
Guido Trotter committed
30
import SocketServer
31
import errno
Iustin Pop's avatar
Iustin Pop committed
32
import logging
33
import signal
Iustin Pop's avatar
Iustin Pop committed
34
35
36
37
38
39
40

from optparse import OptionParser

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

46
47
import ganeti.http.server

Iustin Pop's avatar
Iustin Pop committed
48

49
50
51
queue_lock = None


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

  """
56
57
  QUEUE_LOCK_TIMEOUT = 10

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

67
68
69
  return wrapper


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

  This class holds all methods exposed over the RPC interface.

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

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

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

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

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

Iustin Pop's avatar
Iustin Pop committed
95
    try:
96
      rvalue = method(req.request_body)
97
      return True, rvalue
98

99
100
101
102
103
    except backend.RPCFail, err:
      # our custom failure exception; str(err) works fine if the
      # exception was constructed with a single argument, and in
      # this case, err.message == err.args[0] == str(err)
      return (False, str(err))
104
    except errors.QuitGanetiException, err:
105
      # Tell parent to quit
106
107
      logging.info("Shutting down the node daemon, arguments: %s",
                   str(err.args))
108
      os.kill(self.noded_pid, signal.SIGTERM)
109
110
111
      # And return the error's arguments, which must be already in
      # correct tuple format
      return err.args
112
    except Exception, err:
113
      logging.exception("Error in RPC call")
114
      return False, "Error while executing backend function: %s" % str(err)
Iustin Pop's avatar
Iustin Pop committed
115
116
117

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

118
119
120
121
122
  @staticmethod
  def perspective_blockdev_create(params):
    """Create a block device.

    """
123
    bdev_s, size, owner, on_primary, info = params
124
    bdev = objects.Disk.FromDict(bdev_s)
Iustin Pop's avatar
Iustin Pop committed
125
126
    if bdev is None:
      raise ValueError("can't unserialize data!")
127
    return backend.BlockdevCreate(bdev, size, owner, on_primary, info)
Iustin Pop's avatar
Iustin Pop committed
128

129
130
131
132
133
  @staticmethod
  def perspective_blockdev_remove(params):
    """Remove a block device.

    """
Iustin Pop's avatar
Iustin Pop committed
134
    bdev_s = params[0]
135
    bdev = objects.Disk.FromDict(bdev_s)
136
    return backend.BlockdevRemove(bdev)
Iustin Pop's avatar
Iustin Pop committed
137

Iustin Pop's avatar
Iustin Pop committed
138
139
140
141
142
143
  @staticmethod
  def perspective_blockdev_rename(params):
    """Remove a block device.

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

146
147
148
149
150
  @staticmethod
  def perspective_blockdev_assemble(params):
    """Assemble a block device.

    """
151
    bdev_s, owner, on_primary = params
152
    bdev = objects.Disk.FromDict(bdev_s)
Iustin Pop's avatar
Iustin Pop committed
153
154
    if bdev is None:
      raise ValueError("can't unserialize data!")
155
    return backend.BlockdevAssemble(bdev, owner, on_primary)
Iustin Pop's avatar
Iustin Pop committed
156

157
158
159
160
161
  @staticmethod
  def perspective_blockdev_shutdown(params):
    """Shutdown a block device.

    """
Iustin Pop's avatar
Iustin Pop committed
162
    bdev_s = params[0]
163
    bdev = objects.Disk.FromDict(bdev_s)
Iustin Pop's avatar
Iustin Pop committed
164
165
    if bdev is None:
      raise ValueError("can't unserialize data!")
166
    return backend.BlockdevShutdown(bdev)
Iustin Pop's avatar
Iustin Pop committed
167

168
  @staticmethod
169
  def perspective_blockdev_addchildren(params):
170
171
172
173
174
175
    """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
176
    bdev_s, ndev_s = params
177
    bdev = objects.Disk.FromDict(bdev_s)
178
179
    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
180
      raise ValueError("can't unserialize data!")
181
    return backend.BlockdevAddchildren(bdev, ndevs)
Iustin Pop's avatar
Iustin Pop committed
182

183
  @staticmethod
184
  def perspective_blockdev_removechildren(params):
185
186
187
188
189
190
    """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
191
    bdev_s, ndev_s = params
192
    bdev = objects.Disk.FromDict(bdev_s)
193
194
    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
195
      raise ValueError("can't unserialize data!")
196
    return backend.BlockdevRemovechildren(bdev, ndevs)
Iustin Pop's avatar
Iustin Pop committed
197

198
199
200
201
202
  @staticmethod
  def perspective_blockdev_getmirrorstatus(params):
    """Return the mirror status for a list of disks.

    """
203
    disks = [objects.Disk.FromDict(dsk_s)
Iustin Pop's avatar
Iustin Pop committed
204
            for dsk_s in params]
205
    return backend.BlockdevGetmirrorstatus(disks)
Iustin Pop's avatar
Iustin Pop committed
206

207
208
209
210
211
212
213
  @staticmethod
  def perspective_blockdev_find(params):
    """Expose the FindBlockDevice functionality for a disk.

    This will try to find but not activate a disk.

    """
214
    disk = objects.Disk.FromDict(params[0])
215
    return backend.BlockdevFind(disk)
Iustin Pop's avatar
Iustin Pop committed
216

217
218
219
220
221
222
223
224
225
  @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.

    """
226
    cfbd = objects.Disk.FromDict(params[0])
227
    return backend.BlockdevSnapshot(cfbd)
Iustin Pop's avatar
Iustin Pop committed
228

229
230
231
232
233
234
235
  @staticmethod
  def perspective_blockdev_grow(params):
    """Grow a stack of devices.

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

238
239
240
241
242
  @staticmethod
  def perspective_blockdev_close(params):
    """Closes the given block devices.

    """
243
    disks = [objects.Disk.FromDict(cf) for cf in params[1]]
244
    return backend.BlockdevClose(params[0], disks)
245

Iustin Pop's avatar
Iustin Pop committed
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
  # 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]
270
271
    return backend.DrbdAttachNet(nodes_ip, disks,
                                     instance_name, multimaster)
Iustin Pop's avatar
Iustin Pop committed
272
273
274
275
276
277
278
279
280
281
282
283
284

  @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
285
286
  # export/import  --------------------------

287
288
289
290
291
  @staticmethod
  def perspective_snapshot_export(params):
    """Export a given snapshot.

    """
292
    disk = objects.Disk.FromDict(params[0])
Iustin Pop's avatar
Iustin Pop committed
293
    dest_node = params[1]
294
    instance = objects.Instance.FromDict(params[2])
295
    cluster_name = params[3]
296
297
298
    dev_idx = params[4]
    return backend.ExportSnapshot(disk, dest_node, instance,
                                  cluster_name, dev_idx)
299
300
301
302

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

304
    """
305
306
    instance = objects.Instance.FromDict(params[0])
    snap_disks = [objects.Disk.FromDict(str_data)
Iustin Pop's avatar
Iustin Pop committed
307
308
309
                  for str_data in params[1]]
    return backend.FinalizeExport(instance, snap_disks)

310
311
312
313
314
315
316
317
318
  @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]
319
    return backend.ExportInfo(path)
Iustin Pop's avatar
Iustin Pop committed
320

321
322
323
324
325
326
327
328
329
  @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
330
331
    return backend.ListExports()

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

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

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

342
  @staticmethod
343
  def perspective_lv_list(params):
344
345
346
    """Query the list of logical volumes in a given volume group.

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

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

    """
Iustin Pop's avatar
Iustin Pop committed
355
356
357
358
    return backend.ListVolumeGroups()

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

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

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

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

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

    """
374
    inst_s = params[0]
375
    inst = objects.Instance.FromDict(inst_s)
376
377
    reinstall = params[1]
    return backend.InstanceOsAdd(inst, reinstall)
Iustin Pop's avatar
Iustin Pop committed
378

379
380
381
382
383
  @staticmethod
  def perspective_instance_run_rename(params):
    """Runs the OS rename script for an instance.

    """
384
    inst_s, old_name = params
385
    inst = objects.Instance.FromDict(inst_s)
386
    return backend.RunRenameInstance(inst, old_name)
387

388
389
390
391
392
  @staticmethod
  def perspective_instance_os_import(params):
    """Run the import function of an OS onto a given instance.

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

398
399
400
401
402
  @staticmethod
  def perspective_instance_shutdown(params):
    """Shutdown an instance.

    """
403
    instance = objects.Instance.FromDict(params[0])
404
    return backend.InstanceShutdown(instance)
Iustin Pop's avatar
Iustin Pop committed
405

406
407
408
409
410
  @staticmethod
  def perspective_instance_start(params):
    """Start an instance.

    """
411
    instance = objects.Instance.FromDict(params[0])
412
    return backend.StartInstance(instance)
Iustin Pop's avatar
Iustin Pop committed
413

414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
  @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)

440
441
442
443
444
445
  @staticmethod
  def perspective_instance_migrate(params):
    """Migrates an instance.

    """
    instance, target, live = params
446
    instance = objects.Instance.FromDict(instance)
447
448
    return backend.MigrateInstance(instance, target, live)

449
450
451
452
453
454
455
  @staticmethod
  def perspective_instance_reboot(params):
    """Reboot an instance.

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

458
459
460
461
462
  @staticmethod
  def perspective_instance_info(params):
    """Query instance information.

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

465
466
467
468
469
470
471
472
  @staticmethod
  def perspective_instance_migratable(params):
    """Query whether the specified instance can be migrated.

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

473
474
475
476
477
  @staticmethod
  def perspective_all_instances_info(params):
    """Query information about all instances.

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

480
481
482
483
484
  @staticmethod
  def perspective_instance_list(params):
    """Query the list of running instances.

    """
485
    return backend.GetInstanceList(params[0])
Iustin Pop's avatar
Iustin Pop committed
486
487
488

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

489
490
491
492
493
  @staticmethod
  def perspective_node_tcp_ping(params):
    """Do a TcpPing on the remote node.

    """
494
495
    return utils.TcpPing(params[1], params[2], timeout=params[3],
                         live_port_needed=params[4], source=params[0])
496

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

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

504
505
506
507
508
  @staticmethod
  def perspective_node_info(params):
    """Query node information.

    """
509
510
    vgname, hypervisor_type = params
    return backend.GetNodeInfo(vgname, hypervisor_type)
Iustin Pop's avatar
Iustin Pop committed
511

512
513
514
515
516
  @staticmethod
  def perspective_node_add(params):
    """Complete the registration of this node in the cluster.

    """
Iustin Pop's avatar
Iustin Pop committed
517
518
519
    return backend.AddNode(params[0], params[1], params[2],
                           params[3], params[4], params[5])

520
521
522
523
524
  @staticmethod
  def perspective_node_verify(params):
    """Run a verify sequence on this node.

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

527
528
529
530
531
  @staticmethod
  def perspective_node_start_master(params):
    """Promote this node to master status.

    """
532
    return backend.StartMaster(params[0])
Iustin Pop's avatar
Iustin Pop committed
533

534
535
536
537
538
  @staticmethod
  def perspective_node_stop_master(params):
    """Demote this node from master status.

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

541
542
543
544
545
  @staticmethod
  def perspective_node_leave_cluster(params):
    """Cleanup after leaving a cluster.

    """
Iustin Pop's avatar
Iustin Pop committed
546
547
    return backend.LeaveCluster()

548
549
550
551
552
  @staticmethod
  def perspective_node_volumes(params):
    """Query the list of all logical volume groups.

    """
553
554
    return backend.NodeVolumes()

555
556
557
558
559
560
561
562
  @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
563
564
565
566
567
568
569
570
571
  @staticmethod
  def perspective_node_powercycle(params):
    """Tries to powercycle the nod.

    """
    hypervisor_type = params[0]
    return backend.PowercycleNode(hypervisor_type)


Iustin Pop's avatar
Iustin Pop committed
572
573
  # cluster --------------------------

574
575
576
577
578
  @staticmethod
  def perspective_version(params):
    """Query version information.

    """
579
    return constants.PROTOCOL_VERSION
Iustin Pop's avatar
Iustin Pop committed
580

581
582
583
584
585
586
587
588
  @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
589
590
    return backend.UploadFile(*params)

591
592
593
594
595
596
  @staticmethod
  def perspective_master_info(params):
    """Query master information.

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

598
599
600
601
602
  @staticmethod
  def perspective_write_ssconf_files(params):
    """Write ssconf files.

    """
603
604
    (values,) = params
    return backend.WriteSsconfFiles(values)
605

Iustin Pop's avatar
Iustin Pop committed
606
607
  # os -----------------------

608
609
610
611
612
  @staticmethod
  def perspective_os_diagnose(params):
    """Query detailed information about existing OSes.

    """
613
    return backend.DiagnoseOS()
Iustin Pop's avatar
Iustin Pop committed
614

615
616
617
618
619
  @staticmethod
  def perspective_os_get(params):
    """Query information about a given OS.

    """
Iustin Pop's avatar
Iustin Pop committed
620
    name = params[0]
621
    os_obj = backend.OSFromDisk(name)
622
    return os_obj.ToDict()
Iustin Pop's avatar
Iustin Pop committed
623
624
625

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

626
627
628
629
630
  @staticmethod
  def perspective_hooks_runner(params):
    """Run hook scripts.

    """
Iustin Pop's avatar
Iustin Pop committed
631
632
633
634
    hpath, phase, env = params
    hr = backend.HooksRunner()
    return hr.RunHooks(hpath, phase, env)

635
636
637
638
639
640
641
642
643
644
645
  # iallocator -----------------

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

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

646
647
648
649
650
651
652
653
  # test -----------------------

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

    """
    duration = params[0]
654
655
656
657
    status, rval = utils.TestDelay(duration)
    if not status:
      raise backend.RPCFail(rval)
    return rval
658

659
660
  # file storage ---------------

661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
  @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)

687
688
  # jobs ------------------------

689
  @staticmethod
690
  @_RequireJobQueueLock
691
692
693
694
695
  def perspective_jobqueue_update(params):
    """Update job queue.

    """
    (file_name, content) = params
696
    return backend.JobQueueUpdate(file_name, content)
697
698

  @staticmethod
699
  @_RequireJobQueueLock
700
701
702
703
704
705
  def perspective_jobqueue_purge(params):
    """Purge job queue.

    """
    return backend.JobQueuePurge()

706
707
708
709
710
711
  @staticmethod
  @_RequireJobQueueLock
  def perspective_jobqueue_rename(params):
    """Rename a job queue file.

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

715
716
717
718
719
720
721
722
723
  @staticmethod
  def perspective_jobqueue_set_drain(params):
    """Set/unset the queue drain flag.

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


724
725
726
727
728
729
730
731
732
733
  # 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
734
735
736
737

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

Iustin Pop's avatar
Iustin Pop committed
738
  @return: (options, args) as from OptionParser.parse_args()
Iustin Pop's avatar
Iustin Pop committed
739
740
741

  """
  parser = OptionParser(description="Ganeti node daemon",
742
                        usage="%prog [-f] [-d] [-b ADDRESS]",
Iustin Pop's avatar
Iustin Pop committed
743
744
745
746
747
748
749
750
751
                        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")
752
753
754
755
  parser.add_option("-b", "--bind", dest="bind_address",
                    help="Bind address",
                    default="", metavar="ADDRESS")

Iustin Pop's avatar
Iustin Pop committed
756
757
758
759
760
  options, args = parser.parse_args()
  return options, args


def main():
761
762
763
  """Main function for the node daemon.

  """
764
765
  global queue_lock

Iustin Pop's avatar
Iustin Pop committed
766
  options, args = ParseOptions()
767
  utils.debug = options.debug
Iustin Pop's avatar
Iustin Pop committed
768
769
770
771

  if options.fork:
    utils.CloseFDs()

Iustin Pop's avatar
Iustin Pop committed
772
773
774
775
776
777
  for fname in (constants.SSL_CERT_FILE,):
    if not os.path.isfile(fname):
      print "config %s not there, will not run." % fname
      sys.exit(5)

  try:
Michael Hanselmann's avatar
Michael Hanselmann committed
778
    port = utils.GetNodeDaemonPort()
Iustin Pop's avatar
Iustin Pop committed
779
780
781
782
  except errors.ConfigurationError, err:
    print "Cluster configuration incomplete: '%s'" % str(err)
    sys.exit(5)

783
784
785
786
  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)
787

Iustin Pop's avatar
Iustin Pop committed
788
789
  # become a daemon
  if options.fork:
790
    utils.Daemonize(logfile=constants.LOG_NODESERVER)
Iustin Pop's avatar
Iustin Pop committed
791

792
  utils.WritePidFile(constants.NODED_PID)
793
  try:
Iustin Pop's avatar
Iustin Pop committed
794
795
    utils.SetupLogging(logfile=constants.LOG_NODESERVER, debug=options.debug,
                       stderr_logging=not options.fork)
796
    logging.info("ganeti node daemon startup")
797

798
799
800
801
    # Read SSL certificate
    ssl_params = http.HttpSslParams(ssl_key_path=constants.SSL_CERT_FILE,
                                    ssl_cert_path=constants.SSL_CERT_FILE)

802
803
    # Prepare job queue
    queue_lock = jstore.InitAndVerifyQueue(must_lock=False)
Iustin Pop's avatar
Iustin Pop committed
804

805
    mainloop = daemon.Mainloop()
806
    server = NodeHttpServer(mainloop, options.bind_address, port,
807
                            ssl_params=ssl_params, ssl_verify_peer=True)
808
809
810
811
812
    server.Start()
    try:
      mainloop.Run()
    finally:
      server.Stop()
813
  finally:
814
    utils.RemovePidFile(constants.NODED_PID)
815

Iustin Pop's avatar
Iustin Pop committed
816

817
if __name__ == '__main__':
Iustin Pop's avatar
Iustin Pop committed
818
  main()