Revision 880478f8
b/daemons/ganeti-master | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2006, 2007 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 |
"""Ganeti master script |
|
23 |
|
|
24 |
Exit codes, for both start and stop: |
|
25 |
- 0: master setup successful |
|
26 |
- 1: some generic error (this exit code can also be thrown by exceptions) |
|
27 |
- 11: node is not master, nothing to do |
|
28 |
- 12: node setup incomplete, cannot start |
|
29 |
- 13: node should be master, but someone has the ip address already |
|
30 |
|
|
31 |
Only exit codes 0 and 11 represent an ok state. Code 1 was left for |
|
32 |
generic errors as other python code can cause exit with code 1. |
|
33 |
|
|
34 |
""" |
|
35 |
|
|
36 |
import os |
|
37 |
import sys |
|
38 |
import socket |
|
39 |
|
|
40 |
from optparse import OptionParser |
|
41 |
|
|
42 |
from ganeti import logger |
|
43 |
from ganeti import constants |
|
44 |
from ganeti import errors |
|
45 |
from ganeti import ssconf |
|
46 |
from ganeti import utils |
|
47 |
|
|
48 |
EXIT_OK = 0 |
|
49 |
EXIT_SOME_ERROR = 1 |
|
50 |
EXIT_NOTMASTER = 11 |
|
51 |
EXIT_NODESETUP_ERROR = 12 |
|
52 |
EXIT_DUPLICATE_IP = 13 |
|
53 |
EXIT_ARGS_ERROR = 14 |
|
54 |
|
|
55 |
|
|
56 |
def ParseOptions(): |
|
57 |
"""Parse the command line options. |
|
58 |
|
|
59 |
Returns: |
|
60 |
(options, args) as from OptionParser.parse_args() |
|
61 |
|
|
62 |
""" |
|
63 |
parser = OptionParser(description="Ganeti master", |
|
64 |
usage="%prog [-d]", |
|
65 |
version="%%prog (ganeti) %s" % |
|
66 |
constants.RELEASE_VERSION) |
|
67 |
|
|
68 |
parser.add_option("-d", "--debug", dest="debug", |
|
69 |
help="Enable some debug messages", |
|
70 |
default=False, action="store_true") |
|
71 |
options, args = parser.parse_args() |
|
72 |
|
|
73 |
if len(args) != 1 or args[0] not in ("start", "stop"): |
|
74 |
sys.stderr.write("Usage: %s [-d] start|stop\n" % sys.argv[0]) |
|
75 |
sys.exit(EXIT_ARGS_ERROR) |
|
76 |
|
|
77 |
return options, args |
|
78 |
|
|
79 |
|
|
80 |
def CheckNodeSetup(debug): |
|
81 |
"""Checks the node setup. |
|
82 |
|
|
83 |
If the node setup if ok, this function will return the tuple |
|
84 |
(master_hostname, master_netdev, master_ip). Otherwise the return |
|
85 |
value will be None. |
|
86 |
|
|
87 |
""" |
|
88 |
for fname in (constants.SSL_CERT_FILE,): |
|
89 |
if not os.path.isfile(fname): |
|
90 |
if debug: |
|
91 |
sys.stderr.write("Missing config file %s.\n" % fname) |
|
92 |
return None |
|
93 |
try: |
|
94 |
ss = ssconf.SimpleStore() |
|
95 |
port = ss.GetNodeDaemonPort() |
|
96 |
pwdata = ss.GetNodeDaemonPassword() |
|
97 |
master_name = ss.GetMasterNode() |
|
98 |
master_netdev = ss.GetMasterNetdev() |
|
99 |
master_ip = ss.GetMasterIP() |
|
100 |
except errors.ConfigurationError, err: |
|
101 |
if debug: |
|
102 |
sys.stderr.write("Cluster configuration incomplete: '%s'\n" % str(err)) |
|
103 |
return None |
|
104 |
return (master_name, master_netdev, master_ip) |
|
105 |
|
|
106 |
|
|
107 |
def StartMaster(master_netdev, master_ip, debug): |
|
108 |
"""Starts the master. |
|
109 |
|
|
110 |
""" |
|
111 |
result = utils.RunCmd(["fping", "-q", master_ip]) |
|
112 |
if not result.failed: |
|
113 |
r2 = utils.RunCmd(["fping", "-q", "-S127.0.0.1", master_ip]) |
|
114 |
if not result.failed: |
|
115 |
# we already have the ip: |
|
116 |
if debug: |
|
117 |
sys.stderr.write("Notice: already started.\n") |
|
118 |
return EXIT_OK |
|
119 |
else: |
|
120 |
return EXIT_DUPLICATE_IP |
|
121 |
result = utils.RunCmd(["ip", "address", "add", "%s/32" % master_ip, |
|
122 |
"dev", master_netdev, "label", |
|
123 |
"%s:0" % master_netdev]) |
|
124 |
if result.failed: |
|
125 |
if debug: |
|
126 |
sys.stderr.write("Can't activate master IP: %s\n" % result.output) |
|
127 |
return EXIT_SOME_ERROR |
|
128 |
|
|
129 |
result = utils.RunCmd(["arping", "-q", "-U", "-c 3", "-I", master_netdev, |
|
130 |
"-s", master_ip, master_ip]) |
|
131 |
# we'll ignore the exit code of arping |
|
132 |
return EXIT_OK |
|
133 |
|
|
134 |
|
|
135 |
def StopMaster(master_netdev, master_ip, debug): |
|
136 |
"""Stops the master. |
|
137 |
|
|
138 |
""" |
|
139 |
result = utils.RunCmd(["ip", "address", "del", "%s/32" % master_ip, |
|
140 |
"dev", master_netdev]) |
|
141 |
if result.failed: |
|
142 |
if debug: |
|
143 |
sys.stderr.write("Can't remove the master IP, error: %s" % result.output) |
|
144 |
# but otherwise ignore the failure |
|
145 |
return EXIT_OK |
|
146 |
|
|
147 |
|
|
148 |
def main(): |
|
149 |
"""Main function. |
|
150 |
|
|
151 |
""" |
|
152 |
options, args = ParseOptions() |
|
153 |
debug = options.debug |
|
154 |
result = CheckNodeSetup(debug) |
|
155 |
if not result: |
|
156 |
if debug: |
|
157 |
sys.stderr.write("Node configuration incomplete.\n") |
|
158 |
return EXIT_NODESETUP_ERROR |
|
159 |
|
|
160 |
master_node, master_netdev, master_ip = result |
|
161 |
if socket.gethostname() != master_node and args[0] == "start": |
|
162 |
if debug: |
|
163 |
sys.stderr.write("Not master, ignoring request.\n") |
|
164 |
return EXIT_NOTMASTER |
|
165 |
|
|
166 |
if args[0] == "start": |
|
167 |
fn = StartMaster |
|
168 |
else: |
|
169 |
fn = StopMaster |
|
170 |
|
|
171 |
return fn(master_netdev, master_ip, debug) |
|
172 |
|
|
173 |
|
|
174 |
if __name__=='__main__': |
|
175 |
exit_code = main() |
|
176 |
sys.exit(exit_code) |
b/docs/examples/ganeti.initd | ||
---|---|---|
2 | 2 |
# ganeti node daemon starter script |
3 | 3 |
# based on skeleton from Debian GNU/Linux |
4 | 4 |
|
5 |
PATH=/sbin:/bin:/usr/sbin:/usr/bin |
|
5 |
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
|
6 | 6 |
DAEMON=/usr/local/sbin/ganeti-noded |
7 | 7 |
NAME=ganeti-noded |
8 | 8 |
SCRIPTNAME=/etc/init.d/ganeti |
b/lib/backend.py | ||
---|---|---|
40 | 40 |
from ganeti import constants |
41 | 41 |
from ganeti import bdev |
42 | 42 |
from ganeti import objects |
43 |
from ganeti import ssconf |
|
43 | 44 |
|
44 | 45 |
|
45 | 46 |
def ListConfigFiles(): |
... | ... | |
63 | 64 |
"""Activate local node as master node. |
64 | 65 |
|
65 | 66 |
There are two needed steps for this: |
66 |
- register the master init script, and also run it now
|
|
67 |
- run the master script
|
|
67 | 68 |
- register the cron script |
68 | 69 |
|
69 | 70 |
""" |
70 |
result = utils.RunCmd(["update-rc.d", constants.MASTER_INITD_NAME, |
|
71 |
"defaults", "21", "79"]) |
|
72 |
|
|
73 |
if result.failed: |
|
74 |
logger.Error("could not register the master init.d script with command" |
|
75 |
" %s, error %s" % (result.cmd, result.output)) |
|
76 |
return False |
|
77 |
|
|
78 |
result = utils.RunCmd([constants.MASTER_INITD_SCRIPT, "start"]) |
|
71 |
result = utils.RunCmd([constants.MASTER_SCRIPT, "-d", "start"]) |
|
79 | 72 |
|
80 | 73 |
if result.failed: |
81 | 74 |
logger.Error("could not activate cluster interface with command %s," |
82 |
" error %s" % (result.cmd, result.output))
|
|
75 |
" error: '%s'" % (result.cmd, result.output))
|
|
83 | 76 |
return False |
84 | 77 |
|
85 | 78 |
utils.RemoveFile(constants.MASTER_CRON_LINK) |
... | ... | |
91 | 84 |
"""Deactivate this node as master. |
92 | 85 |
|
93 | 86 |
This does two things: |
94 |
- remove links to master's startup script
|
|
87 |
- run the master stop script
|
|
95 | 88 |
- remove link to master cron script. |
96 | 89 |
|
97 | 90 |
""" |
98 |
result = utils.RunCmd(["update-rc.d", "-f", |
|
99 |
constants.MASTER_INITD_NAME, "remove"]) |
|
100 |
if result.failed: |
|
101 |
logger.Error("could not unregister the master script with command" |
|
102 |
" %s, error %s" % (result.cmd, result.output)) |
|
103 |
return False |
|
104 |
|
|
105 |
output = utils.RunCmd([constants.MASTER_INITD_SCRIPT, "stop"]) |
|
91 |
result = utils.RunCmd([constants.MASTER_SCRIPT, "-d", "stop"]) |
|
106 | 92 |
|
107 | 93 |
if result.failed: |
108 | 94 |
logger.Error("could not deactivate cluster interface with command %s," |
109 |
" error %s" % (result.cmd, result.output))
|
|
95 |
" error: '%s'" % (result.cmd, result.output))
|
|
110 | 96 |
return False |
111 | 97 |
|
112 | 98 |
utils.RemoveFile(constants.MASTER_CRON_LINK) |
... | ... | |
170 | 156 |
if dirpath == constants.DATA_DIR: |
171 | 157 |
for i in filenames: |
172 | 158 |
os.unlink(os.path.join(dirpath, i)) |
173 |
utils.RemoveFile(constants.CLUSTER_NAME_FILE) |
|
174 | 159 |
|
175 | 160 |
f = open('/root/.ssh/id_dsa.pub', 'r') |
176 | 161 |
try: |
... | ... | |
794 | 779 |
file_name) |
795 | 780 |
return False |
796 | 781 |
|
797 |
if file_name not in [constants.CLUSTER_CONF_FILE, "/etc/hosts", |
|
798 |
"/etc/ssh/ssh_known_hosts"]: |
|
782 |
allowed_files = [constants.CLUSTER_CONF_FILE, "/etc/hosts", |
|
783 |
"/etc/ssh/ssh_known_hosts"] |
|
784 |
allowed_files.extend(ssconf.SimpleStore().GetFileList()) |
|
785 |
if file_name not in allowed_files: |
|
799 | 786 |
logger.Error("Filename passed to UploadFile not in allowed" |
800 | 787 |
" upload targets: '%s'" % file_name) |
801 | 788 |
return False |
b/lib/cmdlib.py | ||
---|---|---|
19 | 19 |
# 02110-1301, USA. |
20 | 20 |
|
21 | 21 |
|
22 |
"""Module implementing the commands used by gnt-* programs."""
|
|
22 |
"""Module implementing the master-side code."""
|
|
23 | 23 |
|
24 | 24 |
# pylint: disable-msg=W0613,W0201 |
25 | 25 |
|
... | ... | |
84 | 84 |
raise errors.OpPrereqError, ("Cluster not initialized yet," |
85 | 85 |
" use 'gnt-cluster init' first.") |
86 | 86 |
if self.REQ_MASTER: |
87 |
master = cfg.GetMaster()
|
|
87 |
master = sstore.GetMasterNode()
|
|
88 | 88 |
if master != socket.gethostname(): |
89 | 89 |
raise errors.OpPrereqError, ("Commands must be run on the master" |
90 | 90 |
" node %s" % master) |
... | ... | |
406 | 406 |
(result.cmd, result.exit_code, result.output)) |
407 | 407 |
|
408 | 408 |
|
409 |
def _InitClusterInterface(fullname, name, ip): |
|
410 |
"""Initialize the master startup script. |
|
411 |
|
|
412 |
""" |
|
413 |
f = file(constants.CLUSTER_NAME_FILE, 'w') |
|
414 |
f.write("%s\n" % fullname) |
|
415 |
f.close() |
|
416 |
|
|
417 |
f = file(constants.MASTER_INITD_SCRIPT, 'w') |
|
418 |
f.write ("#!/bin/sh\n") |
|
419 |
f.write ("\n") |
|
420 |
f.write ("# Start Ganeti Master Virtual Address\n") |
|
421 |
f.write ("\n") |
|
422 |
f.write ("DESC=\"Ganeti Master IP\"\n") |
|
423 |
f.write ("MASTERNAME=\"%s\"\n" % name) |
|
424 |
f.write ("MASTERIP=\"%s\"\n" % ip) |
|
425 |
f.write ("case \"$1\" in\n") |
|
426 |
f.write (" start)\n") |
|
427 |
f.write (" if fping -q -c 3 ${MASTERIP} &>/dev/null; then\n") |
|
428 |
f.write (" echo \"$MASTERNAME no-go - there is already a master.\"\n") |
|
429 |
f.write (" rm -f %s\n" % constants.MASTER_CRON_LINK) |
|
430 |
f.write (" scp ${MASTERNAME}:%s %s\n" % |
|
431 |
(constants.CLUSTER_CONF_FILE, constants.CLUSTER_CONF_FILE)) |
|
432 |
f.write (" else\n") |
|
433 |
f.write (" echo -n \"Starting $DESC: \"\n") |
|
434 |
f.write (" ip address add ${MASTERIP}/32 dev xen-br0" |
|
435 |
" label xen-br0:0\n") |
|
436 |
f.write (" arping -q -U -c 3 -I xen-br0 -s ${MASTERIP} ${MASTERIP}\n") |
|
437 |
f.write (" echo \"$MASTERNAME.\"\n") |
|
438 |
f.write (" fi\n") |
|
439 |
f.write (" ;;\n") |
|
440 |
f.write (" stop)\n") |
|
441 |
f.write (" echo -n \"Stopping $DESC: \"\n") |
|
442 |
f.write (" ip address del ${MASTERIP}/32 dev xen-br0\n") |
|
443 |
f.write (" echo \"$MASTERNAME.\"\n") |
|
444 |
f.write (" ;;\n") |
|
445 |
f.write (" *)\n") |
|
446 |
f.write (" echo \"Usage: $0 {start|stop}\" >&2\n") |
|
447 |
f.write (" exit 1\n") |
|
448 |
f.write (" ;;\n") |
|
449 |
f.write ("esac\n") |
|
450 |
f.write ("\n") |
|
451 |
f.write ("exit 0\n") |
|
452 |
f.flush() |
|
453 |
os.fsync(f.fileno()) |
|
454 |
f.close() |
|
455 |
os.chmod(constants.MASTER_INITD_SCRIPT, 0755) |
|
456 |
|
|
457 |
|
|
458 | 409 |
class LUInitCluster(LogicalUnit): |
459 | 410 |
"""Initialise the cluster. |
460 | 411 |
|
... | ... | |
462 | 413 |
HPATH = "cluster-init" |
463 | 414 |
HTYPE = constants.HTYPE_CLUSTER |
464 | 415 |
_OP_REQP = ["cluster_name", "hypervisor_type", "vg_name", "mac_prefix", |
465 |
"def_bridge"] |
|
416 |
"def_bridge", "master_netdev"]
|
|
466 | 417 |
REQ_CLUSTER = False |
467 | 418 |
|
468 | 419 |
def BuildHooksEnv(self): |
... | ... | |
474 | 425 |
""" |
475 | 426 |
|
476 | 427 |
env = {"CLUSTER": self.op.cluster_name, |
477 |
"MASTER": self.hostname} |
|
428 |
"MASTER": self.hostname['hostname_full']}
|
|
478 | 429 |
return env, [], [self.hostname['hostname_full']] |
479 | 430 |
|
480 | 431 |
def CheckPrereq(self): |
... | ... | |
528 | 479 |
raise errors.OpPrereqError, ("Invalid hypervisor type given '%s'" % |
529 | 480 |
self.op.hypervisor_type) |
530 | 481 |
|
482 |
result = utils.RunCmd(["ip", "link", "show", "dev", self.op.master_netdev]) |
|
483 |
if result.failed: |
|
484 |
raise errors.OpPrereqError, ("Invalid master netdev given (%s): '%s'" % |
|
485 |
(self.op.master_netdev, result.output)) |
|
486 |
|
|
531 | 487 |
def Exec(self, feedback_fn): |
532 | 488 |
"""Initialize the cluster. |
533 | 489 |
|
... | ... | |
535 | 491 |
clustername = self.clustername |
536 | 492 |
hostname = self.hostname |
537 | 493 |
|
538 |
# adds the cluste name file and master startup script |
|
539 |
_InitClusterInterface(clustername['hostname_full'], |
|
540 |
clustername['hostname'], |
|
541 |
clustername['ip']) |
|
542 |
|
|
543 | 494 |
# set up the simple store |
544 | 495 |
ss = ssconf.SimpleStore() |
545 | 496 |
ss.SetKey(ss.SS_HYPERVISOR, self.op.hypervisor_type) |
497 |
ss.SetKey(ss.SS_MASTER_NODE, hostname['hostname_full']) |
|
498 |
ss.SetKey(ss.SS_MASTER_IP, clustername['ip']) |
|
499 |
ss.SetKey(ss.SS_MASTER_NETDEV, self.op.master_netdev) |
|
546 | 500 |
|
547 | 501 |
# set up the inter-node password and certificate |
548 | 502 |
_InitGanetiServerSetup(ss) |
... | ... | |
590 | 544 |
Any errors are signalled by raising errors.OpPrereqError. |
591 | 545 |
|
592 | 546 |
""" |
593 |
master = self.cfg.GetMaster()
|
|
547 |
master = self.sstore.GetMasterNode()
|
|
594 | 548 |
|
595 | 549 |
nodelist = self.cfg.GetNodeList() |
596 | 550 |
if len(nodelist) > 0 and nodelist != [master]: |
597 |
raise errors.OpPrereqError, ("There are still %d node(s) in "
|
|
598 |
"this cluster." % (len(nodelist) - 1))
|
|
551 |
raise errors.OpPrereqError, ("There are still %d node(s) in " |
|
552 |
"this cluster." % (len(nodelist) - 1)) |
|
599 | 553 |
|
600 | 554 |
def Exec(self, feedback_fn): |
601 | 555 |
"""Destroys the cluster. |
... | ... | |
603 | 557 |
""" |
604 | 558 |
utils.CreateBackup('/root/.ssh/id_dsa') |
605 | 559 |
utils.CreateBackup('/root/.ssh/id_dsa.pub') |
606 |
rpc.call_node_leave_cluster(self.cfg.GetMaster())
|
|
560 |
rpc.call_node_leave_cluster(self.sstore.GetMasterNode())
|
|
607 | 561 |
|
608 | 562 |
|
609 | 563 |
class LUVerifyCluster(NoHooksLU): |
... | ... | |
795 | 749 |
feedback_fn("* Verifying global settings") |
796 | 750 |
self.cfg.VerifyConfig() |
797 | 751 |
|
798 |
master = self.cfg.GetMaster()
|
|
752 |
master = self.sstore.GetMasterNode()
|
|
799 | 753 |
vg_name = self.cfg.GetVGName() |
800 | 754 |
nodelist = utils.NiceSort(self.cfg.GetNodeList()) |
801 | 755 |
instancelist = utils.NiceSort(self.cfg.GetInstanceList()) |
... | ... | |
1029 | 983 |
|
1030 | 984 |
instance_list = self.cfg.GetInstanceList() |
1031 | 985 |
|
1032 |
masternode = self.cfg.GetMaster()
|
|
986 |
masternode = self.sstore.GetMasterNode()
|
|
1033 | 987 |
if node.name == masternode: |
1034 | 988 |
raise errors.OpPrereqError, ("Node is the master node," |
1035 | 989 |
" you need to failover first.") |
... | ... | |
1253 | 1207 |
|
1254 | 1208 |
# check that the type of the node (single versus dual homed) is the |
1255 | 1209 |
# same as for the master |
1256 |
myself = cfg.GetNodeInfo(cfg.GetMaster())
|
|
1210 |
myself = cfg.GetNodeInfo(self.sstore.GetMasterNode())
|
|
1257 | 1211 |
master_singlehomed = myself.secondary_ip == myself.primary_ip |
1258 | 1212 |
newbie_singlehomed = secondary_ip == primary_ip |
1259 | 1213 |
if master_singlehomed != newbie_singlehomed: |
... | ... | |
1378 | 1332 |
|
1379 | 1333 |
# Distribute updated /etc/hosts and known_hosts to all nodes, |
1380 | 1334 |
# including the node just added |
1381 |
myself = self.cfg.GetNodeInfo(self.cfg.GetMaster())
|
|
1335 |
myself = self.cfg.GetNodeInfo(self.sstore.GetMasterNode())
|
|
1382 | 1336 |
dist_nodes = self.cfg.GetNodeList() + [node] |
1383 | 1337 |
if myself.name in dist_nodes: |
1384 | 1338 |
dist_nodes.remove(myself.name) |
... | ... | |
1391 | 1345 |
logger.Error("copy of file %s to node %s failed" % |
1392 | 1346 |
(fname, to_node)) |
1393 | 1347 |
|
1394 |
to_copy = [constants.MASTER_CRON_FILE, |
|
1395 |
constants.MASTER_INITD_SCRIPT, |
|
1396 |
constants.CLUSTER_NAME_FILE] |
|
1348 |
to_copy = [constants.MASTER_CRON_FILE] |
|
1397 | 1349 |
to_copy.extend(ss.GetFileList()) |
1398 | 1350 |
for fname in to_copy: |
1399 | 1351 |
if not ssh.CopyFileToNode(node, fname): |
... | ... | |
1435 | 1387 |
""" |
1436 | 1388 |
self.new_master = socket.gethostname() |
1437 | 1389 |
|
1438 |
self.old_master = self.cfg.GetMaster()
|
|
1390 |
self.old_master = self.sstore.GetMasterNode()
|
|
1439 | 1391 |
|
1440 | 1392 |
if self.old_master == self.new_master: |
1441 | 1393 |
raise errors.OpPrereqError, ("This commands must be run on the node" |
... | ... | |
1460 | 1412 |
logger.Error("could disable the master role on the old master" |
1461 | 1413 |
" %s, please disable manually" % self.old_master) |
1462 | 1414 |
|
1415 |
ss = self.sstore |
|
1416 |
ss.SetKey(ss.SS_MASTER_NODE, self.new_master) |
|
1417 |
if not rpc.call_upload_file(self.cfg.GetNodeList(), |
|
1418 |
ss.KeyToFilename(ss.SS_MASTER_NODE)): |
|
1419 |
logger.Error("could not distribute the new simple store master file" |
|
1420 |
" to the other nodes, please check.") |
|
1421 |
|
|
1463 | 1422 |
if not rpc.call_node_start_master(self.new_master): |
1464 | 1423 |
logger.Error("could not start the master role on the new master" |
1465 | 1424 |
" %s, please check" % self.new_master) |
1425 |
feedback_fn("Error in activating the master IP on the new master,\n" |
|
1426 |
"please fix manually.") |
|
1466 | 1427 |
|
1467 |
self.cfg.SetMaster(self.new_master) |
|
1468 | 1428 |
|
1469 | 1429 |
|
1470 | 1430 |
class LUQueryClusterInfo(NoHooksLU): |
... | ... | |
1492 | 1452 |
"config_version": constants.CONFIG_VERSION, |
1493 | 1453 |
"os_api_version": constants.OS_API_VERSION, |
1494 | 1454 |
"export_version": constants.EXPORT_VERSION, |
1495 |
"master": self.cfg.GetMaster(),
|
|
1455 |
"master": self.sstore.GetMasterNode(),
|
|
1496 | 1456 |
"architecture": (platform.architecture()[0], platform.machine()), |
1497 | 1457 |
"instances": [(instance.name, instance.primary_node) |
1498 | 1458 |
for instance in instances], |
... | ... | |
1748 | 1708 |
"INSTANCE_SECONDARIES": " ".join(self.instance.secondary_nodes), |
1749 | 1709 |
"FORCE": self.op.force, |
1750 | 1710 |
} |
1751 |
nl = ([self.cfg.GetMaster(), self.instance.primary_node] +
|
|
1711 |
nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
|
|
1752 | 1712 |
list(self.instance.secondary_nodes)) |
1753 | 1713 |
return env, nl, nl |
1754 | 1714 |
|
... | ... | |
1833 | 1793 |
"INSTANCE_PRIMARY": self.instance.primary_node, |
1834 | 1794 |
"INSTANCE_SECONDARIES": " ".join(self.instance.secondary_nodes), |
1835 | 1795 |
} |
1836 |
nl = ([self.cfg.GetMaster(), self.instance.primary_node] +
|
|
1796 |
nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
|
|
1837 | 1797 |
list(self.instance.secondary_nodes)) |
1838 | 1798 |
return env, nl, nl |
1839 | 1799 |
|
... | ... | |
1882 | 1842 |
"INSTANCE_PRIMARY": self.instance.primary_node, |
1883 | 1843 |
"INSTANCE_SECONDARIES": " ".join(self.instance.secondary_nodes), |
1884 | 1844 |
} |
1885 |
nl = ([self.cfg.GetMaster(), self.instance.primary_node] +
|
|
1845 |
nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
|
|
1886 | 1846 |
list(self.instance.secondary_nodes)) |
1887 | 1847 |
return env, nl, nl |
1888 | 1848 |
|
... | ... | |
2044 | 2004 |
"INSTANCE_SECONDARIES": " ".join(self.instance.secondary_nodes), |
2045 | 2005 |
"IGNORE_CONSISTENCY": self.op.ignore_consistency, |
2046 | 2006 |
} |
2047 |
nl = [self.cfg.GetMaster()] + list(self.instance.secondary_nodes)
|
|
2007 |
nl = [self.sstore.GetMasterNode()] + list(self.instance.secondary_nodes)
|
|
2048 | 2008 |
return env, nl, nl |
2049 | 2009 |
|
2050 | 2010 |
def CheckPrereq(self): |
... | ... | |
2367 | 2327 |
if self.inst_ip: |
2368 | 2328 |
env["INSTANCE_IP"] = self.inst_ip |
2369 | 2329 |
|
2370 |
nl = ([self.cfg.GetMaster(), self.op.pnode] +
|
|
2330 |
nl = ([self.sstore.GetMasterNode(), self.op.pnode] +
|
|
2371 | 2331 |
self.secondaries) |
2372 | 2332 |
return env, nl, nl |
2373 | 2333 |
|
... | ... | |
2675 | 2635 |
"NEW_SECONDARY": self.op.remote_node, |
2676 | 2636 |
"DISK_NAME": self.op.disk_name, |
2677 | 2637 |
} |
2678 |
nl = [self.cfg.GetMaster(), self.instance.primary_node,
|
|
2638 |
nl = [self.sstore.GetMasterNode(), self.instance.primary_node,
|
|
2679 | 2639 |
self.op.remote_node,] + list(self.instance.secondary_nodes) |
2680 | 2640 |
return env, nl, nl |
2681 | 2641 |
|
... | ... | |
2787 | 2747 |
"DISK_ID": self.op.disk_id, |
2788 | 2748 |
"OLD_SECONDARY": self.old_secondary, |
2789 | 2749 |
} |
2790 |
nl = [self.cfg.GetMaster(),
|
|
2750 |
nl = [self.sstore.GetMasterNode(),
|
|
2791 | 2751 |
self.instance.primary_node] + list(self.instance.secondary_nodes) |
2792 | 2752 |
return env, nl, nl |
2793 | 2753 |
|
... | ... | |
2872 | 2832 |
"NEW_SECONDARY": self.op.remote_node, |
2873 | 2833 |
"OLD_SECONDARY": self.instance.secondary_nodes[0], |
2874 | 2834 |
} |
2875 |
nl = [self.cfg.GetMaster(),
|
|
2835 |
nl = [self.sstore.GetMasterNode(),
|
|
2876 | 2836 |
self.instance.primary_node] + list(self.instance.secondary_nodes) |
2877 | 2837 |
return env, nl, nl |
2878 | 2838 |
|
... | ... | |
2920 | 2880 |
# start of work |
2921 | 2881 |
remote_node = self.op.remote_node |
2922 | 2882 |
cfg = self.cfg |
2883 |
vgname = cfg.GetVGName() |
|
2923 | 2884 |
for dev in instance.disks: |
2924 | 2885 |
size = dev.size |
2925 |
new_drbd = _GenerateMDDRBDBranch(cfg, self.cfg.GetVGName(),
|
|
2926 |
instance.primary_node, remote_node, size,
|
|
2886 |
new_drbd = _GenerateMDDRBDBranch(cfg, vgname, instance.primary_node,
|
|
2887 |
remote_node, size, |
|
2927 | 2888 |
"%s-%s" % (instance.name, dev.iv_name)) |
2928 | 2889 |
iv_names[dev.iv_name] = (dev, dev.children[0], new_drbd) |
2929 | 2890 |
logger.Info("adding new mirror component on secondary for %s" % |
... | ... | |
2948 | 2909 |
# call the primary node to add the mirror to md |
2949 | 2910 |
logger.Info("adding new mirror component to md") |
2950 | 2911 |
if not rpc.call_blockdev_addchild(instance.primary_node, dev, |
2951 |
new_drbd):
|
|
2912 |
new_drbd): |
|
2952 | 2913 |
logger.Error("Can't add mirror compoment to md!") |
2953 | 2914 |
cfg.SetDiskID(new_drbd, remote_node) |
2954 | 2915 |
if not rpc.call_blockdev_remove(remote_node, new_drbd): |
... | ... | |
3172 | 3133 |
if self.bridge: |
3173 | 3134 |
env["BRIDGE"] = self.bridge |
3174 | 3135 |
|
3175 |
nl = [self.cfg.GetMaster(),
|
|
3136 |
nl = [self.sstore.GetMasterNode(),
|
|
3176 | 3137 |
self.instance.primary_node] + list(self.instance.secondary_nodes) |
3177 | 3138 |
|
3178 | 3139 |
return env, nl, nl |
... | ... | |
3295 | 3256 |
"EXPORT_NODE": self.op.target_node, |
3296 | 3257 |
"EXPORT_DO_SHUTDOWN": self.op.shutdown, |
3297 | 3258 |
} |
3298 |
nl = [self.cfg.GetMaster(), self.instance.primary_node,
|
|
3259 |
nl = [self.sstore.GetMasterNode(), self.instance.primary_node,
|
|
3299 | 3260 |
self.op.target_node] |
3300 | 3261 |
return env, nl, nl |
3301 | 3262 |
|
b/lib/config.py | ||
---|---|---|
516 | 516 |
self._OpenConfig() |
517 | 517 |
self._ReleaseLock() |
518 | 518 |
return self._config_data.cluster.mac_prefix |
519 |
|
|
520 |
def GetMaster(self): |
|
521 |
"""Get the name of the master. |
|
522 |
|
|
523 |
""" |
|
524 |
self._OpenConfig() |
|
525 |
self._ReleaseLock() |
|
526 |
return self._config_data.cluster.master_node |
|
527 |
|
|
528 |
def SetMaster(self, master_node): |
|
529 |
"""Change the master of the cluster. |
|
530 |
|
|
531 |
As with all changes, the configuration data will be distributed to |
|
532 |
all nodes. |
|
533 |
|
|
534 |
This function is used for manual master failover. |
|
535 |
|
|
536 |
""" |
|
537 |
self._OpenConfig() |
|
538 |
self._config_data.cluster.master_node = master_node |
|
539 |
self._WriteConfig() |
|
540 |
self._ReleaseLock() |
b/lib/constants.py | ||
---|---|---|
32 | 32 |
# file paths |
33 | 33 |
DATA_DIR = "/var/lib/ganeti" |
34 | 34 |
CLUSTER_CONF_FILE = DATA_DIR + "/config.data" |
35 |
CLUSTER_NAME_FILE = DATA_DIR + "/cluster-name" |
|
36 | 35 |
SSL_CERT_FILE = DATA_DIR + "/server.pem" |
37 | 36 |
HYPERCONF_FILE = DATA_DIR + "/hypervisor" |
38 | 37 |
WATCHER_STATEFILE = DATA_DIR + "/restart_state" |
... | ... | |
46 | 45 |
DEFAULT_NODED_PORT = 1811 |
47 | 46 |
FIRST_DRBD_PORT = 11000 |
48 | 47 |
LAST_DRBD_PORT = 14999 |
49 |
MASTER_INITD_SCRIPT = "/etc/init.d/ganeti-master" |
|
50 |
MASTER_INITD_NAME = "ganeti-master" |
|
48 |
MASTER_SCRIPT = "ganeti-master" |
|
51 | 49 |
|
52 | 50 |
LOG_DIR = "/var/log/ganeti" |
53 | 51 |
LOG_OS_DIR = LOG_DIR + "/os" |
... | ... | |
101 | 99 |
MASTER_CRON_FILE, |
102 | 100 |
] |
103 | 101 |
|
104 |
MASTER_CONFIGFILES = [MASTER_CRON_LINK, |
|
105 |
"/etc/rc2.d/S21%s" % MASTER_INITD_NAME] |
|
102 |
MASTER_CONFIGFILES = [MASTER_CRON_LINK,] |
|
106 | 103 |
|
107 | 104 |
NODE_CONFIGFILES = [NODE_INITD_SCRIPT, |
108 | 105 |
"/etc/rc2.d/S20%s" % NODE_INITD_NAME, |
b/lib/mcpu.py | ||
---|---|---|
115 | 115 |
lu.CheckPrereq() |
116 | 116 |
do_hooks = lu_class.HPATH is not None |
117 | 117 |
if do_hooks: |
118 |
hm = HooksMaster(rpc.call_hooks_runner, self.cfg, lu) |
|
118 |
hm = HooksMaster(rpc.call_hooks_runner, self.cfg, self.sstore, lu)
|
|
119 | 119 |
hm.RunPhase(constants.HOOKS_PHASE_PRE) |
120 | 120 |
result = lu.Exec(feedback_fn) |
121 | 121 |
if do_hooks: |
... | ... | |
148 | 148 |
lu = lu_class(self, op, self.cfg, self.sstore) |
149 | 149 |
lu.CheckPrereq() |
150 | 150 |
#if do_hooks: |
151 |
# hm = HooksMaster(rpc.call_hooks_runner, self.cfg, lu) |
|
151 |
# hm = HooksMaster(rpc.call_hooks_runner, self.cfg, self.sstore, lu)
|
|
152 | 152 |
# hm.RunPhase(constants.HOOKS_PHASE_PRE) |
153 | 153 |
result = lu.Exec(feedback_fn) |
154 | 154 |
#if do_hooks: |
... | ... | |
168 | 168 |
which behaves the same works. |
169 | 169 |
|
170 | 170 |
""" |
171 |
def __init__(self, callfn, cfg, lu): |
|
171 |
def __init__(self, callfn, cfg, sstore, lu):
|
|
172 | 172 |
self.callfn = callfn |
173 | 173 |
self.cfg = cfg |
174 |
self.sstore = sstore |
|
174 | 175 |
self.lu = lu |
175 | 176 |
self.op = lu.op |
176 | 177 |
self.hpath = self.lu.HPATH |
... | ... | |
200 | 201 |
|
201 | 202 |
if self.cfg is not None: |
202 | 203 |
env["GANETI_CLUSTER"] = self.cfg.GetClusterName() |
203 |
env["GANETI_MASTER"] = self.cfg.GetMaster() |
|
204 |
if self.sstore is not None: |
|
205 |
env["GANETI_MASTER"] = self.sstore.GetMasterNode() |
|
204 | 206 |
|
205 | 207 |
for key in env: |
206 | 208 |
if not isinstance(env[key], str): |
b/lib/opcodes.py | ||
---|---|---|
53 | 53 |
"""Initialise the cluster.""" |
54 | 54 |
OP_ID = "OP_CLUSTER_INIT" |
55 | 55 |
__slots__ = ["cluster_name", "secondary_ip", "hypervisor_type", |
56 |
"vg_name", "mac_prefix", "def_bridge"] |
|
56 |
"vg_name", "mac_prefix", "def_bridge", "master_netdev"]
|
|
57 | 57 |
|
58 | 58 |
|
59 | 59 |
class OpDestroyCluster(OpCode): |
b/lib/ssconf.py | ||
---|---|---|
57 | 57 |
_SS_FILEPREFIX = "ssconf_" |
58 | 58 |
SS_HYPERVISOR = "hypervisor" |
59 | 59 |
SS_NODED_PASS = "node_pass" |
60 |
_VALID_KEYS = (SS_HYPERVISOR, SS_NODED_PASS,) |
|
60 |
SS_MASTER_NODE = "master_node" |
|
61 |
SS_MASTER_IP = "master_ip" |
|
62 |
SS_MASTER_NETDEV = "master_netdev" |
|
63 |
_VALID_KEYS = (SS_HYPERVISOR, SS_NODED_PASS, SS_MASTER_NODE, SS_MASTER_IP, |
|
64 |
SS_MASTER_NETDEV) |
|
61 | 65 |
_MAX_SIZE = 4096 |
62 | 66 |
|
63 | 67 |
def __init__(self, cfg_location=None): |
... | ... | |
128 | 132 |
""" |
129 | 133 |
return self._ReadFile(self.SS_NODED_PASS) |
130 | 134 |
|
135 |
def GetMasterNode(self): |
|
136 |
"""Get the hostname of the master node for this cluster. |
|
137 |
|
|
138 |
""" |
|
139 |
return self._ReadFile(self.SS_MASTER_NODE) |
|
140 |
|
|
141 |
def GetMasterIP(self): |
|
142 |
"""Get the IP of the master node for this cluster. |
|
143 |
|
|
144 |
""" |
|
145 |
return self._ReadFile(self.SS_MASTER_IP) |
|
146 |
|
|
147 |
def GetMasterNetdev(self): |
|
148 |
"""Get the netdev to which we'll add the master ip. |
|
149 |
|
|
150 |
""" |
|
151 |
return self._ReadFile(self.SS_MASTER_NETDEV) |
|
152 |
|
|
131 | 153 |
def SetKey(self, key, value): |
132 | 154 |
"""Set the value of a key. |
133 | 155 |
|
b/scripts/gnt-cluster | ||
---|---|---|
40 | 40 |
hypervisor_type=opts.hypervisor_type, |
41 | 41 |
vg_name=opts.vg_name, |
42 | 42 |
mac_prefix=opts.mac_prefix, |
43 |
def_bridge=opts.def_bridge) |
|
43 |
def_bridge=opts.def_bridge, |
|
44 |
master_netdev=opts.master_netdev) |
|
44 | 45 |
SubmitOpCode(op) |
45 | 46 |
return 0 |
46 | 47 |
|
... | ... | |
210 | 211 |
" to connect the instances to [xen-br0]", |
211 | 212 |
metavar="BRIDGE", |
212 | 213 |
default="xen-br0",), |
214 |
make_option("--master-netdev", dest="master_netdev", |
|
215 |
help="Specify the node interface (cluster-wide)" |
|
216 |
" on which the master IP address will be added " |
|
217 |
" [xen-br0]", |
|
218 |
metavar="NETDEV", |
|
219 |
default="xen-br0",), |
|
213 | 220 |
], |
214 | 221 |
"[opts...] <cluster_name>", |
215 | 222 |
"Initialises a new cluster configuration"), |
/dev/null | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2006, 2007 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 |
"""Module implementing a fake ConfigWriter""" |
|
23 |
|
|
24 |
import socket |
|
25 |
|
|
26 |
class FakeConfig: |
|
27 |
"""Fake configuration object""" |
|
28 |
|
|
29 |
def IsCluster(self): |
|
30 |
return True |
|
31 |
|
|
32 |
def GetClusterName(self): |
|
33 |
return "test.cluster" |
|
34 |
|
|
35 |
def GetNodeList(self): |
|
36 |
return ["a", "b", "c"] |
|
37 |
|
|
38 |
def GetMaster(self): |
|
39 |
return socket.gethostname() |
b/testing/ganeti.hooks_unittest.py | ||
---|---|---|
36 | 36 |
from ganeti import cmdlib |
37 | 37 |
from ganeti.constants import HKR_SUCCESS, HKR_FAIL, HKR_SKIP |
38 | 38 |
|
39 |
from fake_config import FakeConfig
|
|
39 |
from mocks import FakeConfig, FakeSStore
|
|
40 | 40 |
|
41 | 41 |
class FakeLU(cmdlib.LogicalUnit): |
42 | 42 |
HPATH = "test" |
... | ... | |
228 | 228 |
def testTotalFalse(self): |
229 | 229 |
"""Test complete rpc failure""" |
230 | 230 |
cfg = FakeConfig() |
231 |
sstore = FakeSStore() |
|
231 | 232 |
op = opcodes.OpCode() |
232 |
lu = FakeLU(None, op, cfg, None)
|
|
233 |
hm = mcpu.HooksMaster(self._call_false, cfg, lu) |
|
233 |
lu = FakeLU(None, op, cfg, sstore)
|
|
234 |
hm = mcpu.HooksMaster(self._call_false, cfg, sstore, lu)
|
|
234 | 235 |
self.failUnlessRaises(errors.HooksFailure, |
235 | 236 |
hm.RunPhase, constants.HOOKS_PHASE_PRE) |
236 | 237 |
hm.RunPhase(constants.HOOKS_PHASE_POST) |
... | ... | |
238 | 239 |
def testIndividualFalse(self): |
239 | 240 |
"""Test individual rpc failure""" |
240 | 241 |
cfg = FakeConfig() |
242 |
sstore = FakeSStore() |
|
241 | 243 |
op = opcodes.OpCode() |
242 |
lu = FakeLU(None, op, cfg, None)
|
|
243 |
hm = mcpu.HooksMaster(self._call_nodes_false, cfg, lu) |
|
244 |
lu = FakeLU(None, op, cfg, sstore)
|
|
245 |
hm = mcpu.HooksMaster(self._call_nodes_false, cfg, sstore, lu)
|
|
244 | 246 |
self.failUnlessRaises(errors.HooksFailure, |
245 | 247 |
hm.RunPhase, constants.HOOKS_PHASE_PRE) |
246 | 248 |
hm.RunPhase(constants.HOOKS_PHASE_POST) |
... | ... | |
249 | 251 |
"""Test individual rpc failure""" |
250 | 252 |
cfg = FakeConfig() |
251 | 253 |
op = opcodes.OpCode() |
252 |
lu = FakeLU(None, op, cfg, None) |
|
253 |
hm = mcpu.HooksMaster(self._call_script_fail, cfg, lu) |
|
254 |
sstore = FakeSStore() |
|
255 |
lu = FakeLU(None, op, cfg, sstore) |
|
256 |
hm = mcpu.HooksMaster(self._call_script_fail, cfg, sstore, lu) |
|
254 | 257 |
self.failUnlessRaises(errors.HooksAbort, |
255 | 258 |
hm.RunPhase, constants.HOOKS_PHASE_PRE) |
256 | 259 |
hm.RunPhase(constants.HOOKS_PHASE_POST) |
... | ... | |
259 | 262 |
"""Test individual rpc failure""" |
260 | 263 |
cfg = FakeConfig() |
261 | 264 |
op = opcodes.OpCode() |
262 |
lu = FakeLU(None, op, cfg, None) |
|
263 |
hm = mcpu.HooksMaster(self._call_script_succeed, cfg, lu) |
|
265 |
sstore = FakeSStore() |
|
266 |
lu = FakeLU(None, op, cfg, sstore) |
|
267 |
hm = mcpu.HooksMaster(self._call_script_succeed, cfg, sstore, lu) |
|
264 | 268 |
for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST): |
265 | 269 |
hm.RunPhase(phase) |
266 | 270 |
|
b/testing/mocks.py | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2006, 2007 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 |
"""Module implementing a fake ConfigWriter""" |
|
23 |
|
|
24 |
import socket |
|
25 |
|
|
26 |
class FakeConfig: |
|
27 |
"""Fake configuration object""" |
|
28 |
|
|
29 |
def IsCluster(self): |
|
30 |
return True |
|
31 |
|
|
32 |
def GetClusterName(self): |
|
33 |
return "test.cluster" |
|
34 |
|
|
35 |
def GetNodeList(self): |
|
36 |
return ["a", "b", "c"] |
|
37 |
|
|
38 |
def GetMaster(self): |
|
39 |
return socket.gethostname() |
|
40 |
|
|
41 |
|
|
42 |
class FakeSStore: |
|
43 |
"""Fake simplestore object""" |
|
44 |
|
|
45 |
def GetMasterNode(self): |
|
46 |
return socket.gethostname() |
Also available in: Unified diff