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

CP-52074: Add API for start/stop systemd service sshd #6198

Open
wants to merge 3 commits into
base: feature/configure-ssh
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ocaml/forkexecd/lib/fe_systemctl.ml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ let is_active ~service =
in
Unix.WEXITED 0 = status

let is_enabled ~service =
let status =
Forkhelpers.safe_close_and_exec None None None [] systemctl
["is-enabled"; "--quiet"; service]
|> Forkhelpers.waitpid
|> snd
in
Unix.WEXITED 0 = status

(** path to service file *)
let path service = Filename.concat run_path (service ^ ".service")

Expand Down
3 changes: 3 additions & 0 deletions ocaml/forkexecd/lib/fe_systemctl.mli
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ val start_transient :
val is_active : service:string -> bool
(** [is_active ~service] checks whether the [service] is still running *)

val is_enabled : service:string -> bool
(** [is_enabled ~service] checks whether the [service] is enabled *)

val show : service:string -> status
(** [shows ~service] retrieves the exitcodes and PIDs of the specified [service] *)

Expand Down
6 changes: 6 additions & 0 deletions ocaml/idl/datamodel_errors.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2010,6 +2010,12 @@ let _ =

error Api_errors.too_many_groups [] ~doc:"VM can only belong to one group." () ;

error Api_errors.configure_ssh_failed ["host"]
~doc:"Failed to configure sshd service." () ;

error Api_errors.configure_ssh_partially_failed ["hosts"]
~doc:"Some of hosts failed to configure sshd service." () ;

message
(fst Api_messages.ha_pool_overcommitted)
~doc:
Expand Down
19 changes: 19 additions & 0 deletions ocaml/idl/datamodel_host.ml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ open Datamodel_common
open Datamodel_roles
open Datamodel_types

let ssh_status =
Enum
( "ssh_status"
, [
("on", "Start and enable sshd service")
; ("off", "Stop and disable sshd service")
]
)

let host_memory =
let field = field ~ty:Int in
[
Expand Down Expand Up @@ -2338,6 +2347,15 @@ let emergency_clear_mandatory_guidance =
~doc:"Clear the pending mandatory guidance on this host"
~allowed_roles:_R_LOCAL_ROOT_ONLY ()

let configure_ssh =
call ~name:"configure_ssh" ~doc:"Configure sshd service" ~lifecycle:[]
~params:
[
(Ref _host, "self", "The host")
; (ssh_status, "status", "Status of sshd service")
Copy link
Member

@psafont psafont Dec 27, 2024

Choose a reason for hiding this comment

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

I would recommend to have 2 calls, one to turn it on, and one to turn it off. This is because enums tend to be problematic for upgrades, so I'd rather avoid them

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given we are later going to have an auto mode, I would like to use a single API with different parameters (e.g. on, off, auto, etc). Then we only need to add 1 API instead of adding 3 APIs.

Copy link
Member

@psafont psafont Jan 3, 2025

Choose a reason for hiding this comment

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

Given we are later going to have an auto mode

This doesn't look like it's a small one off, but something that needs its design reviewed.
Please add the design to xapi's documentation so we can take an honest look at this code.

]
~allowed_roles:_R_POOL_ADMIN ()

let latest_synced_updates_applied_state =
Enum
( "latest_synced_updates_applied_state"
Expand Down Expand Up @@ -2494,6 +2512,7 @@ let t =
; set_https_only
; apply_recommended_guidances
; emergency_clear_mandatory_guidance
; configure_ssh
]
~contents:
([
Expand Down
15 changes: 15 additions & 0 deletions ocaml/idl/datamodel_pool.ml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ let telemetry_frequency =
]
)

let ssh_status =
Enum
( "ssh_status"
, [
("on", "Start and enable sshd service")
; ("off", "Stop and disable sshd service")
]
)

let enable_ha =
call
~lifecycle:[(Published, rel_miami, "Turn on High Availability mode")]
Expand Down Expand Up @@ -1539,6 +1548,11 @@ let get_guest_secureboot_readiness =
~result:(pool_guest_secureboot_readiness, "The readiness of the pool")
~allowed_roles:_R_POOL_OP ()

let configure_ssh =
call ~name:"configure_ssh" ~doc:"Configure ssd service" ~lifecycle:[]
~params:[(ssh_status, "status", "Status of ssd service")]
~allowed_roles:_R_POOL_ADMIN ()

(** A pool class *)
let t =
create_obj ~in_db:true
Expand Down Expand Up @@ -1633,6 +1647,7 @@ let t =
; set_ext_auth_cache_size
; set_ext_auth_cache_expiry
; get_guest_secureboot_readiness
; configure_ssh
]
~contents:
([
Expand Down
1 change: 1 addition & 0 deletions ocaml/sdk-gen/go/gen_go_helper.ml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ let acronyms =
; "db"
; "xml"
; "eof"
; "ssh"
]
|> StringSet.of_list

Expand Down
18 changes: 18 additions & 0 deletions ocaml/xapi-cli-server/cli_frontend.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,15 @@ let rec cmdtable_data : (string * cmd_spec) list =
; flags= [Host_selectors]
}
)
; ( "host-configure-ssh"
, {
reqd= ["status"]
; optn= []
; help= "Configure sshd service"
; implementation= No_fd Cli_operations.host_configure_ssh
; flags= [Host_selectors]
}
)
; ( "host-emergency-clear-mandatory-guidance"
, {
reqd= []
Expand Down Expand Up @@ -3105,6 +3114,15 @@ let rec cmdtable_data : (string * cmd_spec) list =
; flags= []
}
)
; ( "pool-configure-ssh"
, {
reqd= ["status"]
; optn= []
; help= "Configure sshd service"
; implementation= No_fd Cli_operations.pool_configure_ssh
; flags= []
}
)
; ( "host-ha-xapi-healthcheck"
, {
reqd= []
Expand Down
15 changes: 15 additions & 0 deletions ocaml/xapi-cli-server/cli_operations.ml
Original file line number Diff line number Diff line change
Expand Up @@ -6779,6 +6779,10 @@ let pool_sync_bundle fd _printer rpc session_id params =
| None ->
failwith "Required parameter not found: filename"

let pool_configure_ssh _printer rpc session_id params =
let status = Record_util.ssh_status_of_string (List.assoc "status" params) in
Client.Pool.configure_ssh ~rpc ~session_id ~status

let host_restore fd _printer rpc session_id params =
let filename = List.assoc "file-name" params in
let op _ host =
Expand Down Expand Up @@ -7729,6 +7733,17 @@ let host_apply_updates _printer rpc session_id params =
params ["hash"]
)

let host_configure_ssh _printer rpc session_id params =
let status = Record_util.ssh_status_of_string (List.assoc "status" params) in
ignore
(do_host_op rpc session_id
(fun _ host ->
let host = host.getref () in
Client.Host.configure_ssh ~rpc ~session_id ~self:host ~status
)
params ["status"]
)

module SDN_controller = struct
let introduce printer rpc session_id params =
let port =
Expand Down
4 changes: 4 additions & 0 deletions ocaml/xapi-consts/api_errors.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1403,3 +1403,7 @@ let telemetry_next_collection_too_late =
let illegal_in_fips_mode = add_error "ILLEGAL_IN_FIPS_MODE"

let too_many_groups = add_error "TOO_MANY_GROUPS"

let configure_ssh_failed = add_error "CONFIGURE_SSH_FAILED"

let configure_ssh_partially_failed = add_error "CONFIGURE_SSH_PARTIALLY_FAILED"
14 changes: 14 additions & 0 deletions ocaml/xapi/message_forwarding.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,11 @@ functor
let get_guest_secureboot_readiness ~__context ~self =
info "%s: pool='%s'" __FUNCTION__ (pool_uuid ~__context self) ;
Local.Pool.get_guest_secureboot_readiness ~__context ~self

let configure_ssh ~__context ~status =
info "%s: status = '%s'" __FUNCTION__
(Record_util.ssh_status_to_string status) ;
Local.Pool.configure_ssh ~__context ~status
end

module VM = struct
Expand Down Expand Up @@ -4154,6 +4159,15 @@ functor
let emergency_clear_mandatory_guidance ~__context =
info "Host.emergency_clear_mandatory_guidance" ;
Local.Host.emergency_clear_mandatory_guidance ~__context

let configure_ssh ~__context ~self ~status =
info "%s: host = '%s' ; status = '%s'" __FUNCTION__
(host_uuid ~__context self)
(Record_util.ssh_status_to_string status) ;
let local_fn = Local.Host.configure_ssh ~self ~status in
do_op_on ~local_fn ~__context ~host:self (fun session_id rpc ->
Client.Host.configure_ssh ~rpc ~session_id ~self ~status
)
end

module Host_crashdump = struct
Expand Down
15 changes: 15 additions & 0 deletions ocaml/xapi/xapi_host.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3127,3 +3127,18 @@ let emergency_clear_mandatory_guidance ~__context =
info "%s: %s is cleared" __FUNCTION__ s
) ;
Db.Host.set_pending_guidances ~__context ~self ~value:[]

let configure_ssh ~__context ~self ~status =
try
match status with
| `on ->
Xapi_systemctl.start ~wait_until_success:false "sshd" ;
Xapi_systemctl.enable ~wait_until_success:false "sshd"
| `off ->
Xapi_systemctl.stop ~wait_until_success:false "sshd" ;
Xapi_systemctl.disable ~wait_until_success:false "sshd"
with _ ->
raise
(Api_errors.Server_error
(Api_errors.configure_ssh_failed, [Ref.string_of self])
)
3 changes: 3 additions & 0 deletions ocaml/xapi/xapi_host.mli
Original file line number Diff line number Diff line change
Expand Up @@ -561,3 +561,6 @@ val set_https_only :
__context:Context.t -> self:API.ref_host -> value:bool -> unit

val emergency_clear_mandatory_guidance : __context:Context.t -> unit

val configure_ssh :
__context:Context.t -> self:API.ref_host -> status:API.ssh_status -> unit
23 changes: 23 additions & 0 deletions ocaml/xapi/xapi_pool.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3952,3 +3952,26 @@ let put_bundle_handler (req : Request.t) s _ =
| None ->
()
)

let configure_ssh ~__context ~status =
let hosts = Db.Host.get_all ~__context in
Helpers.call_api_functions ~__context (fun rpc session_id ->
let failed_hosts =
List.fold_left
(fun failed_hosts host ->
try
Client.Host.configure_ssh ~rpc ~session_id ~self:host ~status ;
failed_hosts
with _ -> Ref.string_of host :: failed_hosts
)
[] hosts
in
match failed_hosts with
| [] ->
()
| _ ->
raise
(Api_errors.Server_error
(Api_errors.configure_ssh_partially_failed, failed_hosts)
)
)
2 changes: 2 additions & 0 deletions ocaml/xapi/xapi_pool.mli
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,5 @@ val get_guest_secureboot_readiness :
-> API.pool_guest_secureboot_readiness

val put_bundle_handler : Http.Request.t -> Unix.file_descr -> 'a -> unit

val configure_ssh : __context:Context.t -> status:API.ssh_status -> unit
23 changes: 21 additions & 2 deletions ocaml/xapi/xapi_systemctl.ml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module D = Debug.Make (struct let name = "xapi_systemctl" end)

open D

type t = Start | Stop | Restart
type t = Start | Stop | Restart | Enable | Disable

exception Systemctl_fail of string

Expand All @@ -30,6 +30,10 @@ let to_string = function
"stop"
| Restart ->
"restart"
| Enable ->
"enable"
| Disable ->
"disable"

let perform ~wait_until_success ~service ~timeout op =
let op_str = op |> to_string in
Expand All @@ -42,8 +46,17 @@ let perform ~wait_until_success ~service ~timeout op =
if wait_until_success then (
if op = Restart then Thread.delay 0.1 ;
let is_active = Fe_systemctl.is_active ~service in
let is_enabled = Fe_systemctl.is_enabled ~service in
let success_cond () =
match op with Start | Restart -> is_active | Stop -> is_active |> not
match op with
| Start | Restart ->
is_active
| Stop ->
is_active |> not
| Enable ->
is_enabled
| Disable ->
is_enabled |> not
in
try
Helpers.retry_until_timeout ~timeout
Expand All @@ -66,3 +79,9 @@ let stop ?(timeout = 5.) ~wait_until_success service =

let start ?(timeout = 5.) ~wait_until_success service =
perform ~wait_until_success ~service ~timeout Start

let disable ?(timeout = 5.) ~wait_until_success service =
perform ~wait_until_success ~service ~timeout Disable

let enable ?(timeout = 5.) ~wait_until_success service =
perform ~wait_until_success ~service ~timeout Enable
10 changes: 10 additions & 0 deletions ocaml/xapi/xapi_systemctl.mli
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
(* Exception about systemctl operation like start/stop failed *)
exception Systemctl_fail of string

type t = Start | Stop | Restart | Enable | Disable

val to_string : t -> string

(* start a service with systemctl *)
val start : ?timeout:float -> wait_until_success:bool -> string -> unit

Expand All @@ -23,3 +27,9 @@ val stop : ?timeout:float -> wait_until_success:bool -> string -> unit

(* restart a service with systemctl *)
val restart : ?timeout:float -> wait_until_success:bool -> string -> unit

(* enable a service with systemctl *)
val enable : ?timeout:float -> wait_until_success:bool -> string -> unit

(* disable a service with systemctl *)
val disable : ?timeout:float -> wait_until_success:bool -> string -> unit
Loading