"""Cluster related commands"""
-# pylint: disable-msg=W0401,W0613,W0614,C0103
+# pylint: disable=W0401,W0613,W0614,C0103
# W0401: Wildcard import ganeti.cli
# W0613: Unused argument, since all functions follow the same API
# W0614: Unused import %s from wildcard import (since we need cli)
return 0
+def ActivateMasterIp(opts, args):
+ """Activates the master IP.
+
+ """
+ op = opcodes.OpClusterActivateMasterIp()
+ SubmitOpCode(op)
+ return 0
+
+
+def DeactivateMasterIp(opts, args):
+ """Deactivates the master IP.
+
+ """
+ if not opts.confirm:
+ usertext = ("This will disable the master IP. All the open connections to"
+ " the master IP will be closed. To reach the master you will"
+ " need to use its node IP."
+ " Continue?")
+ if not AskUser(usertext):
+ return 1
+
+ op = opcodes.OpClusterDeactivateMasterIp()
+ SubmitOpCode(op)
+ return 0
+
+
def RedistributeConfig(opts, args):
"""Forces push of the cluster configuration.
ToStdout(" - default instance allocator: %s", result["default_iallocator"])
ToStdout(" - primary ip version: %d", result["primary_ip_version"])
ToStdout(" - preallocation wipe disks: %s", result["prealloc_wipe_disks"])
+ ToStdout(" - OS search path: %s", utils.CommaJoin(constants.OS_SEARCH_PATH))
ToStdout("Default node parameters:")
_PrintGroupedParams(result["ndparams"], roman=opts.roman_integers)
@return: the desired exit code
"""
- simulate = opts.simulate_errors
skip_checks = []
- if opts.nodegroup is None:
- # Verify cluster config.
- op = opcodes.OpClusterVerifyConfig(verbose=opts.verbose,
- error_codes=opts.error_codes,
- debug_simulate_errors=simulate)
-
- success, all_groups = SubmitOpCode(op, opts=opts)
- else:
- success = True
- all_groups = [opts.nodegroup]
-
if opts.skip_nplusone_mem:
skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
- jex = JobExecutor(opts=opts, verbose=False)
+ cl = GetClient()
- for group in all_groups:
- op = opcodes.OpClusterVerifyGroup(group_name=group,
- skip_checks=skip_checks,
- verbose=opts.verbose,
- error_codes=opts.error_codes,
- debug_simulate_errors=simulate)
- jex.QueueJob("group " + group, op)
+ op = opcodes.OpClusterVerify(verbose=opts.verbose,
+ error_codes=opts.error_codes,
+ debug_simulate_errors=opts.simulate_errors,
+ skip_checks=skip_checks,
+ group_name=opts.nodegroup)
+ result = SubmitOpCode(op, cl=cl, opts=opts)
+
+ # Keep track of submitted jobs
+ jex = JobExecutor(cl=cl, opts=opts)
+
+ for (status, job_id) in result[constants.JOB_IDS_KEY]:
+ jex.AddJobId(None, status, job_id)
results = jex.GetResults()
- success &= compat.all(r[1][0] for r in results)
- if success:
- return constants.EXIT_SUCCESS
+ (bad_jobs, bad_results) = \
+ map(len,
+ # Convert iterators to lists
+ map(list,
+ # Count errors
+ map(compat.partial(itertools.ifilterfalse, bool),
+ # Convert result to booleans in a tuple
+ zip(*((job_success, len(op_results) == 1 and op_results[0])
+ for (job_success, op_results) in results)))))
+
+ if bad_jobs == 0 and bad_results == 0:
+ rcode = constants.EXIT_SUCCESS
else:
- return constants.EXIT_FAILURE
+ rcode = constants.EXIT_FAILURE
+ if bad_jobs > 0:
+ ToStdout("%s job(s) failed while verifying the cluster.", bad_jobs)
+
+ return rcode
def VerifyDisks(opts, args):
cl = GetClient()
op = opcodes.OpClusterVerifyDisks()
- result = SubmitOpCode(op, opts=opts, cl=cl)
- if not isinstance(result, (list, tuple)) or len(result) != 3:
- raise errors.ProgrammerError("Unknown result type for OpClusterVerifyDisks")
- bad_nodes, instances, missing = result
+ result = SubmitOpCode(op, cl=cl, opts=opts)
+
+ # Keep track of submitted jobs
+ jex = JobExecutor(cl=cl, opts=opts)
+
+ for (status, job_id) in result[constants.JOB_IDS_KEY]:
+ jex.AddJobId(None, status, job_id)
retcode = constants.EXIT_SUCCESS
- if bad_nodes:
+ for (status, result) in jex.GetResults():
+ if not status:
+ ToStdout("Job failed: %s", result)
+ continue
+
+ ((bad_nodes, instances, missing), ) = result
+
for node, text in bad_nodes.items():
ToStdout("Error gathering data on node %s: %s",
node, utils.SafeEncode(text[-400:]))
- retcode |= 1
+ retcode = constants.EXIT_FAILURE
ToStdout("You need to fix these nodes first before fixing instances")
- if instances:
for iname in instances:
if iname in missing:
continue
retcode |= nret
ToStderr("Error activating disks for instance %s: %s", iname, msg)
- if missing:
- for iname, ival in missing.iteritems():
- all_missing = compat.all(x[0] in bad_nodes for x in ival)
- if all_missing:
- ToStdout("Instance %s cannot be verified as it lives on"
- " broken nodes", iname)
- else:
- ToStdout("Instance %s has missing logical volumes:", iname)
- ival.sort()
- for node, vol in ival:
- if node in bad_nodes:
- ToStdout("\tbroken node %s /dev/%s", node, vol)
- else:
- ToStdout("\t%s /dev/%s", node, vol)
-
- ToStdout("You need to run replace or recreate disks for all the above"
- " instances, if this message persist after fixing nodes.")
- retcode |= 1
+ if missing:
+ for iname, ival in missing.iteritems():
+ all_missing = compat.all(x[0] in bad_nodes for x in ival)
+ if all_missing:
+ ToStdout("Instance %s cannot be verified as it lives on"
+ " broken nodes", iname)
+ else:
+ ToStdout("Instance %s has missing logical volumes:", iname)
+ ival.sort()
+ for node, vol in ival:
+ if node in bad_nodes:
+ ToStdout("\tbroken node %s /dev/%s", node, vol)
+ else:
+ ToStdout("\t%s /dev/%s", node, vol)
+
+ ToStdout("You need to replace or recreate disks for all the above"
+ " instances if this message persists after fixing broken nodes.")
+ retcode = constants.EXIT_FAILURE
return retcode
cl = GetClient()
cl.QueryClusterInfo()
return 0
- except Exception: # pylint: disable-msg=W0703
+ except Exception: # pylint: disable=W0703
return 1
ToStdout("%s %s", path, tag)
-def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
- new_confd_hmac_key, new_cds, cds_filename,
- force):
+def _ReadAndVerifyCert(cert_filename, verify_private_key=False):
+ """Reads and verifies an X509 certificate.
+
+ @type cert_filename: string
+ @param cert_filename: the path of the file containing the certificate to
+ verify encoded in PEM format
+ @type verify_private_key: bool
+ @param verify_private_key: whether to verify the private key in addition to
+ the public certificate
+ @rtype: string
+ @return: a string containing the PEM-encoded certificate.
+
+ """
+ try:
+ pem = utils.ReadFile(cert_filename)
+ except IOError, err:
+ raise errors.X509CertError(cert_filename,
+ "Unable to read certificate: %s" % str(err))
+
+ try:
+ OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pem)
+ except Exception, err:
+ raise errors.X509CertError(cert_filename,
+ "Unable to load certificate: %s" % str(err))
+
+ if verify_private_key:
+ try:
+ OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, pem)
+ except Exception, err:
+ raise errors.X509CertError(cert_filename,
+ "Unable to load private key: %s" % str(err))
+
+ return pem
+
+
+def _RenewCrypto(new_cluster_cert, new_rapi_cert, #pylint: disable=R0911
+ rapi_cert_filename, new_spice_cert, spice_cert_filename,
+ spice_cacert_filename, new_confd_hmac_key, new_cds,
+ cds_filename, force):
"""Renews cluster certificates, keys and secrets.
@type new_cluster_cert: bool
@param new_rapi_cert: Whether to generate a new RAPI certificate
@type rapi_cert_filename: string
@param rapi_cert_filename: Path to file containing new RAPI certificate
+ @type new_spice_cert: bool
+ @param new_spice_cert: Whether to generate a new SPICE certificate
+ @type spice_cert_filename: string
+ @param spice_cert_filename: Path to file containing new SPICE certificate
+ @type spice_cacert_filename: string
+ @param spice_cacert_filename: Path to file containing the certificate of the
+ CA that signed the SPICE certificate
@type new_confd_hmac_key: bool
@param new_confd_hmac_key: Whether to generate a new HMAC key
@type new_cds: bool
"""
if new_rapi_cert and rapi_cert_filename:
- ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
+ ToStderr("Only one of the --new-rapi-certificate and --rapi-certificate"
" options can be specified at the same time.")
return 1
" the same time.")
return 1
- if rapi_cert_filename:
- # Read and verify new certificate
- try:
- rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
-
- OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
- rapi_cert_pem)
- except Exception, err: # pylint: disable-msg=W0703
- ToStderr("Can't load new RAPI certificate from %s: %s" %
- (rapi_cert_filename, str(err)))
- return 1
+ if new_spice_cert and (spice_cert_filename or spice_cacert_filename):
+ ToStderr("When using --new-spice-certificate, the --spice-certificate"
+ " and --spice-ca-certificate must not be used.")
+ return 1
- try:
- OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
- except Exception, err: # pylint: disable-msg=W0703
- ToStderr("Can't load new RAPI private key from %s: %s" %
- (rapi_cert_filename, str(err)))
- return 1
+ if bool(spice_cacert_filename) ^ bool(spice_cert_filename):
+ ToStderr("Both --spice-certificate and --spice-ca-certificate must be"
+ " specified.")
+ return 1
- else:
- rapi_cert_pem = None
+ rapi_cert_pem, spice_cert_pem, spice_cacert_pem = (None, None, None)
+ try:
+ if rapi_cert_filename:
+ rapi_cert_pem = _ReadAndVerifyCert(rapi_cert_filename, True)
+ if spice_cert_filename:
+ spice_cert_pem = _ReadAndVerifyCert(spice_cert_filename, True)
+ spice_cacert_pem = _ReadAndVerifyCert(spice_cacert_filename)
+ except errors.X509CertError, err:
+ ToStderr("Unable to load X509 certificate from %s: %s", err[0], err[1])
+ return 1
if cds_filename:
try:
cds = utils.ReadFile(cds_filename)
- except Exception, err: # pylint: disable-msg=W0703
+ except Exception, err: # pylint: disable=W0703
ToStderr("Can't load new cluster domain secret from %s: %s" %
(cds_filename, str(err)))
return 1
def _RenewCryptoInner(ctx):
ctx.feedback_fn("Updating certificates and keys")
- bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
+ bootstrap.GenerateClusterCrypto(new_cluster_cert,
+ new_rapi_cert,
+ new_spice_cert,
new_confd_hmac_key,
new_cds,
rapi_cert_pem=rapi_cert_pem,
+ spice_cert_pem=spice_cert_pem,
+ spice_cacert_pem=spice_cacert_pem,
cds=cds)
files_to_copy = []
if new_rapi_cert or rapi_cert_pem:
files_to_copy.append(constants.RAPI_CERT_FILE)
+ if new_spice_cert or spice_cert_pem:
+ files_to_copy.append(constants.SPICE_CERT_FILE)
+ files_to_copy.append(constants.SPICE_CACERT_FILE)
+
if new_confd_hmac_key:
files_to_copy.append(constants.CONFD_HMAC_KEY)
return _RenewCrypto(opts.new_cluster_cert,
opts.new_rapi_cert,
opts.rapi_cert,
+ opts.new_spice_cert,
+ opts.spice_cert,
+ opts.spice_cacert,
opts.new_confd_hmac_key,
opts.new_cluster_domain_secret,
opts.cluster_domain_secret,
RenewCrypto, ARGS_NONE,
[NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
- NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
+ NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT,
+ NEW_SPICE_CERT_OPT, SPICE_CERT_OPT, SPICE_CACERT_OPT],
"[opts...]",
"Renews cluster certificates, keys and secrets"),
"epo": (
SHUTDOWN_TIMEOUT_OPT, POWER_DELAY_OPT],
"[opts...] [args]",
"Performs an emergency power-off on given args"),
+ "activate-master-ip": (
+ ActivateMasterIp, ARGS_NONE, [], "", "Activates the master IP"),
+ "deactivate-master-ip": (
+ DeactivateMasterIp, ARGS_NONE, [CONFIRM_OPT], "",
+ "Deactivates the master IP"),
}