Commit ef3e16db authored by Vitali Stupin's avatar Vitali Stupin
Browse files

Subsystem adding

parent 4c301a48
......@@ -3,7 +3,8 @@
"""This is a module for X-Road Central Server API.
This module allows:
* adding new members to the X-Road Central Server.
* adding new member to the X-Road Central Server.
* adding new subsystem to the X-Road Central Server.
"""
import logging
......@@ -61,19 +62,49 @@ def get_member_class_id(cur, member_class):
return None
def member_exists(cur, member_code):
def member_exists(cur, class_id, member_code):
"""Check if member exists in Central Server"""
cur.execute(
"""
select exists(
select * from security_server_clients
where type='XRoadMember' and member_code=%(str)s
where type='XRoadMember' member_class_id=%(class_id)s
and member_code=%(member_code)s
)
""", {'str': member_code})
""", {'class_id': class_id, 'member_code': member_code})
rec = cur.fetchone()
return rec[0]
def subsystem_exists(cur, member_id, subsystem_code):
"""Check if subsystem exists in Central Server"""
cur.execute(
"""
select exists(
select * from security_server_clients
where type='Subsystem' and xroad_member_id=%(member_id)s
and subsystem_code=%(subsystem_code)s
)
""", {'member_id': member_id, 'subsystem_code': subsystem_code})
rec = cur.fetchone()
return rec[0]
def get_member_data(cur, class_id, member_code):
"""Get member data from Central Server"""
cur.execute(
"""
select id, name
from security_server_clients
where type='XRoadMember' and member_class_id=%(class_id)s
and member_code=%(member_code)s
""", {'class_id': class_id, 'member_code': member_code})
rec = cur.fetchone()
if rec:
return {'id': rec[0], 'name': rec[2]}
return None
def get_utc_time(cur):
"""Get current time in UTC timezone from Central Server database"""
cur.execute("""select current_timestamp at time zone 'UTC'""")
......@@ -102,6 +133,28 @@ def add_identifier(cur, **kwargs):
return cur.fetchone()[0]
def add_subsystem_identifier(cur, **kwargs):
"""Add new X-Road subsystem identifier to Central Server
Required keyword arguments:
member_class, member_code, subsystem_code, utc_time
"""
cur.execute(
"""
insert into identifiers (
object_type, xroad_instance, member_class, member_code, subsystem_code, type,
created_at, updated_at
) values (
'SUBSYSTEM', (select value from system_parameters where key='instanceIdentifier'),
%(class)s, %(member_code)s, %(subsystem_code)s, 'ClientId', %(time)s, %(time)s
) returning id
""", {
'class': kwargs['member_class'], 'member_code': kwargs['member_code'],
'subsystem_code': kwargs['member_code'], 'time': kwargs['utc_time']}
)
return cur.fetchone()[0]
def add_client(cur, **kwargs):
"""Add new X-Road client to Central Server
......@@ -124,6 +177,27 @@ def add_client(cur, **kwargs):
)
def add_subsystem_client(cur, **kwargs):
"""Add new X-Road subsystem as a client to Central Server
Required keyword arguments:
subsystem_code, member_id, identifier_id, utc_time
"""
cur.execute(
"""
insert into security_server_clients (
subsystem_code, xroad_member_id, server_client_id, type, created_at, updated_at
) values (
%(subsystem_code)s, %(member_id)s, %(identifier_id)s, 'Subsystem', %(time)s,
%(time)s
)
""", {
'subsystem_code': kwargs['subsystem_code'], 'member_id': kwargs['member_id'],
'identifier_id': kwargs['identifier_id'], 'time': kwargs['utc_time']
}
)
def add_client_name(cur, **kwargs):
"""Add new X-Road client name to Central Server
......@@ -163,7 +237,7 @@ def add_member(member_code, member_name, member_class, json_data):
'http_status': 400, 'code': 'INVALID_MEMBER_CLASS',
'msg': 'Provided Member Class does not exist'}
if member_exists(cur, member_code):
if member_exists(cur, class_id, member_code):
LOGGER.warning(
'MEMBER_EXISTS: Provided Member already exists '
'(Request: %s)', json_data)
......@@ -187,10 +261,71 @@ def add_member(member_code, member_name, member_class, json_data):
conn.commit()
LOGGER.info(
'Added new member: member_code=%s, member_name=%s, member_class=%s',
'Added new Member: member_code=%s, member_name=%s, member_class=%s',
member_code, member_name, member_class)
return {'http_status': 201, 'code': 'CREATED', 'msg': 'New member added'}
return {'http_status': 201, 'code': 'CREATED', 'msg': 'New Member added'}
def add_subsystem(member_class, member_code, subsystem_code, json_data):
"""Add new X-Road subsystem to Central Server"""
conf = get_db_conf()
if not conf['username'] or not conf['password'] or not conf['database']:
LOGGER.error('DB_CONF_ERROR: Cannot access database configuration')
return {
'http_status': 500, 'code': 'DB_CONF_ERROR',
'msg': 'Cannot access database configuration'}
with get_db_connection(conf) as conn:
with conn.cursor() as cur:
class_id = get_member_class_id(cur, member_class)
if class_id is None:
LOGGER.warning(
'INVALID_MEMBER_CLASS: Provided Member Class does not exist '
'(Request: %s)', json_data)
return {
'http_status': 400, 'code': 'INVALID_MEMBER_CLASS',
'msg': 'Provided Member Class does not exist'}
member_data = get_member_data(cur, class_id, member_code)
if member_data is None:
LOGGER.warning(
'INVALID_MEMBER: Provided Member does not exist '
'(Request: %s)', json_data)
return {
'http_status': 400, 'code': 'INVALID_MEMBER',
'msg': 'Provided Member does not exist'}
if subsystem_exists(cur, member_data['id'], subsystem_code):
LOGGER.warning(
'SUBSYSTEM_EXISTS: Provided Subsystem already exists '
'(Request: %s)', json_data)
return {
'http_status': 409, 'code': 'SUBSYSTEM_EXISTS',
'msg': 'Provided Subsystem already exists'}
# Timestamps must be in UTC timezone
utc_time = get_utc_time(cur)
identifier_id = add_subsystem_identifier(
cur, member_class=member_class, member_code=member_code,
subsystem_code=subsystem_code, utc_time=utc_time)
add_subsystem_client(
cur, subsystem_code=subsystem_code, member_id=member_data['id'],
identifier_id=identifier_id, utc_time=utc_time)
add_client_name(
cur, member_name=member_data['name'], identifier_id=identifier_id,
utc_time=utc_time)
conn.commit()
LOGGER.info(
'Added new Subsystem: member_class=%s, member_code=%s, subsystem_code=%s',
member_class, member_code, subsystem_code)
return {'http_status': 201, 'code': 'CREATED', 'msg': 'New Subsystem added'}
def make_response(data):
......@@ -245,8 +380,40 @@ class MemberApi(Resource):
try:
response = add_member(member_code, member_name, member_class, json_data)
except psycopg2.Error:
LOGGER.error('DB_ERROR: Unclassified database error')
except psycopg2.Error as err:
LOGGER.error('DB_ERROR: Unclassified database error: %s', err)
response = {
'http_status': 500, 'code': 'DB_ERROR',
'msg': 'Unclassified database error'}
return make_response(response)
class SubsystemApi(Resource):
"""Subsystem API class for Flask"""
@staticmethod
def post():
"""POST method"""
json_data = request.get_json(force=True)
LOGGER.info('Incoming request: %s', json_data)
(member_class, fault_response) = get_input(json_data, 'member_class')
if member_class is None:
return make_response(fault_response)
(member_code, fault_response) = get_input(json_data, 'member_code')
if member_code is None:
return make_response(fault_response)
(subsystem_code, fault_response) = get_input(json_data, 'subsystem_code')
if subsystem_code is None:
return make_response(fault_response)
try:
response = add_subsystem(member_class, member_code, subsystem_code, json_data)
except psycopg2.Error as err:
LOGGER.error('DB_ERROR: Unclassified database error: %s', err)
response = {
'http_status': 500, 'code': 'DB_ERROR',
'msg': 'Unclassified database error'}
......
......@@ -3,7 +3,7 @@
import logging
from flask import Flask
from flask_restful import Api
from csapi import MemberApi
from csapi import MemberApi, SubsystemApi
handler = logging.FileHandler('/var/log/xroad/csapi.log')
handler.setFormatter(logging.Formatter('%(asctime)s - %(process)d - %(levelname)s: %(message)s'))
......@@ -21,5 +21,6 @@ logger.addHandler(handler)
app = Flask(__name__)
api = Api(app)
api.add_resource(MemberApi, '/member')
api.add_resource(SubsystemApi, '/subsystem')
logger.info('Starting Central Server API')
......@@ -71,11 +71,12 @@ reconnect=true
cur = MagicMock()
cur.execute = MagicMock()
cur.fetchone = MagicMock(return_value=[True])
self.assertEqual(True, csapi.member_exists(cur, 'MEMBER_CODE'))
self.assertEqual(True, csapi.member_exists(cur, 123, 'MEMBER_CODE'))
cur.execute.assert_called_with(
"\n select exists(\n select * from security_server_clients\n"
" where type='XRoadMember' and member_code=%(str)s\n )\n"
" ", {'str': 'MEMBER_CODE'})
" where type='XRoadMember' member_class_id=%(class_id)s\n"
" and member_code=%(member_code)s\n"
" )\n ", {'class_id': 123, 'member_code': 'MEMBER_CODE'})
cur.fetchone.assert_called_once()
def test_get_utc_time(self):
......@@ -228,7 +229,7 @@ reconnect=true
mock_get_member_class_id.assert_called_with(
mock_get_db_connection().__enter__().cursor().__enter__(), 'MEMBER_CLASS')
mock_member_exists.assert_called_with(
mock_get_db_connection().__enter__().cursor().__enter__(), 'MEMBER_CODE')
mock_get_db_connection().__enter__().cursor().__enter__(), 12345, 'MEMBER_CODE')
@patch('csapi.add_client_name')
@patch('csapi.add_client')
......@@ -249,10 +250,10 @@ reconnect=true
self.assertEqual(
{
'code': 'CREATED', 'http_status': 201,
'msg': 'New member added'},
'msg': 'New Member added'},
csapi.add_member('MEMBER_CODE', 'MEMBER_NAME', 'MEMBER_CLASS', 'JSON_DATA'))
self.assertEqual([
'INFO:csapi:Added new member: member_code=MEMBER_CODE, '
'INFO:csapi:Added new Member: member_code=MEMBER_CODE, '
'member_name=MEMBER_NAME, member_class=MEMBER_CLASS'], cm.output)
mock_get_db_conf.assert_called_with()
mock_get_db_connection.assert_called_with({
......@@ -261,7 +262,7 @@ reconnect=true
mock_get_member_class_id.assert_called_with(
mock_get_db_connection().__enter__().cursor().__enter__(), 'MEMBER_CLASS')
mock_member_exists.assert_called_with(
mock_get_db_connection().__enter__().cursor().__enter__(), 'MEMBER_CODE')
mock_get_db_connection().__enter__().cursor().__enter__(), 12345, 'MEMBER_CODE')
mock_get_utc_time.assert_called_with(
mock_get_db_connection().__enter__().cursor().__enter__())
mock_add_identifier.assert_called_with(
......@@ -382,7 +383,7 @@ reconnect=true
"INFO:csapi:Response: {'http_status': 400, 'code': 'MISSING_PARAMETER', "
"'msg': 'Request parameter member_class is missing'}"], cm.output)
@patch('csapi.add_member', side_effect=psycopg2.Error)
@patch('csapi.add_member', side_effect=psycopg2.Error('DB_ERROR_MSG'))
def test_db_error_handled(self, mock_add_member):
with self.app.app_context():
with self.assertLogs(csapi.LOGGER, level='INFO') as cm:
......@@ -399,7 +400,7 @@ reconnect=true
self.assertEqual([
"INFO:csapi:Incoming request: {'member_code': 'MEMBER_CODE', 'member_name': "
"'MEMBER_NAME', 'member_class': 'MEMBER_CLASS'}",
'ERROR:csapi:DB_ERROR: Unclassified database error',
'ERROR:csapi:DB_ERROR: Unclassified database error: DB_ERROR_MSG',
"INFO:csapi:Response: {'http_status': 500, 'code': 'DB_ERROR', 'msg': "
"'Unclassified database error'}"], cm.output)
mock_add_member.assert_called_with('MEMBER_CODE', 'MEMBER_NAME', 'MEMBER_CLASS', {
......
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