Skip to content

Commit 17514dc

Browse files
authored
CP-48676: Reuse pool sessions on slave logins (#6258)
This was first introduced in #5986 but was later reverted as the mutex it introduced caused a deadlock with the DB mutex. This PR has now been reworked to use an Atomic variable, removing the need for the mutex. This PR allows pool sessions to be reused (if the flag is turned on, which it is by default). This greatly speeds up communications between hosts which is important for repetitive calls e.g. one customer was running get_servertime frequently on all hosts in a pool which was causing problems due to the length of each call. When we get this reusable session, we can optionally validate the session before using it but this increases the duration of the call, so this is off by default. Excepting a toolstack restart, the reusable session should always be valid as it is excluded from deletion in destroy_db_session
2 parents 82064d3 + e42cca1 commit 17514dc

File tree

2 files changed

+87
-13
lines changed

2 files changed

+87
-13
lines changed

ocaml/xapi/xapi_globs.ml

+16
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,12 @@ let pool_recommendations_dir = ref "/etc/xapi.pool-recommendations.d"
10751075

10761076
let disable_webserver = ref false
10771077

1078+
let reuse_pool_sessions = ref false
1079+
(* Enables the reuse of pool sessions, speeding up intrapool communication *)
1080+
1081+
let validate_reusable_pool_session = ref false
1082+
(* Validate a reusable session before each use. This is slower and should not be required *)
1083+
10781084
let test_open = ref 0
10791085

10801086
let xapi_requests_cgroup =
@@ -1707,6 +1713,16 @@ let other_options =
17071713
, (fun () -> !driver_tool)
17081714
, "Path to drivertool for selecting host driver variants"
17091715
)
1716+
; ( "reuse-pool-sessions"
1717+
, Arg.Set reuse_pool_sessions
1718+
, (fun () -> string_of_bool !reuse_pool_sessions)
1719+
, "Enable the reuse of pool sessions"
1720+
)
1721+
; ( "validate-reusable-pool-session"
1722+
, Arg.Set validate_reusable_pool_session
1723+
, (fun () -> string_of_bool !validate_reusable_pool_session)
1724+
, "Enable validation of reusable pool sessions before use"
1725+
)
17101726
]
17111727

17121728
(* The options can be set with the variable xapiflags in /etc/sysconfig/xapi.

ocaml/xapi/xapi_session.ml

+71-13
Original file line numberDiff line numberDiff line change
@@ -398,19 +398,24 @@ let is_subject_suspended ~__context ~cache subject_identifier =
398398
debug "Subject identifier %s is suspended" subject_identifier ;
399399
(is_suspended, subject_name)
400400

401+
let reusable_pool_session = Atomic.make Ref.null
402+
401403
let destroy_db_session ~__context ~self =
402404
Context.with_tracing ~__context __FUNCTION__ @@ fun __context ->
403-
Xapi_event.on_session_deleted self ;
404-
(* unregister from the event system *)
405-
(* This info line is important for tracking, auditability and client accountability purposes on XenServer *)
406-
(* Never print the session id nor uuid: they are secret values that should be known only to the user that *)
407-
(* logged in. Instead, we print a non-invertible hash as the tracking id for the session id *)
408-
(* see also task creation in context.ml *)
409-
(* CP-982: create tracking id in log files to link username to actions *)
410-
info "Session.destroy %s" (trackid self) ;
411-
Rbac_audit.session_destroy ~__context ~session_id:self ;
412-
(try Db.Session.destroy ~__context ~self with _ -> ()) ;
413-
Rbac.destroy_session_permissions_tbl ~session_id:self
405+
if self <> Atomic.get reusable_pool_session then (
406+
Xapi_event.on_session_deleted self ;
407+
(* unregister from the event system *)
408+
(* This info line is important for tracking, auditability and client accountability purposes on XenServer *)
409+
(* Never print the session id nor uuid: they are secret values that should be known only to the user that *)
410+
(* logged in. Instead, we print a non-invertible hash as the tracking id for the session id *)
411+
(* see also task creation in context.ml *)
412+
(* CP-982: create tracking id in log files to link username to actions *)
413+
info "Session.destroy %s" (trackid self) ;
414+
Rbac_audit.session_destroy ~__context ~session_id:self ;
415+
(try Db.Session.destroy ~__context ~self with _ -> ()) ;
416+
Rbac.destroy_session_permissions_tbl ~session_id:self
417+
) else
418+
info "Skipping Session.destroy for reusable pool session %s" (trackid self)
414419

415420
(* CP-703: ensure that activate sessions are invalidated in a bounded time *)
416421
(* in response to external authentication/directory services updates, such as *)
@@ -610,8 +615,8 @@ let revalidate_all_sessions ~__context =
610615
debug "Unexpected exception while revalidating external sessions: %s"
611616
(ExnHelper.string_of_exn e)
612617

613-
let login_no_password_common ~__context ~uname ~originator ~host ~pool
614-
~is_local_superuser ~subject ~auth_user_sid ~auth_user_name
618+
let login_no_password_common_create_session ~__context ~uname ~originator ~host
619+
~pool ~is_local_superuser ~subject ~auth_user_sid ~auth_user_name
615620
~rbac_permissions ~db_ref ~client_certificate =
616621
Context.with_tracing ~originator ~__context __FUNCTION__ @@ fun __context ->
617622
let create_session () =
@@ -661,6 +666,59 @@ let login_no_password_common ~__context ~uname ~originator ~host ~pool
661666
ignore (Client.Pool.get_all ~rpc ~session_id) ;
662667
session_id
663668

669+
let login_no_password_common ~__context ~uname ~originator ~host ~pool
670+
~is_local_superuser ~subject ~auth_user_sid ~auth_user_name
671+
~rbac_permissions ~db_ref ~client_certificate =
672+
Context.with_tracing ~originator ~__context __FUNCTION__ @@ fun __context ->
673+
let is_valid_session session_id =
674+
match (session_id, !Xapi_globs.validate_reusable_pool_session) with
675+
| session, _ when session = Ref.null ->
676+
false
677+
| _, false ->
678+
true
679+
| session_id, true -> (
680+
try
681+
(* Call an API function to check the session is still valid *)
682+
let rpc = Helpers.make_rpc ~__context in
683+
ignore (Client.Pool.get_all ~rpc ~session_id) ;
684+
true
685+
with Api_errors.Server_error (err, _) ->
686+
debug "%s: Invalid session: %s" __FUNCTION__ err ;
687+
false
688+
)
689+
in
690+
let create_session () =
691+
login_no_password_common_create_session ~__context ~uname ~originator ~host
692+
~pool ~is_local_superuser ~subject ~auth_user_sid ~auth_user_name
693+
~rbac_permissions ~db_ref ~client_certificate
694+
in
695+
let rec get_session () =
696+
let session = Atomic.get reusable_pool_session in
697+
if is_valid_session session then (
698+
(* Check if the session changed during validation.
699+
Use our version regardless to avoid being stuck in a loop of session creation *)
700+
if Atomic.get reusable_pool_session <> session then
701+
debug "reusable_pool_session has changed, using the original anyway" ;
702+
session
703+
) else
704+
let new_session = create_session () in
705+
if Atomic.compare_and_set reusable_pool_session session new_session then
706+
new_session
707+
else (
708+
(* someone else raced with us and created a session, destroy ours and attempt to use theirs *)
709+
destroy_db_session ~__context ~self:new_session ;
710+
(get_session [@tailcall]) ()
711+
)
712+
in
713+
if
714+
(originator, pool, is_local_superuser, uname)
715+
= (xapi_internal_originator, true, true, None)
716+
&& !Xapi_globs.reuse_pool_sessions
717+
then
718+
get_session ()
719+
else
720+
create_session ()
721+
664722
(* XXX: only used internally by the code which grants the guest access to the API.
665723
Needs to be protected by a proper access control system *)
666724
let login_no_password ~__context ~uname ~host ~pool ~is_local_superuser ~subject

0 commit comments

Comments
 (0)