Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fi 2311 store session ids #427

Merged
merged 11 commits into from
Feb 27, 2024
Merged

Fi 2311 store session ids #427

merged 11 commits into from
Feb 27, 2024

Conversation

rpassas
Copy link
Contributor

@rpassas rpassas commented Jan 5, 2024

Summary

Adding validator sessions to a separate table in the db (requires running migrate). At start up, the invoke validator session job will populate the table.

Comments re: adding the new table for validator sessions and session id retrieval are appreciated.

Copy link

codecov bot commented Jan 5, 2024

Codecov Report

Attention: Patch coverage is 89.79592% with 5 lines in your changes are missing coverage. Please review.

Project coverage is 77.79%. Comparing base (123ab58) to head (416d0e2).

Files Patch % Lines
lib/inferno/dsl/fhir_resource_validation.rb 83.33% 2 Missing ⚠️
lib/inferno/jobs/invoke_validator_session.rb 0.00% 2 Missing ⚠️
lib/inferno/entities/validator_session.rb 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #427      +/-   ##
==========================================
+ Coverage   77.68%   77.79%   +0.10%     
==========================================
  Files         230      233       +3     
  Lines       11510    11583      +73     
  Branches     1083     1099      +16     
==========================================
+ Hits         8942     9011      +69     
- Misses       1936     1940       +4     
  Partials      632      632              
Flag Coverage Δ
backend 93.90% <89.79%> (+0.01%) ⬆️
frontend 70.92% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@rpassas rpassas force-pushed the fi-2311-store-session-ids branch 2 times, most recently from 3387adb to 56b63d3 Compare January 9, 2024 17:51
@rpassas rpassas requested review from dehall and Jammjammjamm and removed request for dehall January 19, 2024 20:31
@rpassas rpassas marked this pull request as ready for review January 19, 2024 20:32
Copy link
Collaborator

@Jammjammjamm Jammjammjamm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs unit tests.

It doesn't look like this handles multiple validators with the same name but different suite option requirements.

session_repo.create(test_suite_id: suite_id, validator_session_id: session_id, \
validator_name:)
rescue Sequel::ValidationFailed => e
puts e.message
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to use the logger. This error can just be ignored?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. I think we should not catch and log this exception because it is not actually ignore-able. If we cannot save a validated session, it defeats the purpose.

)
end

def validator_session(test_suite_id, validator_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method needs a more descriptive name.


def validator_session(test_suite_id, validator_name)
session = self.class::Model
.find(test_suite_id:, validator_name:)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table needs an index on test_suite_id and validator_name.

super(params, ATTRIBUTES)
end

def test_suite
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not - removing

@@ -231,6 +233,9 @@ def issue_message(issue, resource)

# @private
def wrap_resource_for_hl7_wrapper(resource, profile_url)
validator_session_id = \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need a backslash here.

@@ -231,6 +233,9 @@ def issue_message(issue, resource)

# @private
def wrap_resource_for_hl7_wrapper(resource, profile_url)
validator_session_id = \
Inferno::Repositories::ValidatorSessions.new.validator_session(test_suite_id,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the circumstance under which this would return nil and rely on the instance variable? If it doesn't return nil, wouldn't the instance variable need to be updated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could imagine a situation where the startup invoke-session process fails for some reason but we still want to continue and try validating again just in case. But if that happens, this call would return nil but the instance variable would also be nil. I don't think it's possible for the instance variable to be set but not get a result from the DB.

Also, good point about updating the instance variable. We may not keep all sessions forever, so if a session expires, the next validation call will return with a new session id. If the new session id != the old one then it should be persisted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured this was in case the DB fails temporarily for some reason. Updating the instance variable makes sense here. It also gets set in operation_outcome_from_hl7_wrapped_response(), but that is after the response is received. In theory these would be sync'd up.

def validator_session(test_suite_id, validator_name)
session = self.class::Model
.find(test_suite_id:, validator_name:)
if session.nil?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think return nil if session.nil? would be a lot clearer.

@@ -3,6 +3,7 @@
require_relative 'repositories/test_groups'
require_relative 'repositories/test_suites'
require_relative 'repositories/tests'
require_relative 'repositories/validator_sessions'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure which group this belongs with, but I don't think this needs to be require'd twice.

Copy link
Collaborator

@Jammjammjamm Jammjammjamm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if two users are running a suite, a session expires, and one of them tries to create a new session while the other one is also trying to create a new session?

column :validator_name, String, null: false, size: 255
column :suite_options, String, text: true, size: 255
column :last_accessed, DateTime, null: false
index [:validator_name, :test_suite_id, :suite_options]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the columns in the index have to be in the same order as they are in the query in order for the index to work.

column :validator_session_id, String, null: false, size: 255, unique: true
column :test_suite_id, String, null: false, size: 255
column :validator_name, String, null: false, size: 255
column :suite_options, String, text: true, size: 255
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we are likely to encounter issues with the length of this field since it's serialized JSON?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid concern given the length of options in g10. I will remove the size constraint.

@dehall
Copy link
Contributor

dehall commented Feb 13, 2024

What happens if two users are running a suite, a session expires, and one of them tries to create a new session while the other one is also trying to create a new session?

The hl7 validator wrapper itself doesn't have any special logic to "deconflict" that so there would be two new sessions created on that side. Depending on the sequence the two users do things in, I could believe either the cached session ID value flip-flops back and forth as each user makes subsequent validation calls, or the second one just overwrites the first and both users wind up using that. I don't think anything will break in either case, but we can think about the right places to check/save the session ID

@dehall
Copy link
Contributor

dehall commented Feb 13, 2024

I can't add a comment to the specific line since it's not modified, but I think one thing still missing is saving the session ID that comes back from the validator service to the DB if it's different from the one that was sent over (ie, if the first session expired and a new one was spun up). Currently the check that caches the session ID just as an instance variable is in lib/inferno/dsl/fhir_resource_validation.rb : operation_outcome_from_hl7_wrapped_response (line 279 or so)

@rpassas
Copy link
Contributor Author

rpassas commented Feb 16, 2024

What happens if two users are running a suite, a session expires, and one of them tries to create a new session while the other one is also trying to create a new session?

The hl7 validator wrapper itself doesn't have any special logic to "deconflict" that so there would be two new sessions created on that side. Depending on the sequence the two users do things in, I could believe either the cached session ID value flip-flops back and forth as each user makes subsequent validation calls, or the second one just overwrites the first and both users wind up using that. I don't think anything will break in either case, but we can think about the right places to check/save the session ID

I think if both users tried at the same time, calling validateSource with the expired ID passed in, initializeValidator would generate a new session and engine twice. Subsequently, they would just start using the same session after checking the DB for an existing session. I agree with Dylan that I don't think it would cause any problems, but it may allow for the possibility that an extra session is created and ignored until it expires.

Do we think this merits testing?


primary_key [:id]

index [:validator_session_id], :name=>:sqlite_autoindex_validator_sessions_2, :unique=>true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having this index name isn't great. If you replace 'unique: truewithindex [:validator_session_id], unique: true` does that get rid of it?

validators.each_with_index do |validator, index|
if validator.is_a? Inferno::DSL::FHIRResourceValidation::Validator
Inferno::Jobs.perform(Inferno::Jobs::InvokeValidatorSession, suite.id, name.to_s, index)
Inferno::Jobs.perform(Inferno::Jobs::InvokeValidatorSession, suite.id, name.to_s, index,
required_suite_options)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem like there is currently any code in core that exercises creating validator sessions with options. Have you tried this with Dylan's g10 PR? It doesn't work when I try it with this change.

13:01:46 worker.1  | E, [2024-02-20T13:01:46.587085 #51795] ERROR -- : SQLite3::SQLException: no such column: us_core_version: SELECT * FROM `validator_sessions` WHERE ((`test_suite_id` = 'g10_certification') AND (`validator_name` = 'default') AND (`suite_options` = (`us_core_version` = 'us_core_4'))) LIMIT 1
13:01:46 worker.1  | 2024-02-20T18:01:46.594Z pid=51795 tid=9gb class=Inferno::Jobs::InvokeValidatorSession jid=55fc3b4eebfae2b253df5e6d elapsed=0.009 INFO: fail
13:01:46 worker.1  | 2024-02-20T18:01:46.594Z pid=51795 tid=9gb WARN: {"context":"Job raised exception","job":{"retry":true,"queue":"default","args":["g10_certification","default",1,null],"class":"Inferno::Jobs::InvokeValidatorSession","jid":"55fc3b4eebfae2b253df5e6d","created_at":1708451870.651393,"enqueued_at":1708452106.580876,"error_message":"SQLite3::SQLException: no such column: us_core_version","error_class":"Sequel::DatabaseError","failed_at":1708451870.684702,"retry_count":3,"retried_at":1708451988.8564491}}
13:01:46 worker.1  | 2024-02-20T18:01:46.594Z pid=51795 tid=9gb WARN: Sequel::DatabaseError: SQLite3::SQLException: no such column: us_core_version

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the query it looks like it's converting the dict to an invalid query structure.

SELECT * FROM `validator_sessions` WHERE ((`test_suite_id` = 'g10_certification') AND (`validator_name` = 'default') AND (`suite_options` = (`us_core_version` = 'us_core_4')))

Specifically the (`suite_options` = (`us_core_version` = 'us_core_4')) part.

We may have to turn the options dict into a string before sending it to the database

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's also important to verify that it is being properly serialized/deserialized when being passed to the job.

@dehall
Copy link
Contributor

dehall commented Feb 23, 2024

It doesn't look like this handles multiple validators with the same name but different suite option requirements.

@Jammjammjamm this was an old comment but I think I just now grasped it in a working session with @rpassas . Is it possible for two validators to have the same name but different requirements within a single test suite? Since requirements is a field on the Validator instance, it seems to me like the minimum "unique key" for a validator is just [test suite id, validator name].

@Jammjammjamm
Copy link
Collaborator

@Jammjammjamm this was an old comment but I think I just now grasped it in a working session with @rpassas . Is it possible for two validators to have the same name but different requirements within a single test suite? Since requirements is a field on the Validator instance, it seems to me like the minimum "unique key" for a validator is just [test suite id, validator name].

Yes, we rely on this currently in g10.

https://github.com/onc-healthit/onc-certification-g10-test-kit/blob/main/lib/onc_certification_g10_test_kit.rb#L81

@dehall
Copy link
Contributor

dehall commented Feb 26, 2024

Of course I forgot that [test suite id, validator name] points to an array of validators, not a single validator. That's why InvokeValidatorSession job takes the index of that array as a parameter. But then to do lookups later each Validator instance doesn't know its own index in that array, but within a given test suite the combination [name, requirements] will be unique.

Rob's latest changes look like they should handle that properly now (though I haven't run the code to test yet)

Copy link
Collaborator

@Jammjammjamm Jammjammjamm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I try this fresh with Dylan's g10 PR I get:

11:11:09 worker.1  | 2024-02-26T16:11:09.037Z pid=55352 tid=1j0 class=Inferno::Jobs::InvokeValidatorSession jid=717fe026905ef24320b53083 INFO: start
11:11:09 worker.1  | 2024-02-26T16:11:09.039Z pid=55352 tid=1ko class=Inferno::Jobs::InvokeValidatorSession jid=8bc5f2b5aaddb01b1f30c6f9 INFO: start
11:11:09 worker.1  | 2024-02-26T16:11:09.037Z pid=55352 tid=1hw class=Inferno::Jobs::InvokeValidatorSession jid=a4d185bd97e9de4115bfbda8 INFO: start
11:11:09 worker.1  | 2024-02-26T16:11:09.037Z pid=55352 tid=1i8 class=Inferno::Jobs::InvokeValidatorSession jid=3bc5dd915949cb64ed1d885d INFO: start
11:11:23 worker.1  | E, [2024-02-26T11:11:23.328527 #55352] ERROR -- : InvokeValidatorSession - error from validator:
11:11:23 worker.1  | 2024-02-26T16:11:23.328Z pid=55352 tid=1ko class=Inferno::Jobs::InvokeValidatorSession jid=8bc5f2b5aaddb01b1f30c6f9 elapsed=14.289 INFO: done
11:11:25 worker.1  | E, [2024-02-26T11:11:25.742478 #55352] ERROR -- : InvokeValidatorSession - error from validator:
11:11:25 worker.1  | 2024-02-26T16:11:25.742Z pid=55352 tid=1i8 class=Inferno::Jobs::InvokeValidatorSession jid=3bc5dd915949cb64ed1d885d elapsed=16.705 INFO: done
11:11:26 worker.1  | E, [2024-02-26T11:11:26.083959 #55352] ERROR -- : InvokeValidatorSession - error from validator:
11:11:26 worker.1  | 2024-02-26T16:11:26.084Z pid=55352 tid=1hw class=Inferno::Jobs::InvokeValidatorSession jid=a4d185bd97e9de4115bfbda8 elapsed=17.046 INFO: done

After stopping and restarting inferno, but leaving the services running, I get:

11:12:52 worker.1  | 2024-02-26T16:12:52.882Z pid=55833 tid=13x class=Inferno::Jobs::InvokeValidatorSession jid=8f02737ccc4c6b655706c0df INFO: start
11:12:52 worker.1  | 2024-02-26T16:12:52.884Z pid=55833 tid=135 class=Inferno::Jobs::InvokeValidatorSession jid=ad7e3874268a09ab365468f9 INFO: start
11:12:52 worker.1  | 2024-02-26T16:12:52.884Z pid=55833 tid=14l class=Inferno::Jobs::InvokeValidatorSession jid=15ec110ad367c8662411234a INFO: start
11:12:52 worker.1  | 2024-02-26T16:12:52.884Z pid=55833 tid=15l class=Inferno::Jobs::InvokeValidatorSession jid=107302688ee036e4270f0220 INFO: start
11:12:53 worker.1  | E, [2024-02-26T11:12:53.355174 #55833] ERROR -- : InvokeValidatorSession - error from validator: "/home/ktor/.fhir/packages/1d3a3a7e-aea5-4340-8ab9-b13f93966d1f"
11:12:53 worker.1  | 2024-02-26T16:12:53.355Z pid=55833 tid=135 class=Inferno::Jobs::InvokeValidatorSession jid=ad7e3874268a09ab365468f9 elapsed=0.471 INFO: done
11:12:53 worker.1  | E, [2024-02-26T11:12:53.449162 #55833] ERROR -- : InvokeValidatorSession - error from validator: "/home/ktor/.fhir/packages/1d3a3a7e-aea5-4340-8ab9-b13f93966d1f"
11:12:53 worker.1  | 2024-02-26T16:12:53.449Z pid=55833 tid=15l class=Inferno::Jobs::InvokeValidatorSession jid=107302688ee036e4270f0220 elapsed=0.565 INFO: done
11:12:53 worker.1  | E, [2024-02-26T11:12:53.465213 #55833] ERROR -- : InvokeValidatorSession - error from validator: "/home/ktor/.fhir/packages/1d3a3a7e-aea5-4340-8ab9-b13f93966d1f"
11:12:53 worker.1  | 2024-02-26T16:12:53.465Z pid=55833 tid=14l class=Inferno::Jobs::InvokeValidatorSession jid=15ec110ad367c8662411234a elapsed=0.581 INFO: done
11:12:53 worker.1  | E, [2024-02-26T11:12:53.667094 #55833] ERROR -- : InvokeValidatorSession - error from validator: "/home/ktor/.fhir/packages/1d3a3a7e-aea5-4340-8ab9-b13f93966d1f"
11:12:53 worker.1  | 2024-02-26T16:12:53.667Z pid=55833 tid=13x class=Inferno::Jobs::InvokeValidatorSession jid=8f02737ccc4c6b655706c0df elapsed=0.785 INFO: done

change do
create_table :validator_sessions do
column :id, String, primary_key: true, null: false, size: 36
column :validator_session_id, String, null: false, size: 255 # , unique: true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't leave in commented out code.

@dehall
Copy link
Contributor

dehall commented Feb 26, 2024

Hmm I have seen that error of just a filename once or twice but never multiple times in a row. I'll look into that.

The "blank" error is tough to debug (hence FI-2476 for better error handling) but in my experience that happens when an Error is thrown in the core validator, which can either be something really broken like OutOfMemory or more likely the profile that is trying to be validated can't be found. But that shouldn't happen with our setup.

repo.save(session1_params)
end

it 'single record' do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The descriptions on these it calls don't make sense. They should read like the ones above. I think this is good to go otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

@rpassas rpassas force-pushed the fi-2311-store-session-ids branch 2 times, most recently from ccc049a to 89333d5 Compare February 27, 2024 17:08
@rpassas rpassas merged commit cfabde4 into main Feb 27, 2024
10 checks passed
@rpassas rpassas deleted the fi-2311-store-session-ids branch February 27, 2024 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants