Skip to content

Commit

Permalink
Merge pull request #171 from openvstorage/develop
Browse files Browse the repository at this point in the history
Promote
  • Loading branch information
JeffreyDevloo authored Aug 28, 2018
2 parents 2b59283 + 19c5a7c commit b32bece
Show file tree
Hide file tree
Showing 23 changed files with 969 additions and 83 deletions.
2 changes: 1 addition & 1 deletion packaging/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"source_contents": "--transform 's,^,{0}-{1}/,' src *.txt",
"version": {
"major": 0,
"minor": 4
"minor": 5
}
}
22 changes: 22 additions & 0 deletions src/api/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (C) 2018 iNuron NV
#
# This file is part of Open vStorage Open Source Edition (OSE),
# as available from
#
# http://www.openvstorage.org and
# http://www.openvstorage.com.
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License v3 (GNU AGPLv3)
# as published by the Free Software Foundation, in version 3 as it comes
# in the LICENSE.txt file of the Open vStorage OSE distribution.
#
# Open vStorage is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY of any kind.

# Backwards compatibility & easier importing
from .baseclient import BaseClient
from .ovsclient import OVSClient
from .simpleclient import SimpleClient

__all__ = ["BaseClient", "OVSClient", "SimpleClient"]
101 changes: 53 additions & 48 deletions src/api/client.py → src/api/client/baseclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"""
Module for the OVS API client
"""
import time
import base64
import urllib
import hashlib
Expand All @@ -36,14 +35,18 @@
logging.getLogger('urllib3').setLevel(logging.WARNING)


class OVSClient(object):
class BaseClient(object):
"""
Represents the OVS client
Basic API client
- Supports Authorization with tokens
- Caches tokens
"""
disable_warnings(InsecurePlatformWarning)
disable_warnings(InsecureRequestWarning)
disable_warnings(SNIMissingWarning)

_logger = Logger('api')

def __init__(self, ip, port, credentials=None, verify=False, version='*', raw_response=False, cache_store=None):
"""
Initializes the object with credentials and connection information
Expand Down Expand Up @@ -73,7 +76,6 @@ def __init__(self, ip, port, credentials=None, verify=False, version='*', raw_re
self._url = 'https://{0}:{1}/api'.format(ip, port)
self._key = hashlib.sha256('{0}{1}{2}{3}'.format(self.ip, self.port, self.client_id, self.client_secret)).hexdigest()
self._token = None
self._logger = Logger('api')
self._verify = verify
self._version = version
self._raw_response = raw_response
Expand Down Expand Up @@ -102,6 +104,41 @@ def _connect(self):
raise error
self._token = response['access_token']

def _build_headers(self):
"""
Builds the request headers
:return: The request headers
:rtype: dict
"""
headers = {'Accept': 'application/json; version={0}'.format(self._version),
'Content-Type': 'application/json'}
if self._token is not None:
headers['Authorization'] = 'Bearer {0}'.format(self._token)
return headers

@classmethod
def _build_url_params(cls, params=None):
"""
Build the URL params
:param params: URL parameters
:type params: str
:return: The url params
:rtype: string
"""
url_params = ''
if params:
url_params = '?{0}'.format(urllib.urlencode(params))
return url_params

def _cache_token(self):
"""
Caches the JWT
:return: None
:rtype: NoneType
"""
if self._volatile_client is not None:
self._volatile_client.set(self._key, self._token, 300)

def _prepare(self, **kwargs):
"""
Prepares the call:
Expand All @@ -111,17 +148,10 @@ def _prepare(self, **kwargs):
if self.client_id is not None and self._token is None:
self._connect()

headers = {'Accept': 'application/json; version={0}'.format(self._version),
'Content-Type': 'application/json'}
if self._token is not None:
headers['Authorization'] = 'Bearer {0}'.format(self._token)

params = ''
if 'params' in kwargs and kwargs['params'] is not None:
params = '?{0}'.format(urllib.urlencode(kwargs['params']))
headers = self._build_headers()
params = self._build_url_params(kwargs.get('params'))
url = '{0}{{0}}{1}'.format(self._url, params)
if self._volatile_client is not None:
self._volatile_client.set(self._key, self._token, 300)
self._cache_token() # Volatile cache might have expired or the key is gone

return headers, url

Expand Down Expand Up @@ -171,7 +201,7 @@ def _process(self, response, overrule_raw=False):
else:
raise HttpException(status_code, message)

def _call(self, api, params, fct, **kwargs):
def _call(self, api, params, fct, timeout=None, **kwargs):
if not api.endswith('/'):
api = '{0}/'.format(api)
if not api.startswith('/'):
Expand All @@ -181,7 +211,7 @@ def _call(self, api, params, fct, **kwargs):
first_connect = self._token is None
headers, url = self._prepare(params=params)
try:
return self._process(fct(url=url.format(api), headers=headers, verify=self._verify, **kwargs))
return self._process(fct(url=url.format(api), headers=headers, verify=self._verify, timeout=timeout, **kwargs))
except HttpForbiddenException:
if self._volatile_client is not None:
self._volatile_client.delete(self._key)
Expand All @@ -195,8 +225,8 @@ def _call(self, api, params, fct, **kwargs):
self._volatile_client.delete(self._key)
raise

@staticmethod
def get_instance(connection_info, cache_store=None, version=6):
@classmethod
def get_instance(cls, connection_info, cache_store=None, version=6):
"""
Retrieve an OVSClient instance to the connection information passed
:param connection_info: Connection information, includes: 'host', 'port', 'client_id', 'client_secret'
Expand All @@ -214,11 +244,11 @@ def get_instance(connection_info, cache_store=None, version=6):
'client_id': (str, None),
'client_secret': (str, None),
'local': (bool, None, False)})
return OVSClient(ip=connection_info['host'],
port=connection_info['port'],
credentials=(connection_info['client_id'], connection_info['client_secret']),
version=version,
cache_store=cache_store)
return cls(ip=connection_info['host'],
port=connection_info['port'],
credentials=(connection_info['client_id'], connection_info['client_secret']),
version=version,
cache_store=cache_store)

def get(self, api, params=None):
"""
Expand Down Expand Up @@ -263,28 +293,3 @@ def delete(self, api, params=None):
:param params: Additional query parameters, eg: _dynamics
"""
return self._call(api=api, params=params, fct=requests.delete)

def wait_for_task(self, task_id, timeout=None):
"""
Waits for a task to complete
:param task_id: Task to wait for
:param timeout: Time to wait for task before raising
"""
start = time.time()
finished = False
previous_metadata = None
while finished is False:
if timeout is not None and timeout < (time.time() - start):
raise RuntimeError('Waiting for task {0} has timed out.'.format(task_id))
task_metadata = self.get('/tasks/{0}/'.format(task_id))
finished = task_metadata['status'] in ('FAILURE', 'SUCCESS')
if finished is False:
if task_metadata != previous_metadata:
self._logger.debug('Waiting for task {0}, got: {1}'.format(task_id, task_metadata))
previous_metadata = task_metadata
else:
self._logger.debug('Still waiting for task {0}...'.format(task_id))
time.sleep(1)
else:
self._logger.debug('Task {0} finished, got: {1}'.format(task_id, task_metadata))
return task_metadata['successful'], task_metadata['result']
45 changes: 45 additions & 0 deletions src/api/client/ovsclient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright (C) 2018 iNuron NV
#
# This file is part of Open vStorage Open Source Edition (OSE),
# as available from
#
# http://www.openvstorage.org and
# http://www.openvstorage.com.
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License v3 (GNU AGPLv3)
# as published by the Free Software Foundation, in version 3 as it comes
# in the LICENSE.txt file of the Open vStorage OSE distribution.
#
# Open vStorage is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY of any kind.
import time
from .baseclient import BaseClient


class OVSClient(BaseClient):

def wait_for_task(self, task_id, timeout=None):
"""
Waits for a task to complete
:param task_id: Task to wait for
:param timeout: Time to wait for task before raising
"""
start = time.time()
finished = False
previous_metadata = None
while finished is False:
if timeout is not None and timeout < (time.time() - start):
raise RuntimeError('Waiting for task {0} has timed out.'.format(task_id))
task_metadata = self.get('/tasks/{0}/'.format(task_id))
finished = task_metadata['status'] in ('FAILURE', 'SUCCESS')
if finished is False:
if task_metadata != previous_metadata:
self._logger.debug('Waiting for task {0}, got: {1}'.format(task_id, task_metadata))
previous_metadata = task_metadata
else:
self._logger.debug('Still waiting for task {0}...'.format(task_id))
time.sleep(1)
else:
self._logger.debug('Task {0} finished, got: {1}'.format(task_id, task_metadata))
return task_metadata['successful'], task_metadata['result']
54 changes: 54 additions & 0 deletions src/api/client/simpleclient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (C) 2018 iNuron NV
#
# This file is part of Open vStorage Open Source Edition (OSE),
# as available from
#
# http://www.openvstorage.org and
# http://www.openvstorage.com.
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License v3 (GNU AGPLv3)
# as published by the Free Software Foundation, in version 3 as it comes
# in the LICENSE.txt file of the Open vStorage OSE distribution.
#
# Open vStorage is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY of any kind.
import base64
from .baseclient import BaseClient


class SimpleClient(BaseClient):
"""
Simple API client
- No JWT
- No caching
- No versioning
- No verification
- Authorization with username/password
"""
def __init__(self, ip, port, credentials=None):
"""
Initializes the object with credentials and connection information
:param ip: IP to which to connect
:type ip: str
:param port: Port on which to connect
:type port: int
:param credentials: Credentials to connect
:type credentials: tuple
:return: None
:rtype: NoneType
"""
super(SimpleClient, self).__init__(ip, port, credentials)
self._url = 'https://{0}:{1}/'.format(ip, port)

def _build_headers(self):
"""
Builds the request headers
:return: The request headers
:rtype: dict
"""
headers = {'Accept': 'application/json; version={0}'.format(self._version),
'Content-Type': 'application/json'}
if self._token is not None:
headers['Authorization'] = 'Basic {0}'.format(base64.b64encode('{0}:{1}'.format(self.client_id, self.client_secret)).strip())
return headers
Empty file added src/api/decorators/__init__.py
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from ovs_extensions.dal.base import ObjectNotFoundException


class HTTPRequestDecorators(object):
class HTTPRequestFlaskDecorators(object):
"""
Class with decorator functionality for HTTP requests
"""
Expand Down Expand Up @@ -144,3 +144,4 @@ def new_function(*args, **kwargs):
new_function.__name__ = f.__name__
new_function.__module__ = f.__module__
return new_function

55 changes: 55 additions & 0 deletions src/api/decorators/generic_requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright (C) 2017 iNuron NV
#
# This file is part of Open vStorage Open Source Edition (OSE),
# as available from
#
# http://www.openvstorage.org and
# http://www.openvstorage.com.
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License v3 (GNU AGPLv3)
# as published by the Free Software Foundation, in version 3 as it comes
# in the LICENSE.txt file of the Open vStorage OSE distribution.
#
# Open vStorage is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY of any kind.

"""
API decorators
"""

from functools import wraps


class HTTPRequestGenericDecorators(object):

@classmethod
def wrap_data(cls, data_type='data'):
"""
Wrap the API data in a dict with given_key
Eg.
def xyz: return <Data>
@wrap_data('data_type')
def xyz: return {'data_type': <Data>}
"""

def wrapper(f):
"""
Wrapper function
"""

@wraps(f)
def new_function(*args, **kwargs):
"""
Return the
"""
results = f(*args, **kwargs)
d = {data_type: results}
if data_type != 'data': # no data key present yet
d['data'] = results
return d
return new_function

return wrapper
1 change: 1 addition & 0 deletions src/config/systemd/ovs-nbd.service
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ After=local-fs.target network-online.target
Type=simple
User=root
Group=root
SyslogIdentifier=%N
Environment=PYTHONPATH=<MODULE_PATH>
WorkingDirectory=<WD>
ExecStartPre=/usr/bin/python <SCRIPT> <NODE_ID> <NBDX>
Expand Down
Loading

0 comments on commit b32bece

Please sign in to comment.