Skip to content

Commit

Permalink
Merge pull request xapi-project#2264 from jonludlam/CP-12513-for-trunk2
Browse files Browse the repository at this point in the history
CP-12513: Give xapi the ability to manage xenvm daemons
  • Loading branch information
Jon Ludlam committed Jun 29, 2015
2 parents 688cb11 + ce8b19b commit a077b83
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 3 deletions.
1 change: 1 addition & 0 deletions ocaml/xapi/OMakefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ XAPI_MODULES = $(COMMON) \
storage_migrate \
storage_proxy \
storage_utils \
xapi_xenvmd \
xapi_services \
sm_exec \
sm \
Expand Down
5 changes: 4 additions & 1 deletion ocaml/xapi/xapi.ml
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ let check_no_other_masters() =
(* transition to slave and restart *)
begin
try
(* Belt-n-braces *)
if !Xapi_globs.manage_xenvmd then Xapi_xenvmd.kill_non_sr_master_xenvmds ();
(* now become a slave of the new master we found... *)
Pool_role.set_role (Pool_role.Slave master_address);
with
Expand Down Expand Up @@ -915,7 +917,8 @@ let server_init() =
"executing startup scripts", [ Startup.NoExnRaising], startup_script;

"considering executing on-master-start script", [],
(fun () -> Xapi_pool_transition.run_external_scripts (Pool_role.is_master ()));
(fun () -> Xapi_pool_transition.run_external_scripts (Pool_role.is_master ()));
"starting xenvmd daemons", [ Startup.OnlyMaster], (fun () -> if !Xapi_globs.manage_xenvmd then Xapi_xenvmd.start_xenvmds_for_shared_srs ());
"creating networks", [ Startup.OnlyMaster ], Create_networks.create_networks_localhost;
"updating the vswitch controller", [], (fun () -> Helpers.update_vswitch_controller ~__context ~host:(Helpers.get_localhost ~__context));
(* CA-22417: bring up all non-bond slaves so that the SM backends can use storage NIC IP addresses (if the routing
Expand Down
10 changes: 9 additions & 1 deletion ocaml/xapi/xapi_globs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,8 @@ let igd_passthru_vendor_whitelist = ref []
* with PIF.managed = false during a PIF.scan. *)
let non_managed_pifs = ref "/opt/xensource/libexec/ibft-to-ignore"

let manage_xenvmd = ref false

type xapi_globs_spec_ty = | Float of float ref | Int of int ref

let xapi_globs_spec =
Expand Down Expand Up @@ -857,6 +859,8 @@ let options_of_xapi_globs_spec =

let xapissl_path = ref "xapissl"

let xenvmd_path = ref "xenvmd"

let xenopsd_queues = ref ([
"org.xen.xcp.xenops.classic";
"org.xen.xcp.xenops.simulator";
Expand Down Expand Up @@ -925,7 +929,10 @@ let other_options = [
(fun s -> s) igd_passthru_vendor_whitelist;

"pass-through-pif-carrier", Arg.Set pass_through_pif_carrier,
(fun () -> string_of_bool !pass_through_pif_carrier), "reflect physical interface carrier information to VMs by default";
(fun () -> string_of_bool !pass_through_pif_carrier), "reflect physical interface carrier information to VMs by default";

"manage_xenvmd", Arg.Set manage_xenvmd,
(fun () -> string_of_bool !manage_xenvmd), "Start and stop xenvmd instances on behalf of the SM backends";
]

let all_options = options_of_xapi_globs_spec @ other_options
Expand Down Expand Up @@ -984,6 +991,7 @@ module Resources = struct
"rolling-upgrade-script-hook", rolling_upgrade_script_hook, "Executed when a rolling upgrade is detected starting or stopping";
"xapi-message-script", xapi_message_script, "Executed when messages are generated if email feature is disabled";
"non-managed-pifs", non_managed_pifs, "Executed during PIF.scan to find out which NICs should not be managed by xapi";
"xenvmd", xenvmd_path, "Xenvmd executable for thin-provisioned block storage";
]
let essential_files = [
"pool_config_file", pool_config_file, "Pool configuration file";
Expand Down
3 changes: 2 additions & 1 deletion ocaml/xapi/xapi_pool_transition.ml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ let become_another_masters_slave master_address =
if Pool_role.get_role () = new_role then begin
debug "We are already a slave of %s; nothing to do" master_address;
end else begin
debug "Setting pool.conf to point to %s" master_address;
debug "Setting pool.conf to point to %s" master_address;
if !Xapi_globs.manage_xenvmd then Xapi_xenvmd.kill_non_sr_master_xenvmds ();
Pool_role.set_role new_role;
run_external_scripts false;
Xapi_fuse.light_fuse_and_run ()
Expand Down
2 changes: 2 additions & 0 deletions ocaml/xapi/xapi_services.ml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ let call_xenvmd_on_srmaster __context req from sr_uuid =
if Helpers.i_am_srmaster ~__context ~sr
then begin
let path = Filename.concat "/var/lib/xenvmd" sr_uuid in
if !Xapi_globs.manage_xenvmd then
Xapi_xenvmd.start sr_uuid;
http_proxy_to_unix_sock req from path "xenvmd"
end else begin
let sr_master = Helpers.get_srmaster ~__context ~sr in
Expand Down
190 changes: 190 additions & 0 deletions ocaml/xapi/xapi_xenvmd.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
(*
* Copyright (C) 2015 Citrix Systems Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; version 2.1 only. with the special
* exception on linking described in file LICENSE.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*)

(*
* Code to manage xenvmd
*
* This is a little bit of a layering violation.
*
*)

module D=Debug.Make(struct let name="xapi_xenvmd" end)
open D


(* Thrown if we'd like to start a xenvmd process but there's no config file for it *)
exception NoConfigFile

(* This exception should be internal to this file *)
exception NoLockFile

(* This is a bad exception. We never expect this to happen. *)
exception XenvmdFailed of string * string

(* The storage backends know about these paths *)
let lockfile_path sr = Printf.sprintf "/var/lib/xenvmd/%s.lock" sr
let configfile_path sr = Printf.sprintf "/etc/xenvm.d/VG_XenStorage-%s.xenvmd.config" sr

(* We need to be able to talk SMAPIv2 in order to find out which SRs
are currently attached to this host. For that, we need an RPC
function, a URL to talk to and the client module itself. *)

let rpc x =
if !Xcp_client.use_switch
then Xcp_client.json_switch_rpc !(Storage_interface.queue_name) x
else Xcp_client.http_rpc Xmlrpc.string_of_call Xmlrpc.response_of_string ~srcstr:"xapi" ~dststr:"smapiv2" Storage_interface.uri x

let local_url () = Http.Url.(Http { host="127.0.0.1"; auth=None; port=None; ssl=false }, { uri = "/services/SM"; query_params=["pool_secret",!Xapi_globs.pool_secret] } )

module SM = Storage_interface.Client(struct let rpc = rpc end)


(******************************************************************************************)
(***** Helper functions *****)

(* [assert_file_present file exn] will raise [exn] if [file] does not
exist *)
let assert_file_present file exn =
try
ignore(Unix.stat file)
with
| Unix.Unix_error (Unix.ENOENT, _, _) -> raise exn
| e -> raise e

(* [get_cmdname pid] returns a string option of the name of the
executable corresponding to a running process *)
let get_cmdname pid =
try
Some (Unix.readlink (Printf.sprintf "/proc/%d/exe" pid) |> Filename.basename)
with e ->
info "Unable to read the /proc tree for pid (%d) - has the process already died?" pid;
None

(* [check_is name x] is an internal function to test whether a value
[x] is equal to [name]. If so, returns 'Some x'. This is helpful
for using options in a monadic fashion. *)
let check_is name x =
if x=name
then Some x
else begin
info "check_is: expecting %s, got %s" name x;
None
end

(****************************************************************************************)

(* It is the responsibility of the SR backends to write the config
file for xenvmd. Only they know which devices xenvmd should be
talking to *)
let assert_config_present sr = assert_file_present (configfile_path sr) NoConfigFile

(* [is_running sr] checks to see whether xenvmd is running for a
particular SR. It does this by attempting to lock the
pidfile. Xenvmd will itself lock the pidfile while it is up, so
xapi's attempt will fail if the process still exists. *)
let is_running sr =
debug "Checking for xenvmd running for sr: %s" sr;
try
let l = lockfile_path sr in
assert_file_present l NoLockFile;
let fd = Unix.openfile l [ Unix.O_WRONLY; Unix.O_CREAT ] 0o0644 in
Pervasiveext.finally
(fun () -> Unix.lockf fd Unix.F_TEST 1)
(fun () -> Unix.close fd);
debug "Locked successfully: xenvmd not running";
false
with
| NoLockFile -> debug "Caught NoLockFile (xenvmd not running)"; false (* Lockfile missing *)
| Unix.Unix_error (Unix.EAGAIN, _, _) -> debug "Caught EAGAIN (xenvmd running)"; true (* Locked by xenvmd *)
| Unix.Unix_error (Unix.EACCES, _, _) -> debug "Caught EACCESS (xenvmd running)"; true (* Locked by xenvmd *)
| e -> raise e

(* Verify that xenvmd is running for a particular SR. If xenvmd is not
running and a suitable config file exists, we'll start it. This
function is idempotent. *)
let start sr =

(* Here we _actually_ start the xenvmd process *)
let really_start sr =
assert_config_present sr;
try
let output, stderr = Forkhelpers.execute_command_get_output !Xapi_globs.xenvmd_path
[ "--daemon"; "--config"; configfile_path sr ] in
(* Note that xenvmd will detect multiple copies of itself for the same VG.
it will write an error message to the stdout/stderr but exit with code
0 *)
debug "Xenvmd started for SR %s. output='%s' stderr='%s'" sr output stderr
with
| Forkhelpers.Spawn_internal_error(log, output, x) ->
debug "Failed to execute xenvmd. log='%s' output='%s'" log output;
raise (XenvmdFailed (log, output))
in

let rec retry n =
try
if not (is_running sr)
then really_start sr
with
| XenvmdFailed _ as e ->
if n = 0 then raise e;
Thread.delay 5.0;
retry (n-1)
| NoConfigFile ->
debug "No config file for SR=%s - assuming not thin-lvhd" sr;
in
retry 5

(* Let's try _really hard_ and _really carefully_ to kill xenvmd. First we read the
pidfile to find the pid, then we check to see that that pid corresponds to xenvmd.
Then we try once to sigterm it, and 5 more times to sigkill it. *)
let stop sr =
let open Opt.Monad in
let rec inner signal n =
try
info "Stopping xenvmd for SR uuid: %s" sr;
Unixext.pidfile_read (lockfile_path sr) >>= fun pid ->
get_cmdname pid >>=
check_is "xenvmd" >>= fun _ ->
Unixext.kill_and_wait ~signal pid;
None
with
| Unixext.Process_still_alive ->
if n=0 then begin
error "Failed to kill xenvm process for sr '%s' despite repeated attempts. Giving up." sr;
None
end else begin
warn "xenvmd is still alive. Trying to kill it again (with sigkill this time).";
inner Sys.sigkill (n-1)
end
in
ignore (inner Sys.sigterm 5)


(* The following functions are used on master transition *)

let operate_on_shared_srs op =
let attached_srs = SM.SR.list ~dbg:"xapi_xenvmd" in
Server_helpers.exec_with_new_task "Xapi_xenvmd: Managing xenvmd instances" (fun __context ->
let shared_srs = List.filter (fun uuid ->
let self = Db.SR.get_by_uuid ~__context ~uuid in
Db.SR.get_shared ~__context ~self) attached_srs in
List.iter op shared_srs)

(* Called on transition to slave, on the way down *)
(* We must assume we're a slave, so not the SR master for any shared SRs *)
let kill_non_sr_master_xenvmds () = operate_on_shared_srs stop

(* Start xenvmd for shared SRs *)
let start_xenvmds_for_shared_srs () = operate_on_shared_srs start

0 comments on commit a077b83

Please sign in to comment.