Change in vdsm[master]: vdsm-tool: Add register verb
dougsland at redhat.com
dougsland at redhat.com
Sat May 16 17:02:05 UTC 2015
Douglas Schilling Landgraf has uploaded a new change for review.
Change subject: vdsm-tool: Add register verb
......................................................................
vdsm-tool: Add register verb
The new verb register will make the registration
from a node into the Engine. This verb supports
old Engine registration schema and the new one as
service. This commit it's an alternative for
the registration discussions.
Supported Engine: >= 3.3
Change-Id: Ica800027beec1e5a20165bb5e1e78baf9283c1da
Signed-off-by: Douglas Schilling Landgraf <dougsland at redhat.com>
---
M debian/vdsm-python.install
M lib/vdsm/tool/Makefile.am
A lib/vdsm/tool/register.py
M vdsm.spec.in
4 files changed, 524 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/66/40966/1
diff --git a/debian/vdsm-python.install b/debian/vdsm-python.install
index 7a50353..394d3d3 100644
--- a/debian/vdsm-python.install
+++ b/debian/vdsm-python.install
@@ -36,6 +36,7 @@
./usr/lib/python2.7/dist-packages/vdsm/tool/load_needed_modules.py
./usr/lib/python2.7/dist-packages/vdsm/tool/nwfilter.py
./usr/lib/python2.7/dist-packages/vdsm/tool/passwd.py
+./usr/lib/python2.7/dist-packages/vdsm/tool/register.py
./usr/lib/python2.7/dist-packages/vdsm/tool/restore_nets.py
./usr/lib/python2.7/dist-packages/vdsm/tool/service.py
./usr/lib/python2.7/dist-packages/vdsm/tool/transient.py
diff --git a/lib/vdsm/tool/Makefile.am b/lib/vdsm/tool/Makefile.am
index 9f8108c..f939d90 100644
--- a/lib/vdsm/tool/Makefile.am
+++ b/lib/vdsm/tool/Makefile.am
@@ -39,6 +39,7 @@
nwfilter.py \
configfile.py \
configurator.py \
+ register.py \
restore_nets.py \
service.py \
transient.py \
diff --git a/lib/vdsm/tool/register.py b/lib/vdsm/tool/register.py
new file mode 100644
index 0000000..c75e64f
--- /dev/null
+++ b/lib/vdsm/tool/register.py
@@ -0,0 +1,521 @@
+# Copyright (C) 2012-2015 Red Hat, 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; version 2 of the License.
+#
+# 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. A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+import argparse
+import getpass
+import logging
+import M2Crypto
+import os
+import pwd
+import requests
+import selinux
+import socket
+import ssl
+import sys
+import tempfile
+
+from . import expose
+from .. import utils
+
+from vdsm.utils import getHostUUID
+
+
+class Register(object):
+
+ def __init__(self, engine_fqdn, engine_http_port=None,
+ fingerprint=None, ssh_port=None,
+ ssh_user=None, check_fqdn=True,
+ vdc_port=None, node_fqdn=None,
+ node_name=None):
+ """
+ Attributes:
+
+ engine_fqdn - Engine FQDN
+ engine_port - Engine http port
+ fingeprint - Fingerprint to be validated
+ ssh_user - SSH user that will establish the connection from Engine
+ ssh_port - Port of ssh daemon is running
+ check_fqdn - Validate Engine FQDN against CA (True or False)
+ Default is TRUE
+ vdc_port - The communication port between VDSM and Engine
+ node_fqdn - Specify node FQDN
+ node_name - Specify node name
+ """
+ self.logger = self.__set_logger()
+ self.logger.info("=======================================")
+ self.logger.info("Registering the node")
+ self.logger.info("=======================================")
+ self.logger.info("Received the following attributes:")
+
+ self.engine_fqdn = engine_fqdn
+ self.logger.info("Engine FQDN: {fqdn}".format(fqdn=self.engine_fqdn))
+
+ self.engine_url = "https://{e}".format(e=engine_fqdn)
+ self.logger.info("Engine URL: {url}".format(url=self.engine_url))
+ self.check_fqdn = check_fqdn
+
+ self.fprint = fingerprint
+ self.logger.info("Fingerprint: {fp}".format(fp=self.fprint))
+
+ if node_fqdn is None:
+ self.node_fqdn = socket.gethostname()
+ else:
+ self.node_fqdn = node_fqdn
+ self.logger.info("Node FQDN: {nf}".format(nf=self.node_fqdn))
+
+ if node_name is None:
+ self.node_name = socket.gethostname().split(".")[0]
+ else:
+ self.node_name = node_name
+ self.logger.info("Node name: {na}".format(na=self.node_name))
+
+ if ssh_user is None:
+ self.ssh_user = getpass.getuser()
+ else:
+ self.ssh_user = ssh_user
+ self.logger.info("SSH User: {su}".format(su=self.ssh_user))
+
+ if ssh_port is None:
+ self.ssh_port = "22"
+ else:
+ self.ssh_port = ssh_port
+ self.logger.info("SSH Port: {sp}".format(sp=self.ssh_port))
+
+ if vdc_port is None:
+ self.vdc_port = "54321"
+ else:
+ self.vdc_port = vdc_port
+ self.logger.info("VDC Port: {sp}".format(sp=self.vdc_port))
+
+ if engine_http_port is None:
+ self.engine_port = "443"
+ else:
+ self.engine_port = engine_http_port
+ self.logger.info("Engine http port: {hp}".format(hp=self.engine_port))
+
+ self.ca_dir = "/etc/pki/ovirt-engine/"
+ self.ca_engine = "{d}{f}".format(d=self.ca_dir, f="ca.pem")
+ self.logger.info("Engine CA: {ca}".format(ca=self.ca_engine))
+
+ self.__get_protocol()
+
+ def __get_protocol(self):
+ """
+ Determine if Engine is running in registration
+ protocol version legacy or service
+ """
+
+ self.__print_and_log("Identifying the registration protocol...",
+ level="info")
+
+ ucmd = "/ovirt-engine/services/host-register?version=1&command="
+ __GET_VERSION = "https://{e}{u}{c}".format(e=self.engine_fqdn,
+ u=ucmd,
+ c="get-version")
+
+ try:
+ res = requests.get(__GET_VERSION, verify=False)
+ except Exception as e:
+ self.__print_and_log(e, level="error")
+ raise e
+
+ if res.status_code != 200:
+ self.reg_protocol = "legacy"
+ self.url_CA = self.engine_url
+
+ self.url_ssh_key = "{e}{k}".format(e=self.engine_url,
+ k="/engine.ssh.key.txt")
+
+ ureg = "/OvirtEngineWeb/register?vds_ip={fqdn}" \
+ "&vds_name={name}&port={mp}".format(fqdn=self.node_fqdn,
+ name=self.node_name,
+ mp=self.vdc_port)
+
+ self.url_reg = "{e}{u}".format(e=self.engine_url, u=ureg)
+ else:
+ self.reg_protocol = "service"
+
+ self.url_CA = "{e}{uc}{c}".format(e=self.engine_url,
+ uc=ucmd,
+ c="get-pki-trust")
+
+ self.url_ssh_key = "{e}{uc}{c}".format(e=self.engine_url,
+ uc=ucmd,
+ c="get-ssh-trust")
+
+ ureg = "{uc}register&name={name}&address={fqdn}&sshUser={sshu}&" \
+ "sshPort={sshp}&port={mp}".format(uc=ucmd,
+ name=self.node_name,
+ fqdn=self.node_fqdn,
+ sshu=self.ssh_user,
+ sshp=self.ssh_port,
+ mp=self.vdc_port)
+
+ self.url_reg = "{e}{u}".format(e=self.engine_url, u=ureg)
+
+ self.__print_and_log("Registration procotol selected: {p}".format(
+ p=self.reg_protocol), level="info")
+
+ self.logger.info("Download CA via: {u}".format(u=self.url_CA))
+ self.logger.info("Download SSH via: {u}".format(u=self.url_ssh_key))
+
+ def __print_and_log(self, msg, level):
+ """
+ Print and log a message
+ """
+ print(msg)
+
+ if level == "error":
+ self.logger.error(msg, exc_info=True)
+ elif level == "debug":
+ self.logger.debug(msg)
+ elif level == "warning":
+ self.logger.warning(msg)
+ elif level == "critical":
+ self.logger.critical(msg)
+ elif level == "info":
+ self.logger.info(msg)
+
+ def __set_logger(self):
+ """
+ The logging settings
+ Saving log in: /var/log/vdsm/register.log
+ """
+ # Avoid node side effects of logging module not been working
+ logging.shutdown()
+ reload(logging)
+
+ logging.basicConfig(
+ filename="/var/log/vdsm/register.log",
+ level=logging.INFO,
+ format='%(asctime)s %(message)s',
+ datefmt='%m/%d/%Y %I:%M:%S %p',
+ )
+
+ if sys.version_info >= (2, 7):
+ logging.captureWarnings(True)
+
+ return logging.getLogger(__name__)
+
+ def __execute_http_request(self, url, cert_validation=True):
+ """
+ Execute http requests
+ url -- URL to be requested
+ cert_validation -- SSL cert will be verified
+
+ Returns: Content of http request
+ """
+ if cert_validation and self.check_fqdn:
+ cert_validation = self.ca_engine
+ else:
+ cert_validation = False
+
+ try:
+ res = requests.get("{u}".format(u=url), verify=cert_validation)
+ if res.status_code != 200:
+ self.logger.error("http response was not OK", exc_info=True)
+ raise requests.RequestException
+ except requests.RequestException as e:
+ self.logger.error("Cannot connect to engine", exc_info=True)
+ raise e
+
+ return res.content
+
+ def __silent_restorecon(self, path):
+ """
+ Execute selinux restorecon cmd to determined file
+
+ Args
+ path -- full path to file
+ """
+
+ try:
+ if selinux.is_selinux_enabled():
+ selinux.restorecon(path)
+ except:
+ self.__print_and_log("restorecon %s failed" % path)
+
+ def __calculate_fingerprint(self, cert):
+ """
+ Calculate fingerprint of certificate
+
+ Args
+ cert -- certificate file to be calculated the fingerprint
+
+ Returns
+ The fingerprint
+ """
+
+ with open(cert, 'r') as f:
+ cert = f.read()
+
+ x509 = M2Crypto.X509.load_cert_string(cert, M2Crypto.X509.FORMAT_PEM)
+ fp = x509.get_fingerprint('sha1')
+ fp = ':'.join(fp[pos:pos + 2] for pos in range(0, len(fp), 2))
+
+ return fp
+
+ def download_ca(self):
+ """
+ Download CA from Engine and save self.ca_engine
+ """
+ if self.reg_protocol == "legacy":
+ # The legacy version uses the format: UUID_MACADDRESS
+ self.uuid = getHostUUID(legacy=True)
+ self.url_reg += "&vds_unique_id={u}".format(u=self.uuid)
+ else:
+ # Non legacy version uses the format: UUID
+ self.uuid = getHostUUID(legacy=False)
+ self.url_reg += "&uniqueId={u}".format(u=self.uuid)
+
+ self.logger.info("Registration via: {u}".format(u=self.url_reg))
+
+ __VDSM_ID = "/etc/vdsm/vdsm.id"
+ if not os.path.exists(__VDSM_ID):
+ with open(__VDSM_ID, 'w') as f:
+ f.write(self.uuid)
+
+ if utils.isOvirtNode():
+ from ovirt.node.utils.fs import Config
+ Config().persist(__VDSM_ID)
+
+ self.__print_and_log("Host UUID: {u}".format(u=self.uuid),
+ level="info")
+
+ self.__print_and_log("Collecting CA data from Engine...",
+ level="info")
+
+ # If engine CA dir doesnt exist create it and download the ca.pem
+ temp_ca_file = None
+ if os.path.exists(self.ca_engine):
+ calculated_fprint = self.__calculate_fingerprint(self.ca_engine)
+ else:
+ if not os.path.exists(self.ca_dir):
+ os.makedirs(self.ca_dir, 0o755)
+ self.__silent_restorecon(self.ca_dir)
+ if utils.isOvirtNode():
+ from ovirt.node.utils.fs import Config
+ Config().persist(self.ca_dir)
+
+ if self.reg_protocol == "legacy":
+ res = ssl.get_server_certificate(
+ (self.engine_fqdn, int(self.engine_port))
+ )
+ else:
+ res = self.__execute_http_request(self.url_CA,
+ cert_validation=False)
+
+ with tempfile.NamedTemporaryFile(
+ dir=os.path.dirname(self.ca_dir),
+ delete=False
+ ) as f:
+ f.write(res)
+
+ calculated_fprint = self.__calculate_fingerprint(f.name)
+ temp_ca_file = True
+
+ if self.fprint and self.fprint != calculated_fprint:
+ msg = "The fingeprints doesn't match:\n" \
+ "Calculated fingerprint: [{c}]\n" \
+ "Attribute fingerprint: [{a}]".format(c=calculated_fprint,
+ a=self.fprint)
+
+ self.__print_and_log(msg, "error")
+ if temp_ca_file:
+ os.unlink(f.name)
+ raise RuntimeError(msg)
+
+ if temp_ca_file:
+ os.rename(f.name, self.ca_engine)
+
+ self.fprint = calculated_fprint
+ self.__print_and_log("Calculated fingerprint: {f}".format(
+ f=self.fprint), level="info")
+
+ if utils.isOvirtNode():
+ from ovirt.node.utils.fs import Config
+ Config().persist(self.ca_engine)
+
+ def download_ssh(self):
+ """
+ Download ssh authorized keys and save it in the node
+ """
+ _auth_keys_dir = pwd.getpwuid(pwd.getpwnam(
+ self.ssh_user).pw_uid).pw_dir + "/.ssh"
+ _auth_keys = _auth_keys_dir + "/authorized_keys"
+
+ if not os.path.exists(_auth_keys_dir):
+ os.makedirs(_auth_keys_dir, 0o700)
+ self.__silent_restorecon(_auth_keys_dir)
+ if utils.isOvirtNode():
+ from ovirt.node.utils.fs import Config
+ Config().persist(_auth_keys_dir)
+
+ res = self.__execute_http_request(self.url_ssh_key)
+ with tempfile.NamedTemporaryFile(
+ dir=_auth_keys_dir,
+ delete=False
+ ) as f:
+ f.write(res)
+
+ # If ssh key is new append it into autorized_keys
+ with open(f.name, "r") as f_ro:
+ content = f_ro.read()
+ with open(_auth_keys, "a+") as f_w:
+ if content not in f_w.read():
+ f_w.write(content)
+ os.chmod(_auth_keys, 0o600)
+ self.__silent_restorecon(_auth_keys)
+
+ os.unlink(f.name)
+ if utils.isOvirtNode():
+ from ovirt.node.utils.fs import Config
+ Config().persist(_auth_keys)
+
+ def execute_registration(self):
+ """
+ Trigger the registration command against Engine
+ """
+ self.__execute_http_request(self.url_reg)
+ self.__print_and_log("Registration completed, host is pending approval"
+ " on Engine: {e}".format(e=self.engine_fqdn),
+ level="info")
+
+ def get_fingerprint(self):
+ """
+ Return the fingerprint for validation
+ """
+ return self.fprint
+
+
+ at expose('register')
+def main(*args):
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawTextHelpFormatter,
+ description='Tool to register node to Engine',
+ epilog='Example of use:\n%(prog)s '
+ '--engine-fqdn engine.mydomain'
+ )
+
+ # Needed by vdsm-tool
+ parser.add_argument(
+ 'register',
+ )
+
+ parser.add_argument(
+ '--node-fqdn',
+ help="Define node FQDN or IP address."
+ " If not provided, will be used system host name",
+ )
+
+ parser.add_argument(
+ '--node-name',
+ help="Define node name."
+ " If not provided, will be used system short host name"
+ " (the name before the first dot in the system host name)",
+ )
+
+ parser.add_argument(
+ '--engine-fqdn',
+ help="Engine FQDN (See also: --check-fqdn)",
+ required=True
+ )
+
+ parser.add_argument(
+ '--ssh-user',
+ help="SSH username to establish the connection with Engine. "
+ "If not provided, the user which is "
+ "executing the script will catch and used",
+ )
+
+ parser.add_argument(
+ '--ssh-port',
+ help="SSH port to establish the connection with Engine "
+ "If not provided, the script will use the default "
+ "SSH port 22"
+ )
+
+ parser.add_argument(
+ '--check-fqdn',
+ help="Disable or Enable FQDN check for Engine CA, this option "
+ "is enabled by default (Use: True or False)",
+ )
+
+ parser.add_argument(
+ '--fingerprint',
+ help="Specify an existing fingerprint to be validated against "
+ "Engine CA fingerprint",
+ )
+
+ args = parser.parse_args()
+
+ reg = Register(engine_fqdn=args.engine_fqdn,
+ node_fqdn=args.node_fqdn,
+ node_name=args.node_name,
+ ssh_user=args.ssh_user,
+ ssh_port=args.ssh_port,
+ fingerprint=args.fingerprint,
+ check_fqdn=args.check_fqdn)
+
+ reg.download_ca()
+ reg.download_ssh()
+ reg.execute_registration()
+
+if __name__ == '__main__':
+ main()
+
+"""
+Registration schema:
+
+UUID
+=========
+ - If there is UUID already generated for the system will be
+ available in /etc/vdsm/vdsm.id
+
+ - In case, there is no UUID, use auxiliary function from VDSM
+ to generate it and store in /etc/vdsm/vdsm.id
+
+Legacy reg:
+============
+ - Process UUID
+
+ - Download CA via
+ https://ENGINE_FQDN
+
+ - Download ssh pub key
+ https://ENGINE_FQDN/engine.ssh.key.txt
+
+ - Register via URL:
+ (Original .NET version and earlier Linux versions)
+ https://ENGINE_FQDN/RHEVManagerWeb/VdsAutoRegistration.aspx?vds_ip=NODE_FQDN_OR_IP&vds_name=NODE_NAME&vds_unique_id=NODE_UUID&port=54321
+
+ or
+
+ https://ENGINE_FQDN/OvirtEngineWeb/register?vds_ip=NODE_FQDN_OR_IP&vds_name=NODE_NAME&vds_unique_id=NODE_UUID&port=54321
+
+Service reg:
+============
+ - Process UUID
+
+ - Download CA via get-pki-trust URL
+ https://ENGINE_FQDN/ovirt-engine/services/host-register?version=1&command=get-pki-trust
+
+ - Download ssh pub key via get-ssh-trust URL
+ https://ENGINE_FQDN/ovirt-engine/services/host-register?version=1&command=get-ssh-trust
+
+ - Register via URL:
+ https://ENGINE_FQDN/ovirt-engine/services/host-register?version=1&command=register&name=NODE_NAME&address=NO_FQDN_OR_IP&uniqueId=NODE_UUID&sshUser=SSH_USERNAME&sshPort=SSHD_PORT
+"""
diff --git a/vdsm.spec.in b/vdsm.spec.in
index 52a8b37..108dff6 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -1372,6 +1372,7 @@
%{python_sitelib}/%{vdsm_name}/tool/configurators/sebool.py*
%{python_sitelib}/%{vdsm_name}/tool/configurators/multipath.py*
%{python_sitelib}/%{vdsm_name}/tool/dump_volume_chains.py*
+%{python_sitelib}/%{vdsm_name}/tool/register.py*
%{python_sitelib}/%{vdsm_name}/tool/restore_nets.py*
%{python_sitelib}/%{vdsm_name}/tool/service.py*
%{python_sitelib}/%{vdsm_name}/tool/transient.py*
--
To view, visit https://gerrit.ovirt.org/40966
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ica800027beec1e5a20165bb5e1e78baf9283c1da
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Douglas Schilling Landgraf <dougsland at redhat.com>
More information about the vdsm-patches
mailing list