Commit bff5c82f authored by Julien Muchembled's avatar Julien Muchembled

Add SSL support

parent 107ca7df
...@@ -40,23 +40,12 @@ There's also a tool to convert back to FileStorage. ...@@ -40,23 +40,12 @@ There's also a tool to convert back to FileStorage.
See also http://www.neoppod.org/links for more detailed information about See also http://www.neoppod.org/links for more detailed information about
features related to scalability. features related to scalability.
Disclaimer
==========
In addition of the disclaimer contained in the licence this code is
released under, please consider the following.
NEO does not implement any authentication mechanism between its nodes, and
does not encrypt data exchanged between nodes either.
If you want to protect your cluster from malicious nodes, or your data from
being snooped, please consider encrypted tunelling (such as openvpn).
Requirements Requirements
============ ============
- Linux 2.6 or later - Linux 2.6 or later
- Python 2.7.x - Python 2.7.x (2.7.9 or later for SSL support)
- For storage nodes using MySQL backend: - For storage nodes using MySQL backend:
...@@ -166,6 +155,24 @@ Packs are currently not replicated, which means packing should always be done ...@@ -166,6 +155,24 @@ Packs are currently not replicated, which means packing should always be done
up to a TID that is already fully replicated, so that the backup cluster has a up to a TID that is already fully replicated, so that the backup cluster has a
full history (and not random holes). full history (and not random holes).
SSL support
-----------
In addition to any external solution like OpenVPN, NEO has builtin SSL support
to authenticate and encrypt communications between nodes.
All commands and configuration files have options to specify a CA certificate,
the node certificate and the node private key. A certificate can be shared
by several nodes.
NEO always uses the latest SSL protocol supported by the Python interpreter,
without fallback to older versions. A "SSL: WRONG_VERSION_NUMBER" error means
that a node runs in an older environment (Python + OpenSSL) than others.
Note also that you can't mix non-SSL nodes and SSL nodes, even between a
upstream cluster and a backup one. In doing so, connections can get stuck,
or fail with malformed packets or SSL handshake errors.
Deployment Deployment
========== ==========
......
...@@ -168,6 +168,3 @@ ...@@ -168,6 +168,3 @@
- Investigate delta compression for stored data - Investigate delta compression for stored data
Idea would be to have a few most recent revisions being stored fully, and Idea would be to have a few most recent revisions being stored fully, and
older revision delta-compressed, in order to save space. older revision delta-compressed, in order to save space.
- Consider using multicast for cluster-wide notifications. (BANDWITH)
Currently, multi-receivers notifications are sent in unicast to each
receiver. Multicast should be used.
...@@ -43,6 +43,13 @@ partitions: 12 ...@@ -43,6 +43,13 @@ partitions: 12
;upstream_cluster: test2 ;upstream_cluster: test2
;upstream_masters: 127.0.0.1:30000 ;upstream_masters: 127.0.0.1:30000
# The 3 following options must be specified to enabled SSL.
# CA should be the same for all nodes, and it can be the concatenation of
# several CAs and CRLs.
;ca = ~/etc/ca.crt
;cert = ~/etc/cert.crt
;key = ~/etc/cert.key
# Individual nodes parameters # Individual nodes parameters
# Some parameters makes no sense to be defined in [DEFAULT] section. # Some parameters makes no sense to be defined in [DEFAULT] section.
# They are: # They are:
...@@ -60,6 +67,10 @@ bind: 127.0.0.1:9999 ...@@ -60,6 +67,10 @@ bind: 127.0.0.1:9999
# common section. # common section.
;logfile: ~/log/admin.log ;logfile: ~/log/admin.log
# Nodes can have their own certicates.
;cert = admin.crt
;key = admin.key
# Master nodes # Master nodes
[master] [master]
bind: 127.0.0.1:10000 bind: 127.0.0.1:10000
......
...@@ -30,7 +30,8 @@ class Application(BaseApplication): ...@@ -30,7 +30,8 @@ class Application(BaseApplication):
"""The storage node application.""" """The storage node application."""
def __init__(self, config): def __init__(self, config):
super(Application, self).__init__(config.getDynamicMasterList()) super(Application, self).__init__(
config.getSSL(), config.getDynamicMasterList())
for address in config.getMasters(): for address in config.getMasters():
self.nm.createMaster(address=address) self.nm.createMaster(address=address)
......
...@@ -65,7 +65,9 @@ class Storage(BaseStorage.BaseStorage, ...@@ -65,7 +65,9 @@ class Storage(BaseStorage.BaseStorage,
): ):
setattr(self, method_id, raiseReadOnlyError) setattr(self, method_id, raiseReadOnlyError)
if _app is None: if _app is None:
_app = Application(master_nodes, name, compress=compress, **kw) ssl = [kw.pop(x, None) for x in ('ca', 'cert', 'key')]
_app = Application(master_nodes, name, compress=compress,
ssl=ssl if any(ssl) else None, **kw)
self.app = _app self.app = _app
@property @property
......
...@@ -39,5 +39,20 @@ ...@@ -39,5 +39,20 @@
be added/removed without requiring a config change each time. be added/removed without requiring a config change each time.
</description> </description>
</key> </key>
<key name="ca" datatype="existing-file">
<description>
Certificate authority in PEM format.
</description>
</key>
<key name="cert" datatype="existing-file">
<description>
Certificate in PEM format.
</description>
</key>
<key name="key" datatype="existing-file">
<description>
Private key in PEM format.
</description>
</key>
</sectiontype> </sectiontype>
</component> </component>
...@@ -14,13 +14,38 @@ ...@@ -14,13 +14,38 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from . import logging
from .event import EventManager from .event import EventManager
from .node import NodeManager from .node import NodeManager
class BaseApplication(object): class BaseApplication(object):
def __init__(self, dynamic_master_list=None): ssl = None
def __init__(self, ssl=None, dynamic_master_list=None):
if ssl:
if not all(ssl):
raise ValueError("To enable encryption, 3 files must be"
" provided: the CA certificate, and the certificate"
" of this node with its private key.")
ca, cert, key = ssl
import ssl
version, version_name = max((getattr(ssl, k), k)
for k in dir(ssl) if k.startswith("PROTOCOL_TLSv"))
self.ssl = context = ssl.SSLContext(version)
context.options |= (0
| ssl.OP_CIPHER_SERVER_PREFERENCE
| ssl.OP_NO_COMPRESSION
)
context.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations(ca)
context.load_cert_chain(cert, key)
context.verify_flags |= ssl.VERIFY_X509_STRICT | (
context.cert_store_stats()['crl'] and ssl.VERIFY_CRL_CHECK_LEAF)
logging.info("TLS %s enabled for %s",
float(version_name[13:].replace("_", ".")), self)
self._handlers = {} self._handlers = {}
self.em = EventManager() self.em = EventManager()
self.nm = NodeManager(dynamic_master_list) self.nm = NodeManager(dynamic_master_list)
......
...@@ -25,6 +25,9 @@ def getOptionParser(): ...@@ -25,6 +25,9 @@ def getOptionParser():
parser = OptionParser() parser = OptionParser()
parser.add_option('-l', '--logfile', parser.add_option('-l', '--logfile',
help='log debugging information to specified SQLite DB') help='log debugging information to specified SQLite DB')
parser.add_option('--ca', help='certificate authority in PEM format')
parser.add_option('--cert', help='certificate in PEM format')
parser.add_option('--key', help='private key in PEM format')
return parser return parser
def getServerOptionParser(): def getServerOptionParser():
...@@ -80,6 +83,11 @@ class ConfigurationManager(object): ...@@ -80,6 +83,11 @@ class ConfigurationManager(object):
def getLogfile(self): def getLogfile(self):
return self.__getPath('logfile', True) return self.__getPath('logfile', True)
def getSSL(self):
r = [self.__getPath(key, True) for key in ('ca', 'cert', 'key')]
if any(r):
return r
def getMasters(self): def getMasters(self):
""" Get the master node list except itself """ """ Get the master node list except itself """
masters = self.__get('masters') masters = self.__get('masters')
......
...@@ -299,6 +299,7 @@ class ListeningConnection(BaseConnection): ...@@ -299,6 +299,7 @@ class ListeningConnection(BaseConnection):
"""A listen connection.""" """A listen connection."""
def __init__(self, app, handler, addr): def __init__(self, app, handler, addr):
self._ssl = app.ssl
logging.debug('listening to %s:%d', *addr) logging.debug('listening to %s:%d', *addr)
connector = self.ConnectorClass(addr) connector = self.ConnectorClass(addr)
BaseConnection.__init__(self, app.em, handler, connector, addr) BaseConnection.__init__(self, app.em, handler, connector, addr)
...@@ -310,6 +311,9 @@ class ListeningConnection(BaseConnection): ...@@ -310,6 +311,9 @@ class ListeningConnection(BaseConnection):
logging.debug('accepted a connection from %s:%d', *addr) logging.debug('accepted a connection from %s:%d', *addr)
handler = self.getHandler() handler = self.getHandler()
new_conn = ServerConnection(self.em, handler, connector, addr) new_conn = ServerConnection(self.em, handler, connector, addr)
if self._ssl:
connector.ssl(self._ssl)
self.em.addWriter(new_conn)
handler.connectionAccepted(new_conn) handler.connectionAccepted(new_conn)
def getAddress(self): def getAddress(self):
...@@ -580,6 +584,7 @@ class ClientConnection(Connection): ...@@ -580,6 +584,7 @@ class ClientConnection(Connection):
client = True client = True
def __init__(self, app, handler, node): def __init__(self, app, handler, node):
self._ssl = app.ssl
addr = node.getAddress() addr = node.getAddress()
connector = self.ConnectorClass(addr) connector = self.ConnectorClass(addr)
Connection.__init__(self, app.em, handler, connector, addr) Connection.__init__(self, app.em, handler, connector, addr)
...@@ -619,6 +624,8 @@ class ClientConnection(Connection): ...@@ -619,6 +624,8 @@ class ClientConnection(Connection):
self.writable() self.writable()
def _connectionCompleted(self): def _connectionCompleted(self):
if self._ssl:
self.connector.ssl(self._ssl)
self.writable = self.lockWrapper(super(ClientConnection, self).writable) self.writable = self.lockWrapper(super(ClientConnection, self).writable)
self.connecting = False self.connecting = False
self.updateTimeout(time()) self.updateTimeout(time())
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import socket import socket
import ssl
import errno import errno
from time import time from time import time
from . import logging from . import logging
...@@ -109,6 +110,14 @@ class SocketConnector(object): ...@@ -109,6 +110,14 @@ class SocketConnector(object):
self.socket.close() self.socket.close()
self._error('listen', e) self._error('listen', e)
def ssl(self, ssl):
self.socket = ssl.wrap_socket(self.socket,
server_side=self.is_server,
do_handshake_on_connect=False,
suppress_ragged_eofs=False)
self.__class__ = self.SSLHandshakeConnectorClass
self.queued or self.queued.append('')
def getError(self): def getError(self):
return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
...@@ -212,6 +221,62 @@ class SocketConnectorIPv6(SocketConnector): ...@@ -212,6 +221,62 @@ class SocketConnectorIPv6(SocketConnector):
registerConnectorHandler(SocketConnectorIPv4) registerConnectorHandler(SocketConnectorIPv4)
registerConnectorHandler(SocketConnectorIPv6) registerConnectorHandler(SocketConnectorIPv6)
def overlay_connector_class(cls):
name = cls.__name__[1:]
alias = name + 'ConnectorClass'
for base in connector_registry.itervalues():
setattr(base, alias, type(name + base.__name__,
cls.__bases__ + (base,), cls.__dict__))
return cls
@overlay_connector_class
class _SSL:
def _error(self, op, exc):
if isinstance(exc, ssl.SSLError):
logging.debug("%s failed for %s: %s", op, self, exc)
raise ConnectorException
SocketConnector._error(self, op, exc)
def receive(self, read_buf):
try:
while 1:
read_buf.append(self.socket.recv(4096))
except ssl.SSLWantReadError:
pass
except socket.error, e:
self._error('recv', e)
@overlay_connector_class
class _SSLHandshake(_SSL):
def receive(self, read_buf=None):
# ???Writer | send | receive
# -----------+--------+--------
# want read | remove | -
# want write | - | add
try:
self.socket.do_handshake()
except ssl.SSLWantReadError:
return read_buf is None
except ssl.SSLWantWriteError:
return read_buf is not None
except socket.error, e:
self._error('SSL handshake', e)
if not self.queued[0]:
del self.queued[0]
self.__class__ = self.SSLConnectorClass
cipher, proto, bits = self.socket.cipher()
logging.debug("SSL handshake done for %s: %s %s", self, cipher, bits)
if read_buf is None:
return self.send()
self.receive(read_buf)
return self.queued
send = receive
class ConnectorException(Exception): class ConnectorException(Exception):
pass pass
......
...@@ -47,7 +47,8 @@ class Application(BaseApplication): ...@@ -47,7 +47,8 @@ class Application(BaseApplication):
uuid = None uuid = None
def __init__(self, config): def __init__(self, config):
super(Application, self).__init__(config.getDynamicMasterList()) super(Application, self).__init__(
config.getSSL(), config.getDynamicMasterList())
self.tm = TransactionManager(self.onTransactionCommitted) self.tm = TransactionManager(self.onTransactionCommitted)
self.name = config.getCluster() self.name = config.getCluster()
......
...@@ -74,6 +74,7 @@ class BackupApplication(object): ...@@ -74,6 +74,7 @@ class BackupApplication(object):
self.nm.createMaster(address=master_address) self.nm.createMaster(address=master_address)
em = property(lambda self: self.app.em) em = property(lambda self: self.app.em)
ssl = property(lambda self: self.app.ssl)
def close(self): def close(self):
self.nm.close() self.nm.close()
......
...@@ -44,8 +44,8 @@ uuid_int = (lambda ns: lambda uuid: ...@@ -44,8 +44,8 @@ uuid_int = (lambda ns: lambda uuid:
)({str(k)[0]: v for k, v in UUID_NAMESPACES.iteritems()}) )({str(k)[0]: v for k, v in UUID_NAMESPACES.iteritems()})
class TerminalNeoCTL(object): class TerminalNeoCTL(object):
def __init__(self, address): def __init__(self, *args, **kw):
self.neoctl = NeoCTL(address) self.neoctl = NeoCTL(*args, **kw)
def __del__(self): def __del__(self):
self.neoctl.close() self.neoctl.close()
...@@ -231,8 +231,8 @@ class TerminalNeoCTL(object): ...@@ -231,8 +231,8 @@ class TerminalNeoCTL(object):
class Application(object): class Application(object):
"""The storage node application.""" """The storage node application."""
def __init__(self, address): def __init__(self, *args, **kw):
self.neoctl = TerminalNeoCTL(address) self.neoctl = TerminalNeoCTL(*args, **kw)
def execute(self, args): def execute(self, args):
"""Execute the command given.""" """Execute the command given."""
......
...@@ -27,8 +27,8 @@ class NeoCTL(BaseApplication): ...@@ -27,8 +27,8 @@ class NeoCTL(BaseApplication):
connection = None connection = None
connected = False connected = False
def __init__(self, address): def __init__(self, address, **kw):
super(NeoCTL, self).__init__() super(NeoCTL, self).__init__(**kw)
self.server = self.nm.createAdmin(address=address) self.server = self.nm.createAdmin(address=address)
self.handler = CommandEventHandler(self) self.handler = CommandEventHandler(self)
self.response_queue = [] self.response_queue = []
......
...@@ -42,5 +42,6 @@ def main(args=None): ...@@ -42,5 +42,6 @@ def main(args=None):
logging.setup(options.logfile) logging.setup(options.logfile)
from neo.neoctl.app import Application from neo.neoctl.app import Application
print Application(address).execute(args) ssl = options.ca, options.cert, options.key
print Application(address, ssl=ssl if any(ssl) else None).execute(args)
...@@ -66,6 +66,7 @@ UNIT_TEST_MODULES = [ ...@@ -66,6 +66,7 @@ UNIT_TEST_MODULES = [
'neo.tests.threaded.test', 'neo.tests.threaded.test',
'neo.tests.threaded.testImporter', 'neo.tests.threaded.testImporter',
'neo.tests.threaded.testReplication', 'neo.tests.threaded.testReplication',
'neo.tests.threaded.testSSL',
] ]
FUNC_TEST_MODULES = [ FUNC_TEST_MODULES = [
......
...@@ -41,7 +41,8 @@ class Application(BaseApplication): ...@@ -41,7 +41,8 @@ class Application(BaseApplication):
"""The storage node application.""" """The storage node application."""
def __init__(self, config): def __init__(self, config):
super(Application, self).__init__(config.getDynamicMasterList()) super(Application, self).__init__(
config.getSSL(), config.getDynamicMasterList())
# set the cluster name # set the cluster name
self.name = config.getCluster() self.name = config.getCluster()
......
...@@ -68,6 +68,9 @@ IP_VERSION_FORMAT_DICT = { ...@@ -68,6 +68,9 @@ IP_VERSION_FORMAT_DICT = {
ADDRESS_TYPE = socket.AF_INET ADDRESS_TYPE = socket.AF_INET
SSL = os.path.dirname(__file__) + os.sep
SSL = SSL + "ca.crt", SSL + "node.crt", SSL + "node.key"
logging.default_root_handler.handle = lambda record: None logging.default_root_handler.handle = lambda record: None
logging.backlog(None, 1<<20) logging.backlog(None, 1<<20)
......
-----BEGIN CERTIFICATE-----
MIIC7TCCAdWgAwIBAgIJAL8e44sA7PDMMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV
BAMMAkNBMB4XDTE1MDkzMDEzNTQzMFoXDTIxMDMyMjEzNTQzMFowDTELMAkGA1UE
AwwCQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgT7DVKM4ViQt3
B0oJC4RGi10dNfpNZZpgA5iC2UJ1u6AqqCf0PCQkrmuIzW3l1TenlOiLNdVASkkT
wf1lekIgg4tR8/22oGTAnfY6R9r1C6jAMV72v1sffz8D6qfkMPzKchJt55zywdhm
KscUsMGzXPGIeKrG20m83dSIO4RmCmq/f4BcuWJu6Kkq4n9Wc2IsvpKk+lqEUxI/
QoqdT6OvMXooGs3t892uvKDu++muBj2Y/yyaXt1tCCjDFsRMLWl3Skks+4PeMCZ4
wugyXEBk3d5Yzdv5NsFzFBjAuRCGxJXEOEcfHj4Xj9qTCErZ1jKzgnuxJCtgdqRC
r4beX1U3AgMBAAGjUDBOMB0GA1UdDgQWBBSFY/jKvo0iSTEzzOIcZZUZCT8JfTAf
BgNVHSMEGDAWgBSFY/jKvo0iSTEzzOIcZZUZCT8JfTAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBCwUAA4IBAQAB0LKDAuhodpyNVwEE9Yl+Q/IiPEPCaix6URJnRn1O
gQnXuZLo1xtJh6wJh1faG1/qNCFMxWEJ+0VkJ7r6v38cNXfYG9OcmD0S6YnNjSuO
VliAtqVVtj8MppJ4vMatLrNi4cvyYucebtNyBCzSIAi+6bkkHeaVgi1EtxXvq+AS
iZp3gl84oXv/gV7Bz4SXmVpFJnhsDMoQZG2KAULAgfZ2Am2I+ffG90cD/oEnS/3O
k3btqTvgIO8MWt8PY3sUOhJEoJYKnC9DppmhOhUTn4zzIIDSluKEOBHZiFb9AcmF
PvzL+8xiORCdUe1d6ANQQlUd0MM810BXZFYEXFbgKg8o
-----END CERTIFICATE-----
#!/bin/sh -e
DAYS=2000
at_exit() { rm -f "$CAkey"; }
trap at_exit 0
CAkey=`mktemp`
openssl req -new -x509 -nodes -keyout "$CAkey" -out ca.crt -subj /CN=CA -days $DAYS
openssl req -new -nodes -keyout node.key -subj /CN=node |
openssl x509 -CA ca.crt -CAkey "$CAkey" -req -out node.crt -set_serial 1 -days $DAYS
...@@ -38,7 +38,7 @@ from neo.lib.protocol import ClusterStates, NodeTypes, CellStates, NodeStates, \ ...@@ -38,7 +38,7 @@ from neo.lib.protocol import ClusterStates, NodeTypes, CellStates, NodeStates, \
UUID_NAMESPACES UUID_NAMESPACES
from neo.lib.util import dump from neo.lib.util import dump
from .. import cluster, DB_USER, setupMySQLdb, NeoTestBase, buildUrlFromString, \ from .. import cluster, DB_USER, setupMySQLdb, NeoTestBase, buildUrlFromString, \
ADDRESS_TYPE, IP_VERSION_FORMAT_DICT, getTempDirectory ADDRESS_TYPE, IP_VERSION_FORMAT_DICT, getTempDirectory, SSL
from neo.client.Storage import Storage from neo.client.Storage import Storage
from neo.storage.database import buildDatabaseManager from neo.storage.database import buildDatabaseManager
...@@ -223,6 +223,8 @@ class NEOProcess(object): ...@@ -223,6 +223,8 @@ class NEOProcess(object):
class NEOCluster(object): class NEOCluster(object):
SSL = None
def __init__(self, db_list, master_count=1, partitions=1, replicas=0, def __init__(self, db_list, master_count=1, partitions=1, replicas=0,
db_user=DB_USER, db_password='', name=None, db_user=DB_USER, db_password='', name=None,
cleanup_on_delete=False, temp_dir=None, clear_databases=True, cleanup_on_delete=False, temp_dir=None, clear_databases=True,
...@@ -289,7 +291,7 @@ class NEOCluster(object): ...@@ -289,7 +291,7 @@ class NEOCluster(object):
self._newProcess(NodeTypes.STORAGE, logger and 'storage_%u' % i, self._newProcess(NodeTypes.STORAGE, logger and 'storage_%u' % i,
0, adapter=adapter, database=self.db_template(db)) 0, adapter=adapter, database=self.db_template(db))
# create neoctl # create neoctl
self.neoctl = NeoCTL((self.local_ip, admin_port)) self.neoctl = NeoCTL((self.local_ip, admin_port), ssl=self.SSL)
def _newProcess(self, node_type, logfile=None, port=None, **kw): def _newProcess(self, node_type, logfile=None, port=None, **kw):
self.uuid_dict[node_type] = uuid = 1 + self.uuid_dict.get(node_type, 0) self.uuid_dict[node_type] = uuid = 1 + self.uuid_dict.get(node_type, 0)
...@@ -301,6 +303,8 @@ class NEOCluster(object): ...@@ -301,6 +303,8 @@ class NEOCluster(object):
kw['logfile'] = os.path.join(self.temp_dir, logfile + '.log') kw['logfile'] = os.path.join(self.temp_dir, logfile + '.log')
if port is not None: if port is not None:
kw['bind'] = '%s:%u' % (buildUrlFromString(self.local_ip), port) kw['bind'] = '%s:%u' % (buildUrlFromString(self.local_ip), port)
if self.SSL:
kw['ca'], kw['cert'], kw['key'] = self.SSL
self.process_dict.setdefault(node_type, []).append( self.process_dict.setdefault(node_type, []).append(
NEOProcess(command_dict[node_type], uuid, kw)) NEOProcess(command_dict[node_type], uuid, kw))
...@@ -402,6 +406,8 @@ class NEOCluster(object): ...@@ -402,6 +406,8 @@ class NEOCluster(object):
def getZODBStorage(self, **kw): def getZODBStorage(self, **kw):
master_nodes = self.master_nodes.replace('/', ' ') master_nodes = self.master_nodes.replace('/', ' ')
if self.SSL:
kw['ca'], kw['cert'], kw['key'] = self.SSL
result = Storage( result = Storage(
master_nodes=master_nodes, master_nodes=master_nodes,
name=self.cluster_name, name=self.cluster_name,
...@@ -633,6 +639,15 @@ class NEOCluster(object): ...@@ -633,6 +639,15 @@ class NEOCluster(object):
class NEOFunctionalTest(NeoTestBase): class NEOFunctionalTest(NeoTestBase):
def setUp(self):
if random.randint(0, 1):
NEOCluster.SSL = SSL
super(NEOFunctionalTest, self).setUp()
def _tearDown(self, success):
super(NEOFunctionalTest, self)._tearDown(success)
NEOCluster.SSL = None
def setupLog(self): def setupLog(self):
logging.setup(os.path.join(self.getTempDirectory(), 'test.log')) logging.setup(os.path.join(self.getTempDirectory(), 'test.log'))
......
-----BEGIN CERTIFICATE-----
MIICkDCCAXgCAQEwDQYJKoZIhvcNAQELBQAwDTELMAkGA1UEAwwCQ0EwHhcNMTUw
OTMwMTM1NDMwWhcNMjEwMzIyMTM1NDMwWjAPMQ0wCwYDVQQDDARub2RlMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuJ+ClJyjhJOJdGUyqHn79opMLP3m
1g27uBWKT+OGd4FcreVoRDxPVuxZxMtDCZcBfUHVvOoSlS06khwSxViEe1hxwHRa
n2qMWlwvaWeNY0CFH5V+DI4XSNojgny85Lb5jB69FuPcrHnwxLk2OFntrXEeNbEa
d7QSoNbPajbJIp5BS/WR9iu5Z5JYdumLWjTvOU+eZc4iA6Wa2kdDtbqkGi4wOJ1L
/ggATL+p+QFcubVptztPT8vq7gvDdGJgXLJ2lPHV0V/sdJB1FB4mSJEDDjSm2Hpp
qPVJSO1GrAy5Ld+0SnXIZZejhIUJIumocY08r+vzDSQ/8NnqXR4Odz1TWwIDAQAB
MA0GCSqGSIb3DQEBCwUAA4IBAQBlYkkInDDDcgnNRdUmzxwejs1PmEehZ3H5FkMp
TsmpoVC+oqM+QywMu8UJRtCjXnnJdAUbVYuZ1Tjm7qvFIhN+5OlIVxJ+8WcmZPSe
lj0N7Dv2nE1diTDS+qPZVPZ0demo1LafRmPomPWiM/CQRlMPxXnimuiYOROhWGn6
jsyoOwquMkAc6Ub++l4OCxLAP0eTgJFkivmqpaYZXG4o7zFvcQ3rQ66rQrMl69sR
8/MVqbT5Sq1CEJbepP4GaFfa5l3CVy7WH2MhCV1/9mNwcXafkTgx3q2HsPon4Dze
kNwiguNAM4L/j4dbIwz+CIVWcgpBCrfv2JYu+jGlRpxIDeWR
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4n4KUnKOEk4l0
ZTKoefv2ikws/ebWDbu4FYpP44Z3gVyt5WhEPE9W7FnEy0MJlwF9QdW86hKVLTqS
HBLFWIR7WHHAdFqfaoxaXC9pZ41jQIUflX4MjhdI2iOCfLzktvmMHr0W49ysefDE
uTY4We2tcR41sRp3tBKg1s9qNskinkFL9ZH2K7lnklh26YtaNO85T55lziIDpZra
R0O1uqQaLjA4nUv+CABMv6n5AVy5tWm3O09Py+ruC8N0YmBcsnaU8dXRX+x0kHUU
HiZIkQMONKbYemmo9UlI7UasDLkt37RKdchll6OEhQki6ahxjTyv6/MNJD/w2epd
Hg53PVNbAgMBAAECggEACCWh0YUIAjWwfx5oGd8oKzz3F5Usto1slzb8H4Je0K84
s8PH0hsHNULAw1pk3ut2+VwKXofFwid4yjHK8kJxti+09VUtGpPNFb+zp+cV6idS
uI4UPeGXTXOy1XNDsqQQZwqENZpghovrAANcTShKwLiZKK1kCZw8pjRUes9oGMrn
zLXvNPgpJbRUPPYFa34jJyaJtiBEgIBWBmAeEk4ccTYFk4Kon93Ljmobm/6H8WZE
bxomzJjFVMSjW/NmMWnalX5H7GegJVOtkOGvHf25dHBdbdU772ReY4PRZJEGwn8E
QcZjzaKkLB34IlbjWjH0nnanNa6DyjhulaqkHjAtIQKBgQDf3zPefVHss4FrajU8
Nvch6mpayOeJ3RzOAiMzmfI/O3zcJqqrlOOKQjvP5feAoLOJTcvu4hAMfUix/7LR
Qag5nALIllox6QnoMWmvKUbcekXunf/PUVFQ7SC3RJKtqh5oq5wRGJWcDPP6/Okk
t+64NeQOT3lPs2LNyy7KpB5YlQKBgQDTHln2fYKCLnJ66+3QPjDSJrwdUD6HFiwp
X5BfNUKpnxged+buFbgp5TB2vu6Z5A3AxwmWXBFiuvHLpl3uSAEIRbFQ+elagUXa
BoiPOnq1b00X/Vdkd3sX4czVemIG9DIA1uo31Gd9fFWMLHZGBOwqgNKiJOgQZXTq
dX/tAGrQLwKBgGt59aXf1j/j4cMWxx30aWq/5nVVNEtsetKwFgRE6RbQUV5DtfYP
0bljmOFzTwJSpD7LuZcisn+8efTyg/+QHNojevafsAd8EISHjGxKTbm1ffNTqSb3
rClE3kr9wclb/aNUl+VhPxoe4dbiKm+1WgbX4He6Ucwgm9OeswUYC3WNAoGAK1i/
/+wlL7V5q+NlIKykOYHafepL7FCRIK2OZv34gfs4aIkV0SyEc5WrLbZmJxK8ACjd
vxGIQE1B+B5gitwd2iT1Ezs8vmhsfyd4QnAvYbFIkvRhTS97BpxGAk7ucZ8R5To7
PNtPpGQy7GT0o8u+8bshhEkvnK44Iyuc6Hx9ceECgYAjM1jEreHzi96v8pmneJja
VIoNNu2PoTS1nTWN+/6//B62GxpKM5UCYZDRDC8urPXb6tXSC29I104yrNJQEGXz
/acVGnA6GJV639pXz++hf2NMHKandlKY+Cz2euZT/qUU80W+B5korsubIS6ATeee
+QU2yqNaC1ZvTO2L1OLYRg==
-----END PRIVATE KEY-----
...@@ -56,6 +56,7 @@ class ConnectionTests(NeoUnitTestBase): ...@@ -56,6 +56,7 @@ class ConnectionTests(NeoUnitTestBase):
def setUp(self): def setUp(self):
NeoUnitTestBase.setUp(self) NeoUnitTestBase.setUp(self)
self.app = Mock({'__repr__': 'Fake App'}) self.app = Mock({'__repr__': 'Fake App'})
self.app.ssl = None
self.em = self.app.em = Mock({'__repr__': 'Fake Em'}) self.em = self.app.em = Mock({'__repr__': 'Fake Em'})
self.handler = Mock({'__repr__': 'Fake Handler'}) self.handler = Mock({'__repr__': 'Fake Handler'})
self.address = ("127.0.0.7", 93413) self.address = ("127.0.0.7", 93413)
......
...@@ -502,6 +502,8 @@ class ConnectionFilter(object): ...@@ -502,6 +502,8 @@ class ConnectionFilter(object):
class NEOCluster(object): class NEOCluster(object):
SSL = None
def __init__(orig, self): # temporary definition for SimpleQueue patch def __init__(orig, self): # temporary definition for SimpleQueue patch
orig(self) orig(self)
lock = self._lock lock = self._lock
...@@ -563,7 +565,8 @@ class NEOCluster(object): ...@@ -563,7 +565,8 @@ class NEOCluster(object):
self.master_nodes = ' '.join('%s:%s' % x for x in master_list) self.master_nodes = ' '.join('%s:%s' % x for x in master_list)
weak_self = weakref.proxy(self) weak_self = weakref.proxy(self)
kw = dict(cluster=weak_self, getReplicas=replicas, getAdapter=adapter, kw = dict(cluster=weak_self, getReplicas=replicas, getAdapter=adapter,
getPartitions=partitions, getReset=clear_databases) getPartitions=partitions, getReset=clear_databases,
getSSL=self.SSL)
if upstream is not None: if upstream is not None:
self.upstream = weakref.proxy(upstream) self.upstream = weakref.proxy(upstream)
kw.update(getUpstreamCluster=upstream.name, kw.update(getUpstreamCluster=upstream.name,
...@@ -600,7 +603,7 @@ class NEOCluster(object): ...@@ -600,7 +603,7 @@ class NEOCluster(object):
self.storage_list = [StorageApplication(getDatabase=db % x, **kw) self.storage_list = [StorageApplication(getDatabase=db % x, **kw)
for x in db_list] for x in db_list]
self.admin_list = [AdminApplication(**kw)] self.admin_list = [AdminApplication(**kw)]
self.neoctl = NeoCTL(self.admin.getVirtualAddress()) self.neoctl = NeoCTL(self.admin.getVirtualAddress(), ssl=self.SSL)
def __repr__(self): def __repr__(self):
return "<%s(%s) at 0x%x>" % (self.__class__.__name__, return "<%s(%s) at 0x%x>" % (self.__class__.__name__,
...@@ -634,7 +637,7 @@ class NEOCluster(object): ...@@ -634,7 +637,7 @@ class NEOCluster(object):
for node in getattr(self, node_type + '_list'): for node in getattr(self, node_type + '_list'):
node.resetNode(**kw) node.resetNode(**kw)
self.neoctl.close() self.neoctl.close()
self.neoctl = NeoCTL(self.admin.getVirtualAddress()) self.neoctl = NeoCTL(self.admin.getVirtualAddress(), ssl=self.SSL)
def start(self, storage_list=None, fast_startup=False): def start(self, storage_list=None, fast_startup=False):
self._patch() self._patch()
...@@ -656,10 +659,13 @@ class NEOCluster(object): ...@@ -656,10 +659,13 @@ class NEOCluster(object):
assert state in (ClusterStates.RUNNING, ClusterStates.BACKINGUP), state assert state in (ClusterStates.RUNNING, ClusterStates.BACKINGUP), state
self.enableStorageList(storage_list) self.enableStorageList(storage_list)
def newClient(self):
return ClientApplication(name=self.name, master_nodes=self.master_nodes,
compress=self.compress, ssl=self.SSL)
@cached_property @cached_property
def client(self): def client(self):
client = ClientApplication(name=self.name, client = self.newClient()
master_nodes=self.master_nodes, compress=self.compress)
# Make sure client won't be reused after it was closed. # Make sure client won't be reused after it was closed.
def close(): def close():
client = self.client client = self.client
......
...@@ -29,7 +29,7 @@ from neo.lib.connection import ConnectionClosed, MTClientConnection ...@@ -29,7 +29,7 @@ from neo.lib.connection import ConnectionClosed, MTClientConnection
from neo.lib.protocol import CellStates, ClusterStates, NodeStates, Packets, \ from neo.lib.protocol import CellStates, ClusterStates, NodeStates, Packets, \
ZERO_TID ZERO_TID
from .. import expectedFailure, _UnexpectedSuccess, Patch from .. import expectedFailure, _UnexpectedSuccess, Patch
from . import ClientApplication, NEOCluster, NEOThreadedTest from . import NEOCluster, NEOThreadedTest
from neo.lib.util import add64, makeChecksum from neo.lib.util import add64, makeChecksum
from neo.client.exception import NEOStorageError from neo.client.exception import NEOStorageError
from neo.client.pool import CELL_CONNECTED, CELL_GOOD from neo.client.pool import CELL_CONNECTED, CELL_GOOD
...@@ -601,8 +601,7 @@ class Test(NEOThreadedTest): ...@@ -601,8 +601,7 @@ class Test(NEOThreadedTest):
# (at this time, we still have x=0 and y=1) # (at this time, we still have x=0 and y=1)
t2, c2 = cluster.getTransaction() t2, c2 = cluster.getTransaction()
# Copy y to x using a different Master-Client connection # Copy y to x using a different Master-Client connection
client = ClientApplication(name=cluster.name, client = cluster.newClient()
master_nodes=cluster.master_nodes)
txn = transaction.Transaction() txn = transaction.Transaction()
client.tpc_begin(txn) client.tpc_begin(txn)
client.store(x1._p_oid, x1._p_serial, y, '', txn) client.store(x1._p_oid, x1._p_serial, y, '', txn)
...@@ -715,8 +714,7 @@ class Test(NEOThreadedTest): ...@@ -715,8 +714,7 @@ class Test(NEOThreadedTest):
self.tic() self.tic()
# modify x with another client # modify x with another client
client = ClientApplication(name=cluster.name, client = cluster.newClient()
master_nodes=cluster.master_nodes)
txn = transaction.Transaction() txn = transaction.Transaction()
client.tpc_begin(txn) client.tpc_begin(txn)
client.store(x1._p_oid, x1._p_serial, y, '', txn) client.store(x1._p_oid, x1._p_serial, y, '', txn)
...@@ -804,8 +802,7 @@ class Test(NEOThreadedTest): ...@@ -804,8 +802,7 @@ class Test(NEOThreadedTest):
with cluster.master.filterConnection(cluster.storage) as m2s: with cluster.master.filterConnection(cluster.storage) as m2s:
m2s.add(delayNotifyInformation) m2s.add(delayNotifyInformation)
cluster.client.master_conn.close() cluster.client.master_conn.close()
client = ClientApplication(name=cluster.name, client = cluster.newClient()
master_nodes=cluster.master_nodes)
p = Patch(client.storage_bootstrap_handler, notReady=notReady) p = Patch(client.storage_bootstrap_handler, notReady=notReady)
try: try:
p.apply() p.apply()
......
#
# Copyright (C) 2015 Nexedi SA
#
# 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; either version 2
# of the License, or (at your option) any later version.
#
# 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, see <http://www.gnu.org/licenses/>.
import unittest
from .. import SSL
from . import NEOCluster, test, testReplication
class SSLMixin:
@classmethod
def setUpClass(cls):
NEOCluster.SSL = SSL
@classmethod
def tearDownClass(cls):
NEOCluster.SSL = None
class SSLTests(SSLMixin, test.Test):
# exclude expected failures
testDeadlockAvoidance = testStorageFailureDuringTpcFinish = None
class SSLReplicationTests(SSLMixin, testReplication.ReplicationTests):
# do not repeat slowest tests with SSL
testBackupNodeLost = testBackupNormalCase = None
if __name__ == "__main__":
unittest.main()
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