Commit 2a7c3583 authored by Michael Hanselmann's avatar Michael Hanselmann

RAPI client: Switch to pycURL

Currently the RAPI client uses the urllib2 and httplib modules from
Python's standard library. They're used with pyOpenSSL in a very fragile
way, and there are known issues when receiving large responses from a RAPI
server.

By switching to PycURL we leverage the power and stability of the
widely-used curl library (libcurl). This brings us much more flexibility
than before, and timeouts were easily implemented (something that would
have involved a lot of work with the built-in modules).

There's one small drawback: Programs using libcurl have to call
curl_global_init(3) (available as pycurl.global_init) while exactly one
thread is running (e.g. before other threads) and are supposed to call
curl_global_cleanup(3) (available as pycurl.global_cleanup) upon exiting.
See the manpages for details. A decorator is provided to simplify this.

Unittests for the new code are provided, increasing the test coverage of
the RAPI client from 74% to 89%.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarGuido Trotter <ultrotter@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent b939de46
......@@ -29,6 +29,7 @@ Before installing, please verify that you have the following programs:
- `simplejson Python module <http://code.google.com/p/simplejson/>`_
- `pyparsing Python module <http://pyparsing.wikispaces.com/>`_
- `pyinotify Python module <http://trac.dbzteam.org/pyinotify/>`_
- `PycURL Python module <http://pycurl.sourceforge.net/>`_
- `socat <http://www.dest-unreach.org/socat/>`_
These programs are supplied as part of most Linux distributions, so
......@@ -39,7 +40,8 @@ packages, except for DRBD and Xen::
$ apt-get install lvm2 ssh bridge-utils iproute iputils-arping \
python python-pyopenssl openssl python-pyparsing \
python-simplejson python-pyinotify socat
python-simplejson python-pyinotify python-pycurl \
socat
If you want to build from source, please see doc/devnotes.rst for more
dependencies.
......
......@@ -610,10 +610,9 @@ def IsRapiResponding(hostname):
@return: Whether RAPI is working properly
"""
ssl_config = rapi.client.CertAuthorityVerify(constants.RAPI_CERT_FILE)
rapi_client = \
rapi.client.GanetiRapiClient(hostname,
config_ssl_verification=ssl_config)
curl_config = rapi.client.GenericCurlConfig(cafile=constants.RAPI_CERT_FILE)
rapi_client = rapi.client.GanetiRapiClient(hostname,
curl_config_fn=curl_config)
try:
master_version = rapi_client.GetVersion()
except rapi.client.CertificateError, err:
......@@ -646,6 +645,7 @@ def ParseOptions():
return options, args
@rapi.client.UsesRapiClient
def main():
"""Main function.
......
This diff is collapsed.
......@@ -39,6 +39,9 @@ import qa_tags
import qa_utils
from ganeti import utils
from ganeti import rapi
import ganeti.rapi.client
def RunTest(fn, *args):
......@@ -269,6 +272,7 @@ def RunHardwareFailureTests(instance, pnode, snode):
instance, pnode, snode)
@rapi.client.UsesRapiClient
def main():
"""Main program.
......
......@@ -72,13 +72,13 @@ def Setup(username, password):
_rapi_ca.flush()
port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
cfg_ssl = rapi.client.CertAuthorityVerify(cafile=_rapi_ca.name)
cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
proxy="")
_rapi_client = rapi.client.GanetiRapiClient(master["primary"], port=port,
username=username,
password=password,
config_ssl_verification=cfg_ssl,
ignore_proxy=True)
curl_config_fn=cfg_curl)
print "RAPI protocol version: %s" % _rapi_client.GetVersion()
......
This diff is collapsed.
......@@ -148,18 +148,18 @@ class RapiClientFactory:
self.src_cluster_name = src_cluster_name
self.dest_cluster_name = dest_cluster_name
# TODO: Implement timeouts for RAPI connections
# TODO: Support for using system default paths for verifying SSL certificate
# (already implemented in CertAuthorityVerify)
logging.debug("Using '%s' as source CA", options.src_ca_file)
src_ssl_config = rapi.client.CertAuthorityVerify(cafile=options.src_ca_file)
src_curl_config = rapi.client.GenericCurlConfig(cafile=options.src_ca_file)
if options.dest_ca_file:
logging.debug("Using '%s' as destination CA", options.dest_ca_file)
dest_ssl_config = \
rapi.client.CertAuthorityVerify(cafile=options.dest_ca_file)
dest_curl_config = \
rapi.client.GenericCurlConfig(cafile=options.dest_ca_file)
else:
logging.debug("Using source CA for destination")
dest_ssl_config = src_ssl_config
dest_curl_config = src_curl_config
logging.debug("Source RAPI server is %s:%s",
src_cluster_name, options.src_rapi_port)
......@@ -182,7 +182,7 @@ class RapiClientFactory:
self.GetSourceClient = lambda: \
rapi.client.GanetiRapiClient(src_cluster_name,
port=options.src_rapi_port,
config_ssl_verification=src_ssl_config,
curl_config_fn=src_curl_config,
username=src_username,
password=src_password)
......@@ -212,7 +212,7 @@ class RapiClientFactory:
self.GetDestClient = lambda: \
rapi.client.GanetiRapiClient(dest_cluster_name,
port=dest_rapi_port,
config_ssl_verification=dest_ssl_config,
curl_config_fn=dest_curl_config,
username=dest_username,
password=dest_password)
......@@ -771,6 +771,7 @@ def CheckOptions(parser, options, args):
return (src_cluster_name, dest_cluster_name, instance_names)
@rapi.client.UsesRapiClient
def main():
"""Main routine.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment