Skip to content

Commit

Permalink
CP-50787 CP-51347: Support pool.sync_updates from remote_pool repo
Browse files Browse the repository at this point in the history
When a remote_pool type repository, which points to the enabled
repository in the remote pool coordinator, is set as the enabled
repository of the pool, updates can be synced from it with API
pool.sync_updates.

The username password of the remote pool coordinator is required as
parameters for pool.sync_updates to login the remote pool.

And the remote pool coordinator's host server certificate needs to be
configured in the remote_pool repository, it will be used to verify the
remote end when sending out username passwords and syncing updates from
it.

A new yum/dnf plugin "xapitoken" is introduced to set xapi token as HTTP
cookie: "session_id" for each HTTP request which downloads files from the
remote_pool repository.

Signed-off-by: Gang Ji <[email protected]>
  • Loading branch information
gangj committed Dec 11, 2024
1 parent 8e1bc73 commit 4c78c86
Show file tree
Hide file tree
Showing 21 changed files with 421 additions and 86 deletions.
12 changes: 12 additions & 0 deletions ocaml/idl/datamodel_errors.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1903,6 +1903,8 @@ let _ =
~doc:"The base url in the repository is invalid." () ;
error Api_errors.invalid_gpgkey_path ["gpgkey_path"]
~doc:"The GPG public key file name in the repository is invalid." () ;
error Api_errors.cdn_token_invalid ["message"]
~doc:"The provided CDN token or token_id is empty." () ;
error Api_errors.repository_already_exists ["ref"]
~doc:"The repository already exists." () ;
error Api_errors.bundle_repository_already_exists ["ref"]
Expand All @@ -1926,6 +1928,16 @@ let _ =
"If the bundle repository or remote_pool repository is enabled, it \
should be the only one enabled repository of the pool."
() ;
error Api_errors.update_syncing_remote_pool_coordinator_connection_failed []
~doc:
"There was an error connecting to the remote pool coordinator while \
syncing updates from it."
() ;
error Api_errors.update_syncing_remote_pool_coordinator_service_failed []
~doc:
"There was an error connecting to the server while syncing updates from \
it. The service contacted didn't reply properly."
() ;
error Api_errors.repository_is_in_use [] ~doc:"The repository is in use." () ;
error Api_errors.repository_cleanup_failed []
~doc:"Failed to clean up local repository on coordinator." () ;
Expand Down
14 changes: 14 additions & 0 deletions ocaml/idl/datamodel_pool.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,20 @@ let sync_updates =
; param_release= numbered_release "1.329.0"
; param_default= Some (VString "")
}
; {
param_type= String
; param_name= "username"
; param_doc= "The username of the remote pool"
; param_release= numbered_release "24.39.0-next"
; param_default= Some (VString "")
}
; {
param_type= String
; param_name= "password"
; param_doc= "The password of the remote pool"
; param_release= numbered_release "24.39.0-next"
; param_default= Some (VString "")
}
]
~result:(String, "The SHA256 hash of updateinfo.xml.gz")
~allowed_roles:(_R_POOL_OP ++ _R_CLIENT_CERT)
Expand Down
2 changes: 1 addition & 1 deletion ocaml/xapi-cli-server/cli_frontend.ml
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ let rec cmdtable_data : (string * cmd_spec) list =
; ( "pool-sync-updates"
, {
reqd= []
; optn= ["force"; "token"; "token-id"]
; optn= ["force"; "token"; "token-id"; "username"; "password"]
; help= "Sync updates from remote YUM repository, pool-wide."
; implementation= No_fd Cli_operations.pool_sync_updates
; flags= []
Expand Down
3 changes: 3 additions & 0 deletions ocaml/xapi-cli-server/cli_operations.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1833,8 +1833,11 @@ let pool_sync_updates printer rpc session_id params =
let force = get_bool_param params "force" in
let token = get_param params "token" ~default:"" in
let token_id = get_param params "token-id" ~default:"" in
let username = get_param params "username" ~default:"" in
let password = get_param params "password" ~default:"" in
let hash =
Client.Pool.sync_updates ~rpc ~session_id ~self:pool ~force ~token ~token_id
~username ~password
in
printer (Cli_printer.PList [hash])

Expand Down
8 changes: 8 additions & 0 deletions ocaml/xapi-consts/api_errors.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1314,6 +1314,8 @@ let invalid_base_url = add_error "INVALID_BASE_URL"

let invalid_gpgkey_path = add_error "INVALID_GPGKEY_PATH"

let cdn_token_invalid = add_error "CDN_TOKEN_INVALID"

let repository_already_exists = add_error "REPOSITORY_ALREADY_EXISTS"

let bundle_repository_already_exists =
Expand All @@ -1330,6 +1332,12 @@ let can_not_periodic_sync_updates = add_error "CAN_NOT_PERIODIC_SYNC_UPDATES"
let repo_should_be_single_one_enabled =
add_error "REPO_SHOULD_BE_SINGLE_ONE_ENABLED"

let update_syncing_remote_pool_coordinator_connection_failed =
add_error "UPDATE_SYNCING_REMOTE_POOL_COORDINATOR_CONNECTION_FAILED"

let update_syncing_remote_pool_coordinator_service_failed =
add_error "UPDATE_SYNCING_REMOTE_POOL_COORDINATOR_SERVICE_FAILED"

let repository_is_in_use = add_error "REPOSITORY_IS_IN_USE"

let repository_cleanup_failed = add_error "REPOSITORY_CLEANUP_FAILED"
Expand Down
21 changes: 13 additions & 8 deletions ocaml/xapi/helpers.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2030,19 +2030,24 @@ let with_temp_file ?mode prefix suffix f =
let path, channel = Filename.open_temp_file ?mode prefix suffix in
finally (fun () -> f (path, channel)) (fun () -> Unix.unlink path)

let with_temp_file_of_content ?mode prefix suffix content f =
let@ temp_file, temp_out_ch = with_temp_file ?mode prefix suffix in
Xapi_stdext_pervasives.Pervasiveext.finally
(fun () -> output_string temp_out_ch content)
(fun () -> close_out temp_out_ch) ;
f temp_file

let with_temp_out_ch_of_temp_file ?mode prefix suffix f =
let@ path, channel = with_temp_file ?mode prefix suffix in
f (path, channel |> with_temp_out_ch)

let make_external_host_verified_rpc ~__context ext_host_address ext_host_cert
xml =
let@ temp_file, temp_out_ch = with_temp_file "external-host-cert" ".pem" in
Xapi_stdext_pervasives.Pervasiveext.finally
(fun () -> output_string temp_out_ch ext_host_cert)
(fun () -> close_out temp_out_ch) ;
let make_external_host_verified_rpc ~__context host_address host_cert xml =
let@ cert_file =
with_temp_file_of_content "external-host-cert-" ".pem" host_cert
in
make_remote_rpc ~__context
~verify_cert:(Stunnel_client.external_host temp_file)
ext_host_address xml
~verify_cert:(Stunnel_client.external_host cert_file)
host_address xml

module FileSys : sig
(* bash-like interface for manipulating files *)
Expand Down
3 changes: 2 additions & 1 deletion ocaml/xapi/pool_periodic_update_sync.ml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ let rec update_sync () =
ignore
(Client.Pool.sync_updates ~rpc ~session_id
~self:(Helpers.get_pool ~__context)
~force:false ~token:"" ~token_id:""
~force:false ~token:"" ~token_id:"" ~username:""
~password:""
)
with e ->
let exc = Printexc.to_string e in
Expand Down
195 changes: 154 additions & 41 deletions ocaml/xapi/repository.ml
Original file line number Diff line number Diff line change
Expand Up @@ -149,26 +149,70 @@ let get_proxy_params ~__context repo_name =
| _ ->
("", "", "")

let sync ~__context ~self ~token ~token_id =
type client_proxy_conf = {
cert: string
; remote_addr: string
; remote_port: int
; local_host: string
; local_port: int
}

type client_auth_conf =
| CdnTokenAuthConf of {token: string; token_id: string}
| PoolExtHostAuthConf of {
cert: string
; remote_addr: string
; username: string
; password: string
}

let sync ~__context ~self ~token ~token_id ~username ~password =
try
let repo_name = get_remote_repository_name ~__context ~self in
remove_repo_conf_file repo_name ;
let binary_url, source_url =
match Db.Repository.get_origin ~__context ~self with
let origin = Db.Repository.get_origin ~__context ~self in
let local_host = "127.0.0.1" in

let ( binary_url
, source_url
, client_auth_config
, client_proxy_config ) =
match origin with
| `remote ->
( Db.Repository.get_binary_url ~__context ~self
, Some (Db.Repository.get_source_url ~__context ~self)
, Some (CdnTokenAuthConf {token; token_id})
, None
)
| `bundle ->
let uri =
Uri.make ~scheme:"file" ~path:!Xapi_globs.bundle_repository_dir ()
in
(Uri.to_string uri, None)
(Uri.to_string uri, None, None, None)
| `remote_pool ->
(* TODO: sync with Stunnel.with_client_proxy as otherwise yum
reposync will fail when checking the self signed certificate on
the remote pool. *)
("", None)
let uri =
Uri.make ~scheme:"http" ~host:local_host
~port:!Xapi_globs.local_yum_repo_port
~path:Constants.get_enabled_repository_uri ()
in
let cert = Db.Repository.get_certificate ~__context ~self in
let remote_addr =
Db.Repository.get_binary_url ~__context ~self
|> Repository_helpers.get_remote_pool_coordinator_ip
in
let local_port = !Xapi_globs.local_yum_repo_port in
( Uri.to_string uri
, None
, Some (PoolExtHostAuthConf {cert; remote_addr; username; password})
, Some
{
cert
; remote_addr
; remote_port= Constants.default_ssl_port
; local_host
; local_port
}
)
in
let gpgkey_path =
match Db.Repository.get_gpgkey_path ~__context ~self with
Expand All @@ -188,39 +232,105 @@ let sync ~__context ~self ~token ~token_id =
Xapi_stdext_unix.Unixext.rm_rec (get_repo_config repo_name "gpgdir") ;
Xapi_stdext_pervasives.Pervasiveext.finally
(fun () ->
with_access_token ~token ~token_id @@ fun token_path ->
(* Configure proxy and token *)
let token_param =
match token_path with
| Some p ->
Printf.sprintf "--setopt=%s.accesstoken=file://%s" repo_name p
| None ->
""
in
let proxy_url_param, proxy_username_param, proxy_password_param =
get_proxy_params ~__context repo_name
in
let Pkg_mgr.{cmd; params} =
[
"--save"
; proxy_url_param
; proxy_username_param
; proxy_password_param
; token_param
]
|> fun config -> Pkgs.config_repo ~repo_name ~config
let config_repo token_path yum_plugin =
(* Configure proxy and token *)
let token_param =
match token_path with
| "" ->
""
| p ->
Printf.sprintf "--setopt=%s.%s=%s" repo_name yum_plugin
(Uri.make ~scheme:"file" ~path:p () |> Uri.to_string)
in
let proxy_url_param, proxy_username_param, proxy_password_param =
get_proxy_params ~__context repo_name
in
let Pkg_mgr.{cmd; params} =
[
"--save"
; proxy_url_param
; proxy_username_param
; proxy_password_param
; token_param
]
|> fun config -> Pkgs.config_repo ~repo_name ~config
in
ignore (Helpers.call_script ~log_output:Helpers.On_failure cmd params)
in
ignore (Helpers.call_script ~log_output:Helpers.On_failure cmd params) ;

(* Import YUM repository GPG key to check metadata in reposync *)
let Pkg_mgr.{cmd; params} = Pkgs.make_cache ~repo_name in
ignore (Helpers.call_script cmd params) ;
let make_cache () =
(* Import YUM repository GPG key to check metadata in reposync *)
let Pkg_mgr.{cmd; params} = Pkgs.make_cache ~repo_name in
ignore (Helpers.call_script cmd params)
in

(* Sync with remote repository *)
let Pkg_mgr.{cmd; params} = Pkgs.sync_repo ~repo_name in
Unixext.mkdir_rec !Xapi_globs.local_pool_repo_dir 0o700 ;
clean_yum_cache repo_name ;
ignore (Helpers.call_script cmd params)
let sync_repo () =
let Pkg_mgr.{cmd; params} = Pkgs.sync_repo ~repo_name in
Unixext.mkdir_rec !Xapi_globs.local_pool_repo_dir 0o700 ;
clean_yum_cache repo_name ;
ignore (Helpers.call_script cmd params)
in

match client_auth_config with
| Some cfg -> (
let auth, yum_plugin =
match cfg with
| CdnTokenAuthConf {token; token_id} ->
(CdnTokenAuth (token_id, token), "accesstoken")
| PoolExtHostAuthConf {cert; remote_addr; username; password} ->
let verified_rpc =
try
Helpers.make_external_host_verified_rpc ~__context
remote_addr cert
with Xmlrpc_client.Connection_reset ->
raise
(Api_errors.Server_error
( Api_errors
.update_syncing_remote_pool_coordinator_connection_failed
, []
)
)
in
let session_id =
try
Client.Client.Session.login_with_password
~rpc:verified_rpc ~uname:username ~pwd:password
~version:Datamodel_common.api_version_string
~originator:Xapi_version.xapi_user_agent
with
| Http_client.Http_request_rejected _
| Http_client.Http_error _
->
raise
(Api_errors.Server_error
( Api_errors
.update_syncing_remote_pool_coordinator_service_failed
, []
)
)
in
let xapi_token = session_id |> Ref.string_of in
(ExtHostAuth xapi_token, "xapitoken")
in
with_sync_client_auth auth @@ fun token_path ->
config_repo token_path yum_plugin ;
match client_proxy_config with
| None ->
make_cache () ; sync_repo ()
| Some {cert; remote_addr; remote_port; local_host; local_port} ->
let ( let@ ) f x = f x in
let@ temp_file =
Helpers.with_temp_file_of_content "external-host-cert-" ".pem"
cert
in
Stunnel.with_client_proxy
~verify_cert:(Stunnel_client.external_host temp_file)
~remote_host:remote_addr ~remote_port ~local_host ~local_port
@@ fun () -> make_cache () ; sync_repo ()
)
| None ->
make_cache () ; sync_repo ()
)
(fun () ->
(* Rewrite repo conf file as initial content to remove credential
Expand All @@ -239,10 +349,13 @@ let sync ~__context ~self ~token ~token_id =
// "repomd.xml.asc"
in
Sys.file_exists repo_gpg_signature
with e ->
error "Failed to sync with remote YUM repository: %s"
(ExnHelper.string_of_exn e) ;
raise Api_errors.(Server_error (reposync_failed, []))
with
| Api_errors.Server_error (_, _) as e ->
raise e
| e ->
error "Failed to sync with remote YUM repository: %s"
(ExnHelper.string_of_exn e) ;
raise Api_errors.(Server_error (reposync_failed, []))

let http_get_host_updates_in_json ~__context ~host ~installed =
let host_session_id =
Expand Down
2 changes: 2 additions & 0 deletions ocaml/xapi/repository.mli
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ val sync :
-> self:[`Repository] API.Ref.t
-> token:string
-> token_id:string
-> username:string
-> password:string
-> bool

val create_pool_repository :
Expand Down
Loading

0 comments on commit 4c78c86

Please sign in to comment.