X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/ae1a845c88bb928228db2e38bdbed7ff4ac01c9e..ef9fa5b9bfcac8b742446864344be0abb1aad3da:/lib/client/gnt_cluster.py diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py index 5ac3713..26d3c3f 100644 --- a/lib/client/gnt_cluster.py +++ b/lib/client/gnt_cluster.py @@ -20,7 +20,7 @@ """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) @@ -139,10 +139,19 @@ def InitCluster(opts, args): ToStderr("Invalid primary ip version value: %s" % str(err)) return 1 + master_netmask = opts.master_netmask + try: + if master_netmask is not None: + master_netmask = int(master_netmask) + except (ValueError, TypeError), err: + ToStderr("Invalid master netmask value: %s" % str(err)) + return 1 + bootstrap.InitCluster(cluster_name=args[0], secondary_ip=opts.secondary_ip, vg_name=vg_name, mac_prefix=opts.mac_prefix, + master_netmask=master_netmask, master_netdev=master_netdev, file_storage_dir=opts.file_storage_dir, shared_file_storage_dir=opts.shared_file_storage_dir, @@ -223,6 +232,32 @@ def RenameCluster(opts, args): 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. @@ -345,6 +380,7 @@ def ShowClusterConfig(opts, args): compat.TryToRoman(result["candidate_pool_size"], convert=opts.roman_integers)) ToStdout(" - master netdev: %s", result["master_netdev"]) + ToStdout(" - master netmask: %s", result["master_netmask"]) ToStdout(" - lvm volume group: %s", result["volume_group_name"]) if result["reserved_lvs"]: reserved_lvs = utils.CommaJoin(result["reserved_lvs"]) @@ -363,6 +399,7 @@ def ShowClusterConfig(opts, args): 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) @@ -454,40 +491,47 @@ def VerifyCluster(opts, args): @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() + + op = opcodes.OpClusterVerify(verbose=opts.verbose, + error_codes=opts.error_codes, + debug_simulate_errors=opts.simulate_errors, + skip_checks=skip_checks, + ignore_errors=opts.ignore_errors, + group_name=opts.nodegroup) + result = SubmitOpCode(op, cl=cl, opts=opts) - 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) + # 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): @@ -615,7 +659,7 @@ def MasterPing(opts, args): cl = GetClient() cl.QueryClusterInfo() return 0 - except Exception: # pylint: disable-msg=W0703 + except Exception: # pylint: disable=W0703 return 1 @@ -639,9 +683,45 @@ def SearchTags(opts, args): 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 @@ -650,6 +730,13 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, @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 @@ -661,7 +748,7 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, """ 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 @@ -671,32 +758,31 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, " 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 @@ -711,10 +797,14 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, 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 = [] @@ -725,6 +815,10 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename, 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) @@ -753,6 +847,9 @@ def RenewCrypto(opts, args): 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, @@ -781,6 +878,7 @@ def SetClusterParams(opts, args): opts.default_iallocator is not None or opts.reserved_lvs is not None or opts.master_netdev is not None or + opts.master_netmask is not None or opts.prealloc_wipe_disks is not None): ToStderr("Please give at least one of the parameters.") return 1 @@ -840,6 +938,13 @@ def SetClusterParams(opts, args): else: opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",") + if opts.master_netmask is not None: + try: + opts.master_netmask = int(opts.master_netmask) + except ValueError: + ToStderr("The --master-netmask option expects an int parameter.") + return 1 + op = opcodes.OpClusterSetParams(vg_name=vg_name, drbd_helper=drbd_helper, enabled_hypervisors=hvlist, @@ -856,6 +961,7 @@ def SetClusterParams(opts, args): default_iallocator=opts.default_iallocator, prealloc_wipe_disks=opts.prealloc_wipe_disks, master_netdev=opts.master_netdev, + master_netmask=opts.master_netmask, reserved_lvs=opts.reserved_lvs) SubmitOpCode(op, opts=opts) return 0 @@ -1253,10 +1359,10 @@ commands = { "init": ( InitCluster, [ArgHost(min=1, max=1)], [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT, - HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT, - NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT, - SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT, - UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT, + HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, MASTER_NETMASK_OPT, + NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, + NOMODIFY_SSH_SETUP_OPT, SECONDARY_IP_OPT, VG_NAME_OPT, + MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT, GLOBAL_SHARED_FILEDIR_OPT], "[opts...] ", "Initialises a new cluster configuration"), @@ -1275,7 +1381,7 @@ commands = { "verify": ( VerifyCluster, ARGS_NONE, [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT, - DRY_RUN_OPT, PRIORITY_OPT, NODEGROUP_OPT], + DRY_RUN_OPT, PRIORITY_OPT, NODEGROUP_OPT, IGNORE_ERRORS_OPT], "", "Does a check on the cluster configuration"), "verify-disks": ( VerifyDisks, ARGS_NONE, [PRIORITY_OPT], @@ -1331,17 +1437,19 @@ commands = { "modify": ( SetClusterParams, ARGS_NONE, [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT, - NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT, - UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT, - NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, RESERVED_LVS_OPT, - DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT], + MASTER_NETMASK_OPT, NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, + MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, + DRBD_HELPER_OPT, NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, + RESERVED_LVS_OPT, DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT, + NODE_PARAMS_OPT], "[opts...]", "Alters the parameters of the cluster"), "renew-crypto": ( 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": ( @@ -1350,6 +1458,11 @@ commands = { 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"), }