Skip to content
Snippets Groups Projects
Commit ada881b0 authored by Samir Faci's avatar Samir Faci
Browse files

Introduce Admin access to querySummary

ChangeLog:
 - Fixing client test to work with py3
 - Fix docs to be more markdown friendly.
 - Fixing various tests: test_config.py, test_providers, test_mulitple.py
 - Fixing the SOAPFaults parsing behavior
parent eddf14f9
No related branches found
No related tags found
No related merge requests found
Showing with 904 additions and 765 deletions
......@@ -12,3 +12,4 @@ docker-compose.override.yml
.devcontainer
twistd.pid
.env
.DS_Store
......@@ -31,24 +31,22 @@ Dmz vs. behind the firewall: Should work with both, not required.
## Dependencies:
* Python 2.7 or later (Python 3 not supported yet)
* Python 3
* Twisted 16.x.x or later, http://twistedmatrix.com/trac/
* Twisted 21.x.x or later, http://twistedmatrix.com/trac/
* Psycopg 2.5.0 or later (http://initd.org/psycopg/, 2.4.6 _might_ work)
* Psycopg 2.9.0 or later (http://initd.org/psycopg/)
* Twistar 1.1 or later (https://pypi.python.org/pypi/twistar/ & http://findingscience.com/twistar/ )
* Twistar 2.0 or later (https://pypi.python.org/pypi/twistar/ & http://findingscience.com/twistar/ )
* PostgreSQL (need 9.5 or later if using connection id assignment)
* PostgreSQL (need 12 or later if using connection id assignment)
* pyOpenSSL 0.14 (when running with SSL/TLS)
* pyOpenSSL 17.5 or later (when running with SSL/TLS)
Python and Twisted should be included in the package system in most recent
Linux distributions.
Older Twisted versions might work, Twisted 15.x and earlier won't work with
OpenSSH 7.0 or later. If you see connection lost for ssh in the log, most
likely your Twisted version is too old.
If you see connection lost for ssh in the log, most likely your Twisted version is too old.
Furthermore, for SSH based backends (Brocade, Force10, and Juniper), the
packages pyasn1 and python-crypto are also required.
......
......
......@@ -37,3 +37,4 @@ NORDUnet License (3-clause BSD). See LICENSE for more details.
[NORDUnet](http://www.nordu.net) (2011-2015)
......@@ -11,6 +11,7 @@ dbhost=${POSTGRES_HOST}
database=${POSTGRES_DB}
dbuser=${POSTGRES_USER}
dbpassword=${POSTGRES_PASSWORD}
allowed_admins=${ALLOWED_ADMINS}
tls=${TLS_ENABLED}
......
......
OpenNSA 3 Configuration Migration
=================================
# OpenNSA 3 Configuration Migration
With the port of OpenNSA from Python 2 to Python 3, and the subsequent release
of OpenNSA 3, support for multiple backends was added. For this, some changes
......@@ -12,7 +12,7 @@ The changes are:
Example of old style:
```
```ini
[service]
network=aruba.net
nrmmap=aruba.nrm
......@@ -22,7 +22,7 @@ nrmmap=aruba.nrm
Equivalent config in new style:
```
```ini
[service]
domain=aruba.net
......@@ -32,7 +32,7 @@ nrmmap=aruba.nrm
An example with multiple backends shows why the change was needed:
```
```ini
[service]
domain=aruba.net
......
......
File moved
......@@ -2,6 +2,7 @@ How to run the the unit/integration tests for OpenNSA
Make sure all the requirements are installed. Then:
```sh
./util/pg-test-run # This will start a Postgres in docker
PYTHONPATH=. trial test
```
TLS/SSL Configuration
---------------------
# TLS/SSL Configuration
The configuration of TLS/SSL of OpenNSA is something that has confused several
people. This guide tries to make it more comprehensible. OpenNSA is somewhat
......@@ -18,7 +18,7 @@ When you have obtained a certificate you should have a private key and a
certificate file (also contains the public key).
** Configuration Options **
## Configuration Options
`tls=true`
Enable TLS.
......@@ -40,7 +40,7 @@ If OpenNSA should verify the peer. You want this to true, unless debugging..
Comma-seperated list of hosts that are allowed to make request to OpenNSA.
** Common Issues **
## Common Issues
If you get:
AttributeError: 'OpenSSL.SSL.Context' object has no attribute 'set_session_cache_mode'
......
......
......@@ -10,7 +10,6 @@ from twisted.internet import reactor, defer
from opennsa import nsa
from opennsa.cli import options, parser, commands, logobserver
CLI_TIMEOUT = 130 # The default 2-PC timeout for nsi is 120 seconds, so just add a bit to that
CLI_DEFAULTS = '.opennsa-cli'
......@@ -64,7 +63,7 @@ def doMain():
else:
defaults = {}
log.msg('Defaults:', debug=True)
log.msg(f"Defaults from {defaults_file}:", debug=True)
for k, v in defaults.items():
log.msg(' %s : %s' % (k, v), debug=True)
......@@ -74,7 +73,8 @@ def doMain():
# network commands, listener port created in this block
# note: we currently only have network commands, but they may change in the future
if config.subCommand in ['reserve', 'reserveonly', 'reservecommit', 'reserveprovision', 'rprt', 'provision', 'release', 'terminate', 'query', 'queryrec']:
if config.subCommand in ['reserve', 'reserveonly', 'reservecommit', 'reserveprovision', 'rprt', 'provision',
'release', 'terminate', 'query', 'queryrec']:
if options.NSA_SHORTHAND in defaults and config.subOptions[options.PROVIDER] in defaults[options.NSA_SHORTHAND]:
ns = defaults[options.NSA_SHORTHAND][config.subOptions[options.PROVIDER]]
......@@ -92,7 +92,8 @@ def doMain():
global_id = config.subOptions[options.GLOBAL_ID] or defaults.get(options.GLOBAL_ID)
# can only be specified on command line for now
security_attributes = [ nsa.SecurityAttribute(type_, value) for type_, value in config.subOptions[options.SECURITY_ATTRIBUTES] ]
security_attributes = [nsa.SecurityAttribute(type_, value) for type_, value in
config.subOptions[options.SECURITY_ATTRIBUTES]]
if service_url is None:
raise usage.UsageError('Service URL not specified')
......@@ -111,7 +112,8 @@ def doMain():
log.msg("Requester URL: %s" % requester_url, debug=True)
nsi_header = nsa.NSIHeader(client_nsa.urn(), provider_nsa.urn(), reply_to=provider_nsa.endpoint, security_attributes=security_attributes)
nsi_header = nsa.NSIHeader(client_nsa.urn(), provider_nsa.urn(), reply_to=provider_nsa.endpoint,
security_attributes=security_attributes)
# setup ssl context
public_key = config.subOptions[options.CERTIFICATE] or defaults.get(options.CERTIFICATE)
......@@ -175,7 +177,8 @@ def doMain():
raise usage.UsageError('Connection ID is not defined')
from opennsa.protocols import nsi2
client, factory = nsi2.createRequester(host, port, service_url, tls=tls, ctx_factory=ctx_factory, authz_header=authz_header, callback_timeout=CLI_TIMEOUT)
client, factory = nsi2.createRequester(host, port, service_url, tls=tls, ctx_factory=ctx_factory,
authz_header=authz_header, callback_timeout=CLI_TIMEOUT)
# setup listener port
if tls:
......@@ -183,20 +186,23 @@ def doMain():
else:
iport = reactor.listenTCP(port, factory)
# start over on commands, now we do the actual dispatch
if config.subCommand == 'reserve':
yield commands.reserve(client, nsi_header, source_stp, dest_stp, start_time, end_time, bandwidth, ero, connection_id, global_id)
yield commands.reserve(client, nsi_header, source_stp, dest_stp, start_time, end_time, bandwidth, ero,
connection_id, global_id)
elif config.subCommand == 'reserveonly':
yield commands.reserveonly(client, nsi_header, source_stp, dest_stp, start_time, end_time, bandwidth, ero, connection_id, global_id)
yield commands.reserveonly(client, nsi_header, source_stp, dest_stp, start_time, end_time, bandwidth, ero,
connection_id, global_id)
elif config.subCommand == 'reserveprovision':
yield commands.reserveprovision(client, nsi_header, source_stp, dest_stp, start_time, end_time, bandwidth, ero, connection_id, global_id, notification_wait)
yield commands.reserveprovision(client, nsi_header, source_stp, dest_stp, start_time, end_time, bandwidth, ero,
connection_id, global_id, notification_wait)
elif config.subCommand == 'rprt':
yield commands.rprt(client, nsi_header, source_stp, dest_stp, start_time, end_time, bandwidth, ero, connection_id, global_id)
yield commands.rprt(client, nsi_header, source_stp, dest_stp, start_time, end_time, bandwidth, ero,
connection_id, global_id)
elif config.subCommand == 'reservecommit':
yield commands.reservecommit(client, nsi_header, connection_id)
......@@ -224,14 +230,11 @@ def doMain():
print('Invalid subcommand specified')
print('{}: Try --help for usage details.'.format(sys.argv[0]))
if iport:
yield iport.stopListening()
def main():
def slightlyDelayedShutdown(_):
# this means that the reactor/kernel will have a bit of time
# to push off any replies/acks before shutdown
......@@ -255,4 +258,3 @@ def main():
if __name__ == '__main__':
reactor.callWhenRunning(main)
reactor.run()
This diff is collapsed.
......@@ -12,7 +12,6 @@ LABEL_MAP = {
def _createSTP(stp_arg):
if not ':' in stp_arg:
raise usage.UsageError('No ":" in stp, invalid format (see docs/cli.md)')
......@@ -22,7 +21,8 @@ def _createSTP(stp_arg):
if not '=' in label_desc:
raise usage.UsageError('No "=" in stp label, invalid format (see docs/cli.md)')
label_type, label_value = label_desc.split("=")
label = nsa.Label(LABEL_MAP[label_type],label_value) # FIXME need good error message if label type doesn't exist
label = nsa.Label(LABEL_MAP[label_type],
label_value) # FIXME need good error message if label type doesn't exist
else:
network, port = stp_arg.rsplit(':', 1)
label = None
......@@ -42,7 +42,6 @@ def _createSTPList(ero):
def _createP2PS(src, dst, capacity, ero):
src_stp = _createSTP(src)
dst_stp = _createSTP(dst)
ordered_stp = _createSTPList(ero)
......@@ -51,7 +50,6 @@ def _createP2PS(src, dst, capacity, ero):
def _handleEvent(event):
notification_type, header, entry = event
if notification_type == 'errorEvent':
......@@ -83,10 +81,8 @@ def _logError(e):
log.msg('Variables: %s' % ' '.join([': '.join(tvp) for tvp in e.variables]))
@defer.inlineCallbacks
def discover(client, service_url):
res = yield client.queryNSA(service_url)
print("-- COMMAND RESULT --")
print(res)
......@@ -95,14 +91,14 @@ def discover(client, service_url):
@defer.inlineCallbacks
def reserveonly(client, nsi_header, src, dst, start_time, end_time, capacity, ero, connection_id, global_id):
schedule = nsa.Schedule(start_time, end_time)
service_def = _createP2PS(src, dst, capacity, ero)
crt = nsa.Criteria(0, schedule, service_def)
try:
nsi_header.connection_trace = [nsi_header.requester_nsa + ':' + '1']
connection_id, _,_,criteria = yield client.reserve(nsi_header, connection_id, global_id, 'Test Connection', crt)
connection_id, _, _, criteria = yield client.reserve(nsi_header, connection_id, global_id, 'Test Connection',
crt)
nsi_header.connection_trace = None
sd = criteria.service_def
log.msg("Connection created and held. Id %s at %s" % (connection_id, nsi_header.provider_nsa))
......@@ -114,14 +110,15 @@ def reserveonly(client, nsi_header, src, dst, start_time, end_time, capacity, er
@defer.inlineCallbacks
def reserve(client, nsi_header, src, dst, start_time, end_time, capacity, ero, connection_id, global_id):
schedule = nsa.Schedule(start_time, end_time)
service_def = _createP2PS(src, dst, capacity, ero)
crt = nsa.Criteria(0, schedule, service_def)
try:
nsi_header.connection_trace = [nsi_header.requester_nsa + ':' + '1']
connection_id, global_reservation_id, description, criteria = yield client.reserve(nsi_header, connection_id, global_id, 'Test Connection', crt)
connection_id, global_reservation_id, description, criteria = yield client.reserve(nsi_header, connection_id,
global_id, 'Test Connection',
crt)
nsi_header.connection_trace = None
sd = criteria.service_def
log.msg("Connection created and held. Id %s at %s" % (connection_id, nsi_header.provider_nsa))
......@@ -136,15 +133,16 @@ def reserve(client, nsi_header, src, dst, start_time, end_time, capacity, ero, c
@defer.inlineCallbacks
def reserveprovision(client, nsi_header, src, dst, start_time, end_time, capacity, ero, connection_id, global_id, notification_wait):
def reserveprovision(client, nsi_header, src, dst, start_time, end_time, capacity, ero, connection_id, global_id,
notification_wait):
schedule = nsa.Schedule(start_time, end_time)
service_def = _createP2PS(src, dst, capacity, ero)
crt = nsa.Criteria(0, schedule, service_def)
try:
nsi_header.connection_trace = [nsi_header.requester_nsa + ':' + '1']
connection_id, _,_, criteria = yield client.reserve(nsi_header, connection_id, global_id, 'Test Connection', crt)
connection_id, _, _, criteria = yield client.reserve(nsi_header, connection_id, global_id, 'Test Connection',
crt)
nsi_header.connection_trace = []
sd = criteria.service_def
log.msg("Connection created and held. Id %s at %s" % (connection_id, nsi_header.provider_nsa))
......@@ -174,7 +172,6 @@ def reserveprovision(client, nsi_header, src, dst, start_time, end_time, capacit
_logError(e)
@defer.inlineCallbacks
def rprt(client, nsi_header, src, dst, start_time, end_time, capacity, ero, connection_id, global_id):
# reserve, provision, release, terminate
......@@ -184,7 +181,8 @@ def rprt(client, nsi_header, src, dst, start_time, end_time, capacity, ero, conn
try:
nsi_header.connection_trace = [nsi_header.requester_nsa + ':' + '1']
connection_id, _,_, criteria = yield client.reserve(nsi_header, connection_id, global_id, 'Test Connection', crt)
connection_id, _, _, criteria = yield client.reserve(nsi_header, connection_id, global_id, 'Test Connection',
crt)
nsi_header.connection_trace = []
sd = criteria.service_def
log.msg("Connection created and held. Id %s at %s" % (connection_id, nsi_header.provider_nsa))
......@@ -216,7 +214,6 @@ def rprt(client, nsi_header, src, dst, start_time, end_time, capacity, ero, conn
@defer.inlineCallbacks
def reservecommit(client, nsi_header, connection_id):
try:
yield client.reserveCommit(nsi_header, connection_id)
log.msg("Reservation committed at %s" % nsi_header.provider_nsa)
......@@ -227,7 +224,6 @@ def reservecommit(client, nsi_header, connection_id):
@defer.inlineCallbacks
def provision(client, nsi_header, connection_id, notification_wait):
try:
yield client.provision(nsi_header, connection_id)
log.msg('Connection %s provisioned' % connection_id)
......@@ -240,7 +236,6 @@ def provision(client, nsi_header, connection_id, notification_wait):
@defer.inlineCallbacks
def release(client, nsi_header, connection_id, notification_wait):
try:
yield client.release(nsi_header, connection_id)
log.msg('Connection %s released' % connection_id)
......@@ -253,7 +248,6 @@ def release(client, nsi_header, connection_id, notification_wait):
@defer.inlineCallbacks
def terminate(client, nsi_header, connection_id):
try:
yield client.terminate(nsi_header, connection_id)
log.msg('Connection %s terminated' % connection_id)
......@@ -261,10 +255,7 @@ def terminate(client, nsi_header, connection_id):
_logError(e)
def _emitQueryResult(query_result, i='', child=False):
qr = query_result
log.msg('')
......@@ -302,11 +293,8 @@ def _emitQueryResult(query_result, i='', child=False):
_emitQueryResult(c, i + ' ', True)
@defer.inlineCallbacks
def querySummary(client, nsi_header, connection_ids, global_reservation_ids):
try:
qc = yield client.querySummary(nsi_header, connection_ids, global_reservation_ids)
if not qc:
......@@ -324,7 +312,6 @@ def querySummary(client, nsi_header, connection_ids, global_reservation_ids):
@defer.inlineCallbacks
def queryRecursive(client, nsi_header, connection_ids, global_reservation_ids):
try:
qc = yield client.queryRecursive(nsi_header, connection_ids, global_reservation_ids)
if not qc:
......@@ -338,4 +325,3 @@ def queryRecursive(client, nsi_header, connection_ids, global_reservation_ids):
except error.NSIError as e:
_logError(e)
......@@ -10,7 +10,6 @@ from twisted.python import log
from opennsa import config
from opennsa.shared.xmlhelper import UTC
# option names, as constants so we don't use strings in other modules
VERBOSE = 'verbose'
DEFAULTS_FILE = 'defaults-file'
......@@ -49,9 +48,7 @@ XSD_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
NSA_SHORTHAND = 'nsa'
def parseTimestamp(value):
if value.startswith('+'):
offset = int(value[1:])
ts = datetime.datetime.fromtimestamp(time.time() + offset, UTC()).replace(tzinfo=None)
......@@ -61,9 +58,7 @@ def parseTimestamp(value):
return ts
def readDefaults(file_):
defaults = {}
for line in file_.readlines():
......@@ -99,4 +94,3 @@ def readDefaults(file_):
log.msg('Error parsing line in CLI defaults file. Line: %s. Error: %s' % (line, str(e)))
return defaults
......@@ -57,52 +57,70 @@ from opennsa.cli import options
class DefaultsFileOption(usage.Options):
optParameters = [[options.DEFAULTS_FILE, 'f', None, 'Defaults file']]
class HostOption(usage.Options):
optParameters = [[options.HOST, 'h', None, 'Host (for callback)']]
class PortOption(usage.Options):
optParameters = [[options.PORT, 'o', None, 'Port (for callback)', int]]
# parameters which are only used for some commands
class ServiceURLOption(usage.Options):
optParameters = [[options.SERVICE_URL, 'u', None, 'Service URL']]
class AuthzHeaderOption(usage.Options):
optParameters = [[options.AUTHZ_HEADER, 'm', None, 'Authorization header']]
class ProviderNSAOption(usage.Options):
optParameters = [[options.PROVIDER, 'p', None, 'Provider NSA Identity']]
class RequesterNSAOption(usage.Options):
optParameters = [[options.REQUESTER, 'r', None, 'Requester NSA Identity']]
class SourceSTPOption(usage.Options):
optParameters = [[options.SOURCE_STP, 's', None, 'Source STP']]
class DestSTPOption(usage.Options):
optParameters = [[options.DEST_STP, 'd', None, 'Dest STP']]
class ConnectionIDOption(usage.Options):
optParameters = [[options.CONNECTION_ID, 'c', None, 'Connection id']]
class GlobalIDOption(usage.Options):
optParameters = [[options.GLOBAL_ID, 'g', None, 'Global id']]
class StartTimeOption(usage.Options):
optParameters = [[options.START_TIME, 'a', None, 'Start time (UTC time)']]
def postOptions(self):
if self[options.START_TIME] is not None:
self[options.START_TIME] = datetime.datetime.strptime(self[options.START_TIME], options.XSD_DATETIME_FORMAT) #.replace(tzinfo=None)
self[options.START_TIME] = datetime.datetime.strptime(self[options.START_TIME],
options.XSD_DATETIME_FORMAT) # .replace(tzinfo=None)
class EndTimeOption(usage.Options):
optParameters = [[options.END_TIME, 'e', None, 'End time (UTC time)']]
def postOptions(self):
if self[options.END_TIME] is not None:
self[options.END_TIME] = datetime.datetime.strptime(self[options.END_TIME], options.XSD_DATETIME_FORMAT) # .replace(tzinfo=None)
self[options.END_TIME] = datetime.datetime.strptime(self[options.END_TIME],
options.XSD_DATETIME_FORMAT) # .replace(tzinfo=None)
class SecurityAttributeOptions(usage.Options):
optParameters = [[options.SECURITY_ATTRIBUTES, 'j', None, 'Security attributes (format attr1=value1,attr2=value2)']]
def postOptions(self):
sats = []
if self[options.SECURITY_ATTRIBUTES]:
......@@ -113,29 +131,38 @@ class SecurityAttributeOptions(usage.Options):
sats.append((key, value))
self[options.SECURITY_ATTRIBUTES] = sats
class BandwidthOption(usage.Options):
optParameters = [[options.BANDWIDTH, 'b', None, 'Bandwidth (Megabits)']]
class EroOption(usage.Options):
optParameters = [[options.ERO, '0', None, 'ERO list']]
class PublicKeyOption(usage.Options):
optParameters = [[options.CERTIFICATE, 'l', None, 'Certificate path']]
class PrivateKeyOption(usage.Options):
optParameters = [[options.KEY, 'k', None, 'Private key path']]
class CertificateDirectoryOption(usage.Options):
optParameters = [[options.CERTIFICATE_DIR, 'i', None, 'Certificate directory']]
# flags
class NotificationWaitFlag(usage.Options):
optFlags = [ [ options.NOTIFICATION_WAIT, 'y', 'Wait for notifications, exists on data plane deactive and errorEvent' ] ]
optFlags = [
[options.NOTIFICATION_WAIT, 'y', 'Wait for notifications, exists on data plane deactive and errorEvent']]
class TLSFlag(usage.Options):
optFlags = [[options.TLS, 'x', 'Use TLS for listener port']]
class SkipCertificateVerificationFlag(usage.Options):
optFlags = [[options.NO_VERIFY_CERT, 'z', 'Skip certificate verification']]
......@@ -143,7 +170,6 @@ class SkipCertificateVerificationFlag(usage.Options):
# command options
class BaseOptions(DefaultsFileOption):
optFlags = [
[options.VERBOSE, 'v', 'Print out more information'],
[options.DUMP_PAYLOAD, 'q', 'Dump message payloads'],
......@@ -152,14 +178,16 @@ class BaseOptions(DefaultsFileOption):
class NetworkBaseOptions(BaseOptions, HostOption, PortOption,
ServiceURLOption, AuthzHeaderOption, SecurityAttributeOptions,
TLSFlag, PublicKeyOption, PrivateKeyOption, CertificateDirectoryOption, SkipCertificateVerificationFlag):
TLSFlag, PublicKeyOption, PrivateKeyOption, CertificateDirectoryOption,
SkipCertificateVerificationFlag):
def postOptions(self):
# technically we should do this for all superclasses, but these are the only ones that has anything to do
SecurityAttributeOptions.postOptions(self)
class NetworkCommandOptions(NetworkBaseOptions, ProviderNSAOption, RequesterNSAOption, ConnectionIDOption, GlobalIDOption):
class NetworkCommandOptions(NetworkBaseOptions, ProviderNSAOption, RequesterNSAOption, ConnectionIDOption,
GlobalIDOption):
pass
......@@ -167,7 +195,8 @@ class ProvisionOptions(NetworkCommandOptions, NotificationWaitFlag):
pass
class ReserveOptions(NetworkCommandOptions, SourceSTPOption, DestSTPOption, StartTimeOption, EndTimeOption, BandwidthOption, EroOption):
class ReserveOptions(NetworkCommandOptions, SourceSTPOption, DestSTPOption, StartTimeOption, EndTimeOption,
BandwidthOption, EroOption):
def postOptions(self):
NetworkCommandOptions.postOptions(self)
......@@ -201,11 +230,8 @@ class Options(usage.Options):
if self.subCommand is None:
return usage.UsageError('No option specified')
def opt_version(self):
from opennsa import __version__
from twisted import copyright
print("OpenNSA version %s. Running on Twisted version %s." % (__version__, copyright.version))
raise SystemExit
......@@ -10,7 +10,6 @@ import configparser
from opennsa import constants as cnt
# defaults
DEFAULT_CONFIG_FILE = '/etc/opennsa.conf'
DEFAULT_LOG_FILE = '/var/log/opennsa.log'
......@@ -22,7 +21,6 @@ DEFAULT_VERIFY = True
# This will work on most mordern linux distros
DEFAULT_CERTIFICATE_DIR = '/etc/ssl/certs'
# config blocks and options
BLOCK_SERVICE = 'service'
BLOCK_DUD = 'dud'
......@@ -38,6 +36,7 @@ BLOCK_JUNOSSPACE = 'junosspace'
BLOCK_OESS = 'oess'
BLOCK_CUSTOM_BACKEND = 'custombackend'
# service block
DOMAIN = 'domain' # mandatory
NETWORK_NAME = 'network' # legacy, used to be mandatory
......@@ -64,6 +63,7 @@ CERTIFICATE = 'certificate' # mandatory, if tls is set
CERTIFICATE_DIR = 'certdir' # mandatory (but dir can be empty)
VERIFY_CERT = 'verify'
ALLOWED_HOSTS = 'allowedhosts' # comma seperated list
ALLOWED_ADMINS = 'allowed_admins' # list of requester nsaId with administration level access
# generic stuff
_SSH_HOST = 'host'
......@@ -113,7 +113,6 @@ PICA8OVS_SSH_PUBLIC_KEY = _SSH_PUBLIC_KEY
PICA8OVS_SSH_PRIVATE_KEY = _SSH_PRIVATE_KEY
PICA8OVS_DB_IP = 'dbip'
# NCS VPN Backend
NCS_SERVICES_URL = 'url'
NCS_USER = 'user'
......@@ -166,15 +165,90 @@ class EnvInterpolation(configparser.BasicInterpolation):
return os.path.expandvars(value)
def readConfig(filename):
class Config(object):
"""
Singleton instance of configuration class. Loads the config and persists it to class object.
Also, provides utility function around the loaded configuration
"""
_instance = None
def __init__(self):
raise RuntimeError("Call instance() instead, singleton class")
@classmethod
def instance(cls):
if cls._instance is None:
print('Creating new instance')
cls._instance = cls.__new__(cls)
cls._instance.cfg = None
cls._instance.vc = None
# Put any initialization here.
return cls._instance
def read_config(self, filename):
"""
Load the configuration from a given file
"""
if self._instance.cfg is None:
cfg = configparser.ConfigParser(interpolation=EnvInterpolation())
cfg.add_section(BLOCK_SERVICE)
cfg.read([filename])
self._instance.cfg = cfg
return self._instance.cfg, self._read_verify_config()
return cfg
def _read_verify_config(self):
"""
Returns a dictionary of the loaded config once verified
"""
if self._instance.vc is None:
self._instance.vc = self._load_config_dict()
return self._instance.vc
def config_dict(self):
"""
Returns the loaded dict if one exists, or an empty one otherwise.
"""
return self._instance.vc if self._instance.vc is not None else {}
def readVerifyConfig(cfg):
@property
def allowed_admins(self):
"""
Property returns array of allowed admins
"""
return self.config_dict().get(ALLOWED_ADMINS, '')
def is_admin_override(self, urn):
"""
Check if the URN matches a valid admin. Allowing all queries to execute
"""
admins = self.allowed_admins
for entry in self.allowed_admins:
if entry == urn:
return True
return False
def _load_database_config(self, vc):
# vc = self._instance.vc
cfg = self._instance.cfg
# database
try:
vc[DATABASE] = cfg.get(BLOCK_SERVICE, DATABASE)
except configparser.NoOptionError:
raise ConfigurationError(
'No database specified in configuration file (mandatory)')
try:
vc[DATABASE_USER] = cfg.get(BLOCK_SERVICE, DATABASE_USER)
except configparser.NoOptionError:
raise ConfigurationError(
'No database user specified in configuration file (mandatory)')
vc[DATABASE_PASSWORD] = cfg.get(BLOCK_SERVICE, DATABASE_PASSWORD, fallback=None)
vc[DATABASE_HOST] = cfg.get(BLOCK_SERVICE, DATABASE_HOST, fallback='localhost')
vc[SERVICE_ID_START] = cfg.get(BLOCK_SERVICE, SERVICE_ID_START, fallback=None)
def _load_config_dict(self) -> dict:
"""
Read a config and verify that things are correct. Will also fill in
default values where applicable.
......@@ -185,7 +259,7 @@ def readVerifyConfig(cfg):
Returns a "verified" config, which is a dictionary.
"""
cfg = self._instance.cfg
vc = {}
# Check for deprecated / old invalid stuff
......@@ -212,10 +286,7 @@ def readVerifyConfig(cfg):
except configparser.NoOptionError:
pass
try:
vc[LOG_FILE] = cfg.get(BLOCK_SERVICE, LOG_FILE)
except configparser.NoOptionError:
vc[LOG_FILE] = DEFAULT_LOG_FILE
vc[LOG_FILE] = cfg.get(BLOCK_SERVICE, LOG_FILE, fallback=DEFAULT_LOG_FILE)
try:
nrm_map_file = cfg.get(BLOCK_SERVICE, NRM_MAP_FILE)
......@@ -226,10 +297,7 @@ def readVerifyConfig(cfg):
except configparser.NoOptionError:
vc[NRM_MAP_FILE] = None
try:
vc[REST] = cfg.getboolean(BLOCK_SERVICE, REST)
except configparser.NoOptionError:
vc[REST] = False
vc[REST] = cfg.getboolean(BLOCK_SERVICE, REST, fallback=False)
try:
peers_raw = cfg.get(BLOCK_SERVICE, PEERS)
......@@ -237,20 +305,9 @@ def readVerifyConfig(cfg):
except configparser.NoOptionError:
vc[PEERS] = None
try:
vc[HOST] = cfg.get(BLOCK_SERVICE, HOST)
except configparser.NoOptionError:
vc[HOST] = None
try:
vc[TLS] = cfg.getboolean(BLOCK_SERVICE, TLS)
except configparser.NoOptionError:
vc[TLS] = DEFAULT_TLS
try:
vc[PORT] = cfg.getint(BLOCK_SERVICE, PORT)
except configparser.NoOptionError:
vc[PORT] = DEFAULT_TLS_PORT if vc[TLS] else DEFAULT_TCP_PORT
vc[HOST] = cfg.get(BLOCK_SERVICE, HOST, fallback=None)
vc[TLS] = cfg.getboolean(BLOCK_SERVICE, TLS, fallback=DEFAULT_TLS)
vc[PORT] = cfg.getint(BLOCK_SERVICE, PORT, fallback=DEFAULT_TLS_PORT if vc[TLS] else DEFAULT_TCP_PORT)
try:
policies = cfg.get(BLOCK_SERVICE, POLICY).split(',')
......@@ -261,39 +318,21 @@ def readVerifyConfig(cfg):
except configparser.NoOptionError:
vc[POLICY] = []
try:
vc[PLUGIN] = cfg.get(BLOCK_SERVICE, PLUGIN)
except configparser.NoOptionError:
vc[PLUGIN] = None
vc[PLUGIN] = cfg.get(BLOCK_SERVICE, PLUGIN, fallback=None)
# database
try:
vc[DATABASE] = cfg.get(BLOCK_SERVICE, DATABASE)
except configparser.NoOptionError:
raise ConfigurationError(
'No database specified in configuration file (mandatory)')
self._load_database_config(vc)
self._load_certificates(vc)
try:
vc[DATABASE_USER] = cfg.get(BLOCK_SERVICE, DATABASE_USER)
except configparser.NoOptionError:
raise ConfigurationError(
'No database user specified in configuration file (mandatory)')
try:
vc[DATABASE_PASSWORD] = cfg.get(BLOCK_SERVICE, DATABASE_PASSWORD)
except configparser.NoOptionError:
vc[DATABASE_PASSWORD] = None
## Set override of allowed Admins
allowed_hosts_admins = cfg.get(BLOCK_SERVICE, ALLOWED_ADMINS, fallback='')
vc[ALLOWED_ADMINS] = [i.strip() for i in allowed_hosts_admins.split(',') if len(i) > 0]
try:
vc[DATABASE_HOST] = cfg.get(BLOCK_SERVICE, DATABASE_HOST)
except configparser.NoOptionError:
vc[DATABASE_HOST] = None
try:
vc[SERVICE_ID_START] = cfg.get(BLOCK_SERVICE, SERVICE_ID_START)
except configparser.NoOptionError:
vc[SERVICE_ID_START] = None
# backends
self._load_backends(vc)
return vc
def _load_certificates(self, vc):
cfg = self._instance.cfg
# we always extract certdir and verify as we need that for performing https requests
try:
certdir = cfg.get(BLOCK_SERVICE, CERTIFICATE_DIR)
......@@ -326,7 +365,8 @@ def readVerifyConfig(cfg):
try:
allowed_hosts_cfg = cfg.get(BLOCK_SERVICE, ALLOWED_HOSTS)
vc[ALLOWED_HOSTS] = allowed_hosts_cfg.split(',')
vc[ALLOWED_HOSTS] = [i.strip() for i in allowed_hosts_cfg.split(',') if len(i) > 0]
except:
pass
......@@ -334,7 +374,11 @@ def readVerifyConfig(cfg):
# Not enough options for configuring tls context
raise ConfigurationError('Missing TLS option: %s' % str(e))
# backends
def _load_backends(self, vc):
"""
Verify and load backends into configuration class
"""
cfg = self._instance.cfg
backends = {}
for section in cfg.sections():
......@@ -352,7 +396,8 @@ def readVerifyConfig(cfg):
raise ConfigurationError(
'Can only have one backend named "%s"' % name)
if backend_type in (BLOCK_DUD, BLOCK_JUNIPER_EX, BLOCK_JUNIPER_VPLS, BLOCK_JUNOSMX, BLOCK_FORCE10, BLOCK_BROCADE,
if backend_type in (
BLOCK_DUD, BLOCK_JUNIPER_EX, BLOCK_JUNIPER_VPLS, BLOCK_JUNOSMX, BLOCK_FORCE10, BLOCK_BROCADE,
BLOCK_NCSVPN, BLOCK_PICA8OVS, BLOCK_OESS, BLOCK_JUNOSSPACE, BLOCK_JUNOSEX,
BLOCK_CUSTOM_BACKEND, 'asyncfail'):
backend_conf = dict(cfg.items(section))
......@@ -360,5 +405,3 @@ def readVerifyConfig(cfg):
backends[name] = backend_conf
vc['backend'] = backends
return vc
File changed. Contains only whitespace changes. Show whitespace changes.
......@@ -21,11 +21,9 @@ from opennsa.protocols.shared import minisoap, httpclient
from opennsa.protocols.nsi2 import helper, queryhelper
from opennsa.protocols.nsi2.bindings import actions, nsiconnection, p2pservices
LOG_SYSTEM = 'nsi2.RequesterClient'
@implementer(INSIProvider)
class RequesterClient:
......@@ -41,13 +39,11 @@ class RequesterClient:
if authz_header:
self.http_headers['Authorization'] = authz_header
def _checkHeader(self, header):
if header.reply_to and header.correlation_id is None:
raise AssertionError('Header must specify correlation id, if reply to is specified')
def _createGenericRequestType(self, body_element_name, header, connection_id):
header_element = helper.convertProviderHeader(header, self.reply_to)
......@@ -56,8 +52,6 @@ class RequesterClient:
payload = minisoap.createSoapPayload(body_element, header_element)
return payload
def _handleErrorReply(self, err, header):
if err.check(WebError) is None:
......@@ -90,7 +84,6 @@ class RequesterClient:
return err
def reserve(self, header, connection_id, global_reservation_id, description, criteria, request_info=None):
# request_info is local only, so it isn't used
......@@ -118,13 +111,15 @@ class RequesterClient:
raise ValueError('Cannot create request for service definition of type %s' % str(type(sd)))
params = [p2pservices.TypeValueType(p[0], p[1]) for p in sd.parameters] if sd.parameters else None
service_def = p2pservices.P2PServiceBaseType(sd.capacity, sd.directionality, sd.symmetric, sd.source_stp.urn(), sd.dest_stp.urn(), sd.ero, params)
service_def = p2pservices.P2PServiceBaseType(sd.capacity, sd.directionality, sd.symmetric, sd.source_stp.urn(),
sd.dest_stp.urn(), sd.ero, params)
schedule_type = nsiconnection.ScheduleType(start_time, end_time)
# service_type = str(p2pservices.p2ps)
service_type = 'http://services.ogf.org/nsi/2013/12/descriptions/EVTS.A-GOLE'
criteria = nsiconnection.ReservationRequestCriteriaType(criteria.revision, schedule_type, service_type, service_def)
criteria = nsiconnection.ReservationRequestCriteriaType(criteria.revision, schedule_type, service_type,
service_def)
reservation = nsiconnection.ReserveType(connection_id, global_reservation_id, description, criteria)
......@@ -135,65 +130,65 @@ class RequesterClient:
header, ack = helper.parseRequest(soap_data)
return ack.connectionId
d = httpclient.soapRequest(self.service_url, actions.RESERVE, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.RESERVE, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(_handleAck, self._handleErrorReply, errbackArgs=(header,))
return d
def reserveCommit(self, header, connection_id, request_info=None):
self._checkHeader(header)
payload = self._createGenericRequestType(nsiconnection.reserveCommit, header, connection_id)
d = httpclient.soapRequest(self.service_url, actions.RESERVE_COMMIT, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.RESERVE_COMMIT, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(lambda sd: None, self._handleErrorReply, errbackArgs=(header,))
return d
def reserveAbort(self, header, connection_id, request_info=None):
self._checkHeader(header)
payload = self._createGenericRequestType(nsiconnection.reserveAbort, header, connection_id)
d = httpclient.soapRequest(self.service_url, actions.RESERVE_ABORT, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.RESERVE_ABORT, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(lambda sd: None, self._handleErrorReply, errbackArgs=(header,))
return d
def provision(self, header, connection_id, request_info=None):
self._checkHeader(header)
payload = self._createGenericRequestType(nsiconnection.provision, header, connection_id)
d = httpclient.soapRequest(self.service_url, actions.PROVISION, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.PROVISION, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(lambda sd: None, self._handleErrorReply, errbackArgs=(header,))
return d
def release(self, header, connection_id, request_info=None):
self._checkHeader(header)
payload = self._createGenericRequestType(nsiconnection.release, header, connection_id)
d = httpclient.soapRequest(self.service_url, actions.RELEASE, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.RELEASE, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(lambda sd: None, self._handleErrorReply, errbackArgs=(header,))
return d
def terminate(self, header, connection_id, request_info=None):
self._checkHeader(header)
payload = self._createGenericRequestType(nsiconnection.terminate, header, connection_id)
d = httpclient.soapRequest(self.service_url, actions.TERMINATE, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.TERMINATE, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(lambda sd: None, self._handleErrorReply, errbackArgs=(header,))
return d
def querySummary(self, header, connection_ids=None, global_reservation_ids=None, request_info=None):
self._checkHeader(header)
......@@ -205,11 +200,11 @@ class RequesterClient:
payload = minisoap.createSoapPayload(body_element, header_element)
d = httpclient.soapRequest(self.service_url, actions.QUERY_SUMMARY, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.QUERY_SUMMARY, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(lambda sd: None, self._handleErrorReply, errbackArgs=(header,))
return d
def querySummarySync(self, header, connection_ids=None, global_reservation_ids=None, request_info=None):
def gotReply(soap_data):
......@@ -224,11 +219,11 @@ class RequesterClient:
payload = minisoap.createSoapPayload(body_element, header_element)
d = httpclient.soapRequest(self.service_url, actions.QUERY_SUMMARY_SYNC, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.QUERY_SUMMARY_SYNC, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(gotReply, self._handleErrorReply, errbackArgs=(header,))
return d
def queryRecursive(self, header, connection_ids, global_reservation_ids=None, request_info=None):
self._checkHeader(header)
......@@ -240,7 +235,7 @@ class RequesterClient:
payload = minisoap.createSoapPayload(body_element, header_element)
d = httpclient.soapRequest(self.service_url, actions.QUERY_RECURSIVE, payload, ctx_factory=self.ctx_factory, headers=self.http_headers)
d = httpclient.soapRequest(self.service_url, actions.QUERY_RECURSIVE, payload, ctx_factory=self.ctx_factory,
headers=self.http_headers)
d.addCallbacks(lambda sd: None, self._handleErrorReply, errbackArgs=(header,))
return d
......@@ -153,8 +153,8 @@ def parseFault(payload):
detail = None
dt = fault.find('detail')
if dt is not None:
dc = dt.getchildren()[0]
if dt is not None and len(list(dt)) > 0:
dc = dt[0]
if dc is not None:
detail = ET.tostring(dc)
......
......
......@@ -24,18 +24,17 @@ from twisted.application import internet, service as twistedservice
from opennsa import __version__ as version
from opennsa.config import Config
from opennsa import config, logging, constants as cnt, nsa, provreg, database, aggregator, viewresource
from opennsa.topology import nrm, nml, linkvector, service as nmlservice
from opennsa.protocols import rest, nsi2
from opennsa.protocols.shared import httplog
from opennsa.discovery import service as discoveryservice, fetcher
NSI_RESOURCE = b'NSI'
def setupBackend(backend_cfg, network_name, nrm_ports, parent_requester):
bc = backend_cfg.copy()
backend_type = backend_cfg.pop('_backend_type')
......@@ -99,7 +98,6 @@ def setupBackend(backend_cfg, network_name, nrm_ports, parent_requester):
def setupTLSContext(vc):
# ssl/tls contxt
if vc[config.TLS]:
from opennsa.opennsaTlsContext import opennsa2WayTlsContext
......@@ -130,7 +128,6 @@ class CS2RequesterCreator:
self.ctx_factory = ctx_factory
def create(self, nsi_agent):
hash_input = nsi_agent.urn() + nsi_agent.endpoint
resource_name = b'RequesterService2-' + \
hashlib.sha1(hash_input.encode()).hexdigest().encode()
......@@ -306,7 +303,6 @@ class OpenNSAService(twistedservice.MultiService):
interfaces.append((cnt.OPENNSA_REST, rest_url, None))
for backend_network_name, no in networks.items():
nml_resource_name = '{}.nml.xml'.format(backend_network_name)
nml_url = '%s/NSI/%s' % (base_url, nml_resource_name)
......@@ -369,12 +365,11 @@ class OpenNSAService(twistedservice.MultiService):
def createApplication(config_file=config.DEFAULT_CONFIG_FILE, debug=False, payload=False):
application = twistedservice.Application('OpenNSA')
try:
cfg = config.readConfig(config_file)
vc = config.readVerifyConfig(cfg)
configIns = Config.instance()
cfg, vc = configIns.read_config(config_file)
# if log file is empty string use stdout
if vc[config.LOG_FILE]:
......
......
......@@ -7,3 +7,4 @@ cryptography>=3.4.8
python-dateutil>=2.8,<2.9
service-identity>=21.1.0,<22.0.0
idna>=3.2,<3.3
pyasn1>=0.4.8
......@@ -3,12 +3,12 @@ from twisted.trial import unittest
import json
import tempfile
import configparser
from io import StringIO
from opennsa import config, setup
from opennsa.config import Config
from . import db
ARUBA_DUD_CONFIG_NO_DATABASE = """
[service]
domain=aruba.net
......@@ -37,11 +37,15 @@ dbpassword={db_password}
tls=false
[dud]
[backends:dummy]
name=foobar
"""
ARUBA_DUD_CONFIG = """
[service]
domain=aruba.net
host=dummy
logfile=
rest=true
port=4080
......@@ -105,31 +109,49 @@ ethernet bon bonaire.net:topology#arb(-in|-out) vlan:1780-1799
class ConfigTest(unittest.TestCase):
def _reset_instance(self):
try:
self.configIns._instance.cfg = None
self.configIns._instance.vc = None
except:
pass
def setUp(self):
self.configIns = Config.instance()
self._reset_instance()
tc = json.load(open(db.CONFIG_FILE))
self.database = tc['database']
self.db_user = tc['user']
self.db_password = tc['password']
self.db_host = '127.0.0.1'
def _generate_temp_file(self, buffer):
"""
Helper utility to generate a temp file and write buffer to it.
"""
tmp = tempfile.NamedTemporaryFile('w+t')
tmp.write(buffer)
tmp.flush()
return tmp
def testConfigParsingNoDatabase(self):
config_file_content = ARUBA_DUD_CONFIG_NO_DATABASE
raw_cfg = configparser.SafeConfigParser()
raw_cfg.read_string(config_file_content)
expectedError = "No database specified in configuration file (mandatory)"
tmp = None
try:
cfg = config.readVerifyConfig(raw_cfg)
tmp = self._generate_temp_file(config_file_content)
cfg, vc = self.configIns.read_config(tmp.name)
nsa_service = setup.OpenNSAService(cfg)
factory, _ = nsa_service.setupServiceFactory()
self.fail('Should have raised config.ConfigurationError')
except config.ConfigurationError as e:
pass
self.assertEquals(expectedError, e.args[0])
finally:
if tmp is not None:
tmp.close()
def testConfigParsingNoNetworkName(self):
......@@ -137,17 +159,18 @@ class ConfigTest(unittest.TestCase):
db_host=self.db_host,
db_user=self.db_user,
db_password=self.db_password)
raw_cfg = configparser.SafeConfigParser()
raw_cfg.read_string(config_file_content)
tmp = None
try:
cfg = config.readVerifyConfig(raw_cfg)
nsa_service = setup.OpenNSAService(cfg)
tmp = self._generate_temp_file(config_file_content)
cfg, vc = self.configIns.read_config(tmp.name)
nsa_service = setup.OpenNSAService(self.configIns.config_dict())
factory, _ = nsa_service.setupServiceFactory()
self.fail('Should have raised config.ConfigurationError')
except config.ConfigurationError as e:
pass
finally:
if tmp is not None:
tmp.close()
def testConfigParsing(self):
......@@ -161,24 +184,28 @@ class ConfigTest(unittest.TestCase):
db_password=self.db_password,
nrm_map=aruba_ojs.name)
raw_cfg = configparser.SafeConfigParser()
raw_cfg.read_string(config_file_content)
tmp = self._generate_temp_file(config_file_content)
cfg, vc = self.configIns.read_config(tmp.name)
cfg = config.readVerifyConfig(raw_cfg)
nsa_service = setup.OpenNSAService(cfg)
try:
nsa_service = setup.OpenNSAService(vc)
factory, _ = nsa_service.setupServiceFactory()
finally:
tmp.close()
aruba_ojs.close()
def testInvalidLegacyConfig(self):
raw_cfg = configparser.SafeConfigParser()
raw_cfg.read_string(INVALID_LEGACY_CONFIG)
config_file_content = INVALID_LEGACY_CONFIG
tmp = self._generate_temp_file(config_file_content)
try:
cfg = config.readVerifyConfig(raw_cfg)
cfg, vc = self.configIns.read_config(tmp.name)
self.fail('Should have raised ConfigurationError')
except config.ConfigurationError:
pass
finally:
tmp.close()
def testConfigParsingMultiBackend(self):
......@@ -201,13 +228,13 @@ class ConfigTest(unittest.TestCase):
nrm_ojs=aruba_ojs.name,
nrm_san=aruba_san.name)
# parse and verify config
tmp = self._generate_temp_file(config_file_content)
cfg = configparser.SafeConfigParser()
cfg.read_string(config_file_content)
verified_config = config.readVerifyConfig(cfg)
try:
cfg, verified_config = self.configIns.read_config(tmp.name)
# do the setup dance to see if all the wiring is working, but don't start anything
nsa_service = setup.OpenNSAService(verified_config)
factory, _ = nsa_service.setupServiceFactory()
finally:
tmp.close()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment