Revision f154a7a3
b/.gitignore | ||
---|---|---|
31 | 31 |
/ganeti-[0-9]*.[0-9]*.[0-9]* |
32 | 32 |
|
33 | 33 |
# daemons |
34 |
/daemons/daemon-util |
|
34 | 35 |
/daemons/ganeti-cleaner |
35 | 36 |
|
36 | 37 |
# devel |
b/Makefile.am | ||
---|---|---|
53 | 53 |
|
54 | 54 |
CLEANFILES = \ |
55 | 55 |
autotools/replace_vars.sed \ |
56 |
daemons/daemon-util \ |
|
56 | 57 |
daemons/ganeti-cleaner \ |
57 | 58 |
devel/upload \ |
58 | 59 |
doc/examples/bash_completion \ |
... | ... | |
220 | 221 |
tools/cfgupgrade \ |
221 | 222 |
tools/lvmstrap |
222 | 223 |
|
224 |
pkglib_SCRIPTS = \ |
|
225 |
daemons/daemon-util |
|
226 |
|
|
223 | 227 |
EXTRA_DIST = \ |
224 | 228 |
NEWS \ |
225 | 229 |
pylintrc \ |
... | ... | |
227 | 231 |
autotools/check-python-code \ |
228 | 232 |
autotools/docbook-wrapper \ |
229 | 233 |
$(RUN_IN_TEMPDIR) \ |
234 |
daemons/daemon-util.in \ |
|
230 | 235 |
daemons/ganeti-cleaner.in \ |
231 | 236 |
devel/upload.in \ |
232 | 237 |
$(docdot) \ |
... | ... | |
342 | 347 |
sed -f $(REPLACE_VARS_SED) < $< > $@ |
343 | 348 |
chmod u+x $@ |
344 | 349 |
|
345 |
daemons/ganeti-cleaner: daemons/ganeti-cleaner.in \
|
|
350 |
daemons/%: daemons/%.in \
|
|
346 | 351 |
$(REPLACE_VARS_SED) |
347 | 352 |
sed -f $(REPLACE_VARS_SED) < $< > $@ |
348 | 353 |
chmod +x $@ |
... | ... | |
418 | 423 |
echo "LVM_STRIPECOUNT = $(LVM_STRIPECOUNT)"; \ |
419 | 424 |
echo "TOOLSDIR = '$(toolsdir)'"; \ |
420 | 425 |
echo "GNT_SCRIPTS = [$(foreach i,$(notdir $(gnt_scripts)),'$(i)',)]"; \ |
426 |
echo "PKGLIBDIR = '$(pkglibdir)'"; \ |
|
421 | 427 |
} > $@ |
422 | 428 |
|
423 | 429 |
$(REPLACE_VARS_SED): Makefile |
b/NEWS | ||
---|---|---|
72 | 72 |
(``rapi_users``) |
73 | 73 |
- Added option to specify maximum timeout on instance shutdown |
74 | 74 |
- Added ``--no-ssh-init`` option to ``gnt-cluster init`` |
75 |
- Added new helper script to start and stop Ganeti daemons |
|
76 |
(``daemon-util``), with the intent to reduce the work necessary to |
|
77 |
adjust Ganeti for non-Debian distributions and to start/stop daemons |
|
78 |
from one place |
|
75 | 79 |
- Added more unittests |
76 | 80 |
- Fixed critical bug in ganeti-masterd startup |
77 | 81 |
- Pass ``INSTANCE_REINSTALL`` variable to OS installation script when |
b/daemons/daemon-util.in | ||
---|---|---|
1 |
#!/bin/bash |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2009 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 |
set -e |
|
22 |
|
|
23 |
defaults_file=@SYSCONFDIR@/default/ganeti |
|
24 |
|
|
25 |
NODED_ARGS= |
|
26 |
MASTERD_ARGS= |
|
27 |
CONFD_ARGS= |
|
28 |
RAPI_ARGS= |
|
29 |
|
|
30 |
# Read defaults file if it exists |
|
31 |
if [[ -s $defaults_file ]]; then |
|
32 |
. $defaults_file |
|
33 |
fi |
|
34 |
|
|
35 |
_daemon_pidfile() { |
|
36 |
echo "@LOCALSTATEDIR@/run/ganeti/$1.pid" |
|
37 |
} |
|
38 |
|
|
39 |
# Checks whether daemon is running |
|
40 |
check() { |
|
41 |
if [[ "$#" -lt 1 ]]; then |
|
42 |
echo 'Missing daemon name.' >&2 |
|
43 |
exit 1 |
|
44 |
fi |
|
45 |
|
|
46 |
local name="$1"; shift |
|
47 |
|
|
48 |
start-stop-daemon --stop --signal 0 --quiet \ |
|
49 |
--pidfile $(_daemon_pidfile $name) |
|
50 |
} |
|
51 |
|
|
52 |
# Starts a daemon |
|
53 |
start() { |
|
54 |
if [[ "$#" -lt 1 ]]; then |
|
55 |
echo 'Missing daemon name.' >&2 |
|
56 |
exit 1 |
|
57 |
fi |
|
58 |
|
|
59 |
local name="$1"; shift |
|
60 |
|
|
61 |
# Convert daemon name to uppercase after removing "ganeti-" prefix |
|
62 |
local ucname=$(tr a-z A-Z <<< ${name#ganeti-}) |
|
63 |
|
|
64 |
# Read $<daemon>_ARGS and $EXTRA_<daemon>_ARGS |
|
65 |
eval local args="\$${ucname}_ARGS \$EXTRA_${ucname}_ARGS" |
|
66 |
|
|
67 |
start-stop-daemon --start --quiet --oknodo \ |
|
68 |
--pidfile $(_daemon_pidfile $name) \ |
|
69 |
--startas "@PREFIX@/sbin/$name" \ |
|
70 |
-- $args "$@" |
|
71 |
} |
|
72 |
|
|
73 |
# Stops a daemon |
|
74 |
stop() { |
|
75 |
if [[ "$#" -lt 1 ]]; then |
|
76 |
echo 'Missing daemon name.' >&2 |
|
77 |
exit 1 |
|
78 |
fi |
|
79 |
|
|
80 |
local name="$1"; shift |
|
81 |
|
|
82 |
start-stop-daemon --stop --quiet --oknodo --retry 30 \ |
|
83 |
--pidfile $(_daemon_pidfile $name) |
|
84 |
} |
|
85 |
|
|
86 |
# Starts a daemon if it's not yet running |
|
87 |
check_and_start() { |
|
88 |
local name="$1" |
|
89 |
|
|
90 |
if ! check $name; then |
|
91 |
start $name |
|
92 |
fi |
|
93 |
} |
|
94 |
|
|
95 |
# Starts the master role |
|
96 |
start_master() { |
|
97 |
start ganeti-masterd |
|
98 |
start ganeti-rapi |
|
99 |
} |
|
100 |
|
|
101 |
# Stops the master role |
|
102 |
stop_master() { |
|
103 |
stop ganeti-rapi |
|
104 |
stop ganeti-masterd |
|
105 |
} |
|
106 |
|
|
107 |
if [[ "$#" -lt 1 ]]; then |
|
108 |
echo "Usage: $0 <action>" >&2 |
|
109 |
exit 1 |
|
110 |
fi |
|
111 |
|
|
112 |
orig_action=$1; shift |
|
113 |
|
|
114 |
# Replace all dashes (-) with underlines (_) |
|
115 |
action=${orig_action//-/_} |
|
116 |
|
|
117 |
# Is it a known function? |
|
118 |
if ! declare -F "$action" >/dev/null 2>&1; then |
|
119 |
echo "Unknown command: $orig_action" >&2 |
|
120 |
exit 1 |
|
121 |
fi |
|
122 |
|
|
123 |
# Call handler function |
|
124 |
$action "$@" |
b/daemons/ganeti-watcher | ||
---|---|---|
78 | 78 |
return bool(utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)) |
79 | 79 |
|
80 | 80 |
|
81 |
def StartMaster():
|
|
82 |
"""Try to start the master daemon.
|
|
81 |
def EnsureDaemon(name):
|
|
82 |
"""Check for and start daemon if not alive.
|
|
83 | 83 |
|
84 | 84 |
""" |
85 |
result = utils.RunCmd(['ganeti-masterd'])
|
|
85 |
result = utils.RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
|
|
86 | 86 |
if result.failed: |
87 |
logging.error("Can't start the master daemon: output '%s'", result.output)
|
|
88 |
return not result.failed
|
|
89 |
|
|
87 |
logging.error("Can't start daemon '%s', failure %s, output: %s",
|
|
88 |
name, result.fail_reason, result.output)
|
|
89 |
return False |
|
90 | 90 |
|
91 |
def EnsureDaemon(daemon): |
|
92 |
"""Check for and start daemon if not alive. |
|
93 |
|
|
94 |
""" |
|
95 |
pidfile = utils.DaemonPidFileName(daemon) |
|
96 |
pid = utils.ReadPidFile(pidfile) |
|
97 |
if pid == 0 or not utils.IsProcessAlive(pid): # no file or dead pid |
|
98 |
logging.debug("Daemon '%s' not alive, trying to restart", daemon) |
|
99 |
result = utils.RunCmd([daemon]) |
|
100 |
if not result: |
|
101 |
logging.error("Can't start daemon '%s', failure %s, output: %s", |
|
102 |
daemon, result.fail_reason, result.output) |
|
91 |
return True |
|
103 | 92 |
|
104 | 93 |
|
105 | 94 |
class WatcherState(object): |
... | ... | |
503 | 492 |
except luxi.NoMasterError, err: |
504 | 493 |
logging.warning("Master seems to be down (%s), trying to restart", |
505 | 494 |
str(err)) |
506 |
if not StartMaster():
|
|
495 |
if not EnsureDaemon(constants.MASTERD):
|
|
507 | 496 |
logging.critical("Can't start the master, exiting") |
508 | 497 |
sys.exit(constants.EXIT_FAILURE) |
509 | 498 |
# else retry the connection |
b/doc/examples/ganeti.initd.in | ||
---|---|---|
14 | 14 |
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin |
15 | 15 |
DESC="Ganeti cluster" |
16 | 16 |
|
17 |
GANETIRUNDIR="@LOCALSTATEDIR@/run/ganeti" |
|
18 |
|
|
19 |
GANETI_DEFAULTS_FILE="@SYSCONFDIR@/default/ganeti" |
|
20 |
|
|
21 | 17 |
NODED="ganeti-noded" |
22 |
NODED_ARGS="" |
|
23 |
|
|
24 | 18 |
MASTERD="ganeti-masterd" |
25 |
MASTERD_ARGS="" |
|
26 |
|
|
27 | 19 |
CONFD="ganeti-confd" |
28 |
CONFD_ARGS="" |
|
29 |
|
|
30 | 20 |
RAPI="ganeti-rapi" |
31 |
RAPI_ARGS="" |
|
21 |
|
|
22 |
DAEMON_UTIL=@PKGLIBDIR@/daemon-util |
|
32 | 23 |
|
33 | 24 |
SCRIPTNAME="@SYSCONFDIR@/init.d/ganeti" |
34 | 25 |
|
... | ... | |
36 | 27 |
|
37 | 28 |
. /lib/lsb/init-functions |
38 | 29 |
|
39 |
if [ -s $GANETI_DEFAULTS_FILE ]; then |
|
40 |
. $GANETI_DEFAULTS_FILE |
|
41 |
fi |
|
42 |
|
|
43 | 30 |
check_config() { |
44 | 31 |
for fname in \ |
45 | 32 |
"@LOCALSTATEDIR@/lib/ganeti/server.pem" |
... | ... | |
69 | 56 |
|
70 | 57 |
start_action() { |
71 | 58 |
# called as start_action daemon-name |
72 |
local daemon="$1"; shift
|
|
59 |
local daemon="$1" |
|
73 | 60 |
log_action_begin_msg "$daemon" |
74 |
start-stop-daemon --start --quiet \ |
|
75 |
--pidfile "${GANETIRUNDIR}/${daemon}.pid" \ |
|
76 |
--startas "@PREFIX@/sbin/$daemon" \ |
|
77 |
--oknodo \ |
|
78 |
-- "$@" |
|
61 |
$DAEMON_UTIL start "$@" |
|
79 | 62 |
check_exitcode $? |
80 | 63 |
} |
81 | 64 |
|
... | ... | |
83 | 66 |
# called as stop_action daemon-name |
84 | 67 |
local daemon="$1" |
85 | 68 |
log_action_begin_msg "$daemon" |
86 |
start-stop-daemon --stop --quiet --oknodo \ |
|
87 |
--retry 30 --pidfile "${GANETIRUNDIR}/${daemon}.pid" |
|
69 |
$DAEMON_UTIL stop "$@" |
|
88 | 70 |
check_exitcode $? |
89 | 71 |
} |
90 | 72 |
|
... | ... | |
97 | 79 |
fi |
98 | 80 |
} |
99 | 81 |
|
82 |
start_all() { |
|
83 |
check_config |
|
84 |
for i in $NODED $MASTERD $CONFD $RAPI; do \ |
|
85 |
maybe_do "$1" stop_action $i |
|
86 |
done |
|
87 |
} |
|
88 |
|
|
89 |
stop_all() { |
|
90 |
for i in $RAPI $CONFD $MASTERD $NODED; do \ |
|
91 |
maybe_do "$1" stop_action $i |
|
92 |
done |
|
93 |
} |
|
94 |
|
|
100 | 95 |
if [ -n "$2" -a \ |
101 | 96 |
"$2" != "$NODED" -a \ |
102 | 97 |
"$2" != "$CONFD" -a \ |
... | ... | |
109 | 104 |
case "$1" in |
110 | 105 |
start) |
111 | 106 |
log_daemon_msg "Starting $DESC" "$2" |
112 |
check_config |
|
113 |
maybe_do "$2" start_action $NODED $NODED_ARGS |
|
114 |
maybe_do "$2" start_action $MASTERD $MASTERD_ARGS |
|
115 |
maybe_do "$2" start_action $CONFD $CONFD_ARGS |
|
116 |
maybe_do "$2" start_action $RAPI $RAPI_ARGS |
|
107 |
start_all "$2" |
|
117 | 108 |
;; |
118 | 109 |
stop) |
119 | 110 |
log_daemon_msg "Stopping $DESC" "$2" |
120 |
maybe_do "$2" stop_action $RAPI |
|
121 |
maybe_do "$2" stop_action $CONFD |
|
122 |
maybe_do "$2" stop_action $MASTERD |
|
123 |
maybe_do "$2" stop_action $NODED |
|
111 |
stop_all "$2" |
|
124 | 112 |
;; |
125 | 113 |
restart|force-reload) |
126 |
maybe_do "$2" stop_action $RAPI |
|
127 |
maybe_do "$2" stop_action $CONFD |
|
128 |
maybe_do "$2" stop_action $MASTERD |
|
129 |
maybe_do "$2" stop_action $NODED |
|
130 |
check_config |
|
131 |
maybe_do "$2" start_action $NODED $NODED_ARGS |
|
132 |
maybe_do "$2" start_action $MASTERD $MASTERD_ARGS |
|
133 |
maybe_do "$2" start_action $CONFD $CONFD_ARGS |
|
134 |
maybe_do "$2" start_action $RAPI $RAPI_ARGS |
|
114 |
stop_all "$2" |
|
115 |
start_all "$2" |
|
135 | 116 |
;; |
136 | 117 |
*) |
137 | 118 |
log_success_msg "Usage: $SCRIPTNAME {start|stop|force-reload|restart}" |
b/lib/backend.py | ||
---|---|---|
255 | 255 |
|
256 | 256 |
# and now start the master and rapi daemons |
257 | 257 |
if start_daemons: |
258 |
daemons_params = { |
|
259 |
'ganeti-masterd': [], |
|
260 |
'ganeti-rapi': [], |
|
261 |
} |
|
262 | 258 |
if no_voting: |
263 |
daemons_params['ganeti-masterd'].append('--no-voting') |
|
264 |
daemons_params['ganeti-masterd'].append('--yes-do-it') |
|
265 |
for daemon in daemons_params: |
|
266 |
cmd = [daemon] |
|
267 |
cmd.extend(daemons_params[daemon]) |
|
268 |
result = utils.RunCmd(cmd) |
|
269 |
if result.failed: |
|
270 |
msg = "Can't start daemon %s: %s" % (daemon, result.output) |
|
271 |
logging.error(msg) |
|
272 |
err_msgs.append(msg) |
|
259 |
masterd_args = "--no-voting --yes-do-it" |
|
260 |
else: |
|
261 |
masterd_args = "" |
|
262 |
|
|
263 |
env = { |
|
264 |
"EXTRA_MASTERD_ARGS": masterd_args, |
|
265 |
} |
|
266 |
|
|
267 |
result = utils.RunCmd([constants.DAEMON_UTIL, "start-master"], env=env) |
|
268 |
if result.failed: |
|
269 |
msg = "Can't start Ganeti master: %s" % result.output |
|
270 |
logging.error(msg) |
|
271 |
err_msgs.append(msg) |
|
273 | 272 |
|
274 | 273 |
if err_msgs: |
275 | 274 |
_Fail("; ".join(err_msgs)) |
... | ... | |
301 | 300 |
# but otherwise ignore the failure |
302 | 301 |
|
303 | 302 |
if stop_daemons: |
304 |
# stop/kill the rapi and the master daemon |
|
305 |
for daemon in constants.RAPI, constants.MASTERD: |
|
306 |
utils.KillProcess(utils.ReadPidFile(utils.DaemonPidFileName(daemon))) |
|
303 |
result = utils.RunCmd([constants.DAEMON_UTIL, "stop-master"]) |
|
304 |
if result.failed: |
|
305 |
logging.error("Could not stop Ganeti master, command %s had exitcode %s" |
|
306 |
" and error %s", |
|
307 |
result.cmd, result.exit_code, result.output) |
|
307 | 308 |
|
308 | 309 |
|
309 | 310 |
def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub): |
... | ... | |
385 | 386 |
except: |
386 | 387 |
logging.exception("Error while removing cluster secrets") |
387 | 388 |
|
388 |
confd_pid = utils.ReadPidFile(utils.DaemonPidFileName(constants.CONFD))
|
|
389 |
|
|
390 |
if confd_pid:
|
|
391 |
utils.KillProcess(confd_pid, timeout=2)
|
|
389 |
result = utils.RunCmd([constants.DAEMON_UTIL, "stop", constants.CONFD])
|
|
390 |
if result.failed: |
|
391 |
logging.error("Command %s failed with exitcode %s and error %s",
|
|
392 |
result.cmd, result.exit_code, result.output)
|
|
392 | 393 |
|
393 | 394 |
# Raise a custom exception (handled in ganeti-noded) |
394 | 395 |
raise errors.QuitGanetiException(True, 'Shutdown scheduled') |
... | ... | |
2435 | 2436 |
master, myself = ssconf.GetMasterAndMyself() |
2436 | 2437 |
if master == myself: |
2437 | 2438 |
_Fail("ssconf status shows I'm the master node, will not demote") |
2438 |
pid_file = utils.DaemonPidFileName(constants.MASTERD) |
|
2439 |
if utils.IsProcessAlive(utils.ReadPidFile(pid_file)): |
|
2439 |
|
|
2440 |
result = utils.RunCmd([constants.DAEMON_UTIL, "check", constants.MASTERD]) |
|
2441 |
if not result.failed: |
|
2440 | 2442 |
_Fail("The master daemon is running, will not demote") |
2443 |
|
|
2441 | 2444 |
try: |
2442 | 2445 |
if os.path.isfile(constants.CLUSTER_CONF_FILE): |
2443 | 2446 |
utils.CreateBackup(constants.CLUSTER_CONF_FILE) |
2444 | 2447 |
except EnvironmentError, err: |
2445 | 2448 |
if err.errno != errno.ENOENT: |
2446 | 2449 |
_Fail("Error while backing up cluster file: %s", err, exc=True) |
2450 |
|
|
2447 | 2451 |
utils.RemoveFile(constants.CLUSTER_CONF_FILE) |
2448 | 2452 |
|
2449 | 2453 |
|
b/lib/bootstrap.py | ||
---|---|---|
126 | 126 |
if not os.path.exists(constants.HMAC_CLUSTER_KEY): |
127 | 127 |
GenerateHmacKey(constants.HMAC_CLUSTER_KEY) |
128 | 128 |
|
129 |
result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"]) |
|
130 |
|
|
129 |
result = utils.RunCmd([constants.DAEMON_UTIL, "start", constants.NODED]) |
|
131 | 130 |
if result.failed: |
132 | 131 |
raise errors.OpExecError("Could not start the node daemon, command %s" |
133 | 132 |
" had exitcode %s and error %s" % |
... | ... | |
241 | 240 |
(master_netdev, |
242 | 241 |
result.output.strip()), errors.ECODE_INVAL) |
243 | 242 |
|
244 |
if not (os.path.isfile(constants.NODE_INITD_SCRIPT) and |
|
245 |
os.access(constants.NODE_INITD_SCRIPT, os.X_OK)): |
|
246 |
raise errors.OpPrereqError("Init.d script '%s' missing or not" |
|
247 |
" executable." % constants.NODE_INITD_SCRIPT, |
|
248 |
errors.ECODE_ENVIRON) |
|
249 |
|
|
250 | 243 |
dirs = [(constants.RUN_GANETI_DIR, constants.RUN_DIRS_MODE)] |
251 | 244 |
utils.EnsureDirs(dirs) |
252 | 245 |
|
... | ... | |
416 | 409 |
"cat > '%s' << '!EOF.' && \n" |
417 | 410 |
"%s!EOF.\n" |
418 | 411 |
"chmod 0400 %s %s %s && " |
419 |
"%s restart" %
|
|
412 |
"%s start %s" %
|
|
420 | 413 |
(constants.SSL_CERT_FILE, noded_cert, |
421 | 414 |
constants.RAPI_CERT_FILE, rapi_cert, |
422 | 415 |
constants.HMAC_CLUSTER_KEY, hmac_key, |
423 | 416 |
constants.SSL_CERT_FILE, constants.RAPI_CERT_FILE, |
424 | 417 |
constants.HMAC_CLUSTER_KEY, |
425 |
constants.NODE_INITD_SCRIPT))
|
|
418 |
constants.DAEMON_UTIL, constants.NODED))
|
|
426 | 419 |
|
427 | 420 |
result = sshrunner.Run(node, 'root', mycommand, batch=False, |
428 | 421 |
ask_key=ssh_key_check, |
b/lib/constants.py | ||
---|---|---|
104 | 104 |
SSH_KNOWN_HOSTS_FILE = DATA_DIR + "/known_hosts" |
105 | 105 |
RAPI_USERS_FILE = DATA_DIR + "/rapi_users" |
106 | 106 |
QUEUE_DIR = DATA_DIR + "/queue" |
107 |
DAEMON_UTIL = _autoconf.PKGLIBDIR + "/daemon-util" |
|
107 | 108 |
ETC_HOSTS = "/etc/hosts" |
108 | 109 |
DEFAULT_FILE_STORAGE_DIR = _autoconf.FILE_STORAGE_DIR |
109 | 110 |
SYSCONFDIR = _autoconf.SYSCONFDIR |
... | ... | |
112 | 113 |
|
113 | 114 |
MASTER_SOCKET = SOCKET_DIR + "/ganeti-master" |
114 | 115 |
|
115 |
NODE_INITD_SCRIPT = SYSCONFDIR + "/init.d/ganeti" |
|
116 |
|
|
117 | 116 |
NODED = "ganeti-noded" |
118 | 117 |
CONFD = "ganeti-confd" |
119 | 118 |
RAPI = "ganeti-rapi" |
Also available in: Unified diff