-
Notifications
You must be signed in to change notification settings - Fork 7
Conversation
2aa98c6
to
8bcb5e4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate there are quite a lot of comments here, but I've been fairly detailed with it being the first review, so we can make sure we're all on the same page in terms of approach. Generally, this looks good though!
One point I wanted to raise was regarding logging. I appreciate we've still got an outstanding task to define standards around levels, etc. and the required output format, but we should be trying to ensure that we include appropriate logging as we go, rather than having to include it afterwards. If you use the standard logging
module with sensible log levels, it should mean it's relatively easy to convert to whatever format we settle on, and we won't have to actually add the log messages later. (CC @ismail-s) One good example here would be that I think we should log at warning level if we find more than one result for an SDS query - the spec says we shouldn't, and we can safely continue using the first result, but we should flag the fact that it's happened.
mhs-reference-implementation/mhs/SDSLookup/tests/test_sdsclient.py
Outdated
Show resolved
Hide resolved
result = await client._mhs_details_lookup(PARTY_KEY, SERVICE_ID) | ||
|
||
self.assertIsNotNone(result) | ||
self.assertEqual(result[0]['attributes']['nhsMHSEndPoint'], ['https://vpn-client-1411.opentest.hscic.gov.uk/']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should probably test that the full list of attributes is returned, since it's one of the acceptance criteria on the story. Also, we need to verify that attributes not on the list are filtered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the test to check for all attributes, however because of the mhs attribute call returning 2 sets of attributes if I check the values for an exact match it will fail half the time (due to responses including a unique id). I've included all the attributes bar the two unique ones and still checked that they are present, just not their values. Does this seem reasonable?
Also added length checking to the attribute list to ensure it contains exactly the number we need
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this does the job, but isn't the MHS attribute call returning 2 sets of attributes because that's what we have in the dummy LDAP server data? Couldn't we edit it so that it doesn't return two sets for this query?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point, I've removed the duplicate data and added all attribute checks in so the test should be complete now.
mhs-reference-implementation/mhs/SDSLookup/tests/test_sdsclient.py
Outdated
Show resolved
Hide resolved
mhs-reference-implementation/mhs/SDSLookup/tests/test_sdsclient.py
Outdated
Show resolved
Hide resolved
mhs-reference-implementation/mhs/SDSLookup/tests/test_sdsclient.py
Outdated
Show resolved
Hide resolved
mhs-reference-implementation/mhs/SDSLookup/tests/test_sds_handler.py
Outdated
Show resolved
Hide resolved
8bcb5e4
to
0817497
Compare
This commit includes the code for the SDS lookup client. Including the following: - SDSClient: This handles LDAP calls to the directory - SDSHandler which handles the interaction with the SDSClient, in future this will consult a cache for SDS data - Test code which emulates the opentest directory to provide responses for unit tests - .gitignore for credentials
8070641
to
564ff81
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bear in mind that I didn't look at Gareth's comments before doing my review.
async def test_tls(self): | ||
pass | ||
# client = sds.SDSClient(DEV_ADDRESS) | ||
# result = await client.get_mhs_details(ODS_CODE, SERVICE_ID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented-out code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed in a previous commit
@@ -9,6 +9,7 @@ verify_ssl = true | |||
integration-adaptors-common = {editable = true,path = "./../common"} | |||
requests = "*" | |||
tornado = "*" | |||
ldap3 = "*" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General question not specific to this PR, but should we be specifying versions (ie the major version)?
# These need replacing as the appropriate TLS certs are received for PTL | ||
PRIVATE_KEY = pathlib.Path(ROOT_DIR) / 'data' / 'certs' / 'client.key' | ||
LOCAL_CERT_FILE = pathlib.Path(ROOT_DIR) / 'data' / 'certs' / 'client.cert' | ||
CA_CERTS_FILE = pathlib.Path(ROOT_DIR) / 'data' / 'certs' / 'client.cert' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is the same as the previous constant and may change, then simpler to do:
CA_CERTS_FILE = pathlib.Path(ROOT_DIR) / 'data' / 'certs' / 'client.cert' | |
CA_CERTS_FILE = LOCAL_CERT_FILE |
for the timebeing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ultimately these will be different files, I'll update the paths to demonstrate this and I've added a constant holding the certs dir path
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed these completely actually
common/utilities/test_utilities.py
Outdated
coro = asyncio.coroutine(f) | ||
future = coro(*args, **kwargs) | ||
loop = asyncio.get_event_loop() | ||
loop.run_until_complete(future) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like asyncio.new_event_loop()
would be better for test isolation.
And then it looks like asyncio.run(future)
would be better than that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've replaced the loop = ...
and loop.run_until_complete(future)
with just asyncio.run(future)
. Is this the correct interpretation that we don't need asyncio.new_event_loop()
?
f"ods: {ods_code} - interaction: {interaction_id}") | ||
return details[0]['attributes'] | ||
|
||
async def _accredited_system_lookup(self, ods_code: str, interaction_id: str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a general comment (not sure that we have/want a rule around whether we want type hints everywhere or not), if you want to type hint the return type, then you can do async def blah() -> someType:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
'nhsMhsFQDN': 'vpn-client-1411.opentest.hscic.gov.uk', | ||
'nhsMhsSvcIA': 'urn:nhs:names:services:psis:MCCI_IN010000UK13', | ||
'nhsProductKey': '7374', | ||
# 'uniqueIdentifier': ['S918999410559'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented-out code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
'nhsMHSSyncReplyMode': 'MSHSignalsOnly', | ||
'nhsMHsIN': 'MCCI_IN010000UK13', | ||
'nhsMHsSN': 'urn:nhs:names:services:psis', | ||
# 'nhsMhsCPAId': 'S918999410559', # Exclude this value since there seems to be two responses causing errors on this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sds call should return exactly 1 value for this lookup, however my opentest instance seems to return 2, this had been copied to the test data and caused an issue due to this value being an unique identifier. I've deleted the second instance in the test data so I've readded these attributes
'nhsMhsFQDN': 'vpn-client-1411.opentest.hscic.gov.uk', | ||
'nhsMhsSvcIA': 'urn:nhs:names:services:psis:MCCI_IN010000UK13', | ||
'nhsProductKey': '7374', | ||
# 'uniqueIdentifier': ['S918999410559'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented-out code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
import mhs.routing.sds as sds | ||
import mhs.routing.tests.ldap_mocks as mocks | ||
from utilities.test_utilities import async_test | ||
import mhs.routing.routing_exception as re |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: re
is the name of the regex stdlib module, so it's a little confusing to use that name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to just use the module name
|
||
|
||
class TestMHSAttributeLookupHandler(TestCase): | ||
opentest_expected_reliability = {'nhsMHSAckRequested': 'always', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this variable being used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, it's no longer being used since we're using the full attribute set defined out of the class
raise re.RoutingException('No response from accredited system lookup') | ||
|
||
if len(accredited_system_lookup) > 1: | ||
logging.warning(f"More than one accredited system details returned on inputs: " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No issues with the log statements added, but we also need to think about how we would trace successful flows through the code, etc. Given log aggregation tools are pretty standard nowadays, our logging approach should be "more is better". As long as we make use of the correct log levels to distinguish between what we're logging, an admin using Splunk, etc. should be able to filter our log messages easily enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added the module reference Ismail suggested and added a couple extra logs around message IDs. Noted that we should try to consist logs throughout
mhs-reference-implementation/mhs/SDSLookup/tests/test_sdsclient.py
Outdated
Show resolved
Hide resolved
raise re.RoutingException('No response from accredited system lookup') | ||
|
||
if len(accredited_system_lookup) > 1: | ||
logging.warning(f"More than one accredited system details returned on inputs: " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should start including logger = logging.getLogger(__name__)
in files that we log in, so as to include the location of the file the log message came from. This is a standard line from the Python website.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, agreed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this
I'm noticing in the code that there is a mixture of terminology used for the interation ID. It is called "Service ID" in places, and "interaction ID" in others. Can we standardise on "interaction ID" as this is commonly accepted term. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good, just a couple of comments on logging that I think we should address before this goes in.
self.connection = sds_connection | ||
self.timeout = timeout | ||
|
||
async def get_mhs_details(self, ods_code: str, interaction_id: str) -> Dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another point: Dict
can be made more specific like eg Dict[keyType, valueType]
. As can other container types eg List[str]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I'd noticed this when looking at type hints, I refrained from using it here since the value type can be more than 1 type but I think in the future we should probably include the key/value types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, you could use Any
, or Union[someType, someOtherType]
, though that could get long-winded.
return response | ||
|
||
async def _mhs_details_lookup(self, party_key: str, interaction_id: str): | ||
async def _mhs_details_lookup(self, party_key: str, interaction_id: str) -> List: | ||
""" | ||
Given a party key and a service, this will return an object containing the attributes of that party key, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming interaction id and service mean the same thing (not familiar with all this terminology yet).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is correct, I will change this as Brian has specified previously that we should try to stick to interaction id, must have missed this one!
|
||
def build_sds_connection(ldap_address: str) -> ldap3.Connection: | ||
""" | ||
Given an ip address this will return a ldap3 connection object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pedantic/minor: do you have to pass an ip address or any address?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the ldap lib it seems like any valid ldap address with be accepted, I'll update to reflect this
This PR contains SDS Lookup implementation for RT-66. Primarily includes the following: