]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/api2/node/services.rs
move worker_task.rs into proxmox-rest-server crate
[proxmox-backup.git] / src / api2 / node / services.rs
index 135eb9c6e210b12b8ce3c3f8fcb9ca1912f1f6a1..8df0fb24b9d59b075aa375b015b5eeac1921050e 100644 (file)
@@ -1,12 +1,15 @@
-use failure::*;
+use std::process::{Command, Stdio};
 
-use crate::tools;
-use crate::api_schema::*;
-use crate::api_schema::router::*;
+use anyhow::{bail, Error};
 use serde_json::{json, Value};
 
-use std::sync::Arc;
-use std::process::{Command, Stdio};
+use proxmox::{sortable, identity, list_subdirs_api_method};
+use proxmox::api::{api, Router, Permission, RpcEnvironment};
+use proxmox::api::router::SubdirMap;
+
+use pbs_api_types::{Authid, NODE_SCHEMA, SERVICE_ID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
+
+use proxmox_rest_server::WorkerTask;
 
 static SERVICE_NAME_LIST: [&str; 7] = [
     "proxmox-backup",
@@ -18,7 +21,7 @@ static SERVICE_NAME_LIST: [&str; 7] = [
     "systemd-timesyncd",
 ];
 
-fn real_service_name(service: &str) -> &str {
+pub fn real_service_name(service: &str) -> &str {
 
     // since postfix package 3.1.0-3.1 the postfix unit is only here
     // to manage subinstances, of which the default is called "-".
@@ -35,7 +38,7 @@ fn get_full_service_state(service: &str) -> Result<Value, Error> {
 
     let real_service_name = real_service_name(service);
 
-    let mut child = Command::new("/bin/systemctl")
+    let mut child = Command::new("systemctl")
         .args(&["show", real_service_name])
         .stdout(Stdio::piped())
         .spawn()?;
@@ -88,11 +91,45 @@ fn json_service_state(service: &str, status: Value) -> Value {
     Value::Null
 }
 
-
+#[api(
+    input: {
+        properties: {
+            node: {
+                schema: NODE_SCHEMA,
+            },
+        },
+    },
+    returns: {
+        description: "Returns a list of systemd services.",
+        type: Array,
+        items: {
+            description: "Service details.",
+            properties: {
+                service: {
+                    schema: SERVICE_ID_SCHEMA,
+                },
+                name: {
+                    type: String,
+                    description: "systemd service name.",
+                },
+                desc: {
+                    type: String,
+                    description: "systemd service description.",
+                },
+                state: {
+                    type: String,
+                    description: "systemd service 'SubState'.",
+                },
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["system", "services"], PRIV_SYS_AUDIT, false),
+    },
+)]
+/// Service list.
 fn list_services(
     _param: Value,
-    _info: &ApiMethod,
-    _rpcenv: &mut RpcEnvironment,
 ) -> Result<Value, Error> {
 
     let mut list = vec![];
@@ -112,196 +149,230 @@ fn list_services(
     Ok(Value::from(list))
 }
 
+#[api(
+    input: {
+        properties: {
+            node: {
+                schema: NODE_SCHEMA,
+            },
+            service: {
+                schema: SERVICE_ID_SCHEMA,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_AUDIT, false),
+    },
+)]
+/// Read service properties.
 fn get_service_state(
-    param: Value,
-    _info: &ApiMethod,
-    _rpcenv: &mut RpcEnvironment,
+    service: String,
+    _param: Value,
 ) -> Result<Value, Error> {
 
-    let service = tools::required_string_param(&param, "service")?;
+    let service = service.as_str();
 
     if !SERVICE_NAME_LIST.contains(&service) {
         bail!("unknown service name '{}'", service);
     }
 
-    let status = get_full_service_state(service)?;
+    let status = get_full_service_state(&service)?;
 
-    Ok(json_service_state(service, status))
+    Ok(json_service_state(&service, status))
 }
 
-fn run_service_command(service: &str, cmd: &str) -> Result<Value, Error> {
+fn run_service_command(service: &str, cmd: &str, auth_id: Authid) -> Result<Value, Error> {
 
-    // fixme: run background worker (fork_worker) ???
+    let workerid = format!("srv{}", &cmd);
 
-    match cmd {
-        "start"|"stop"|"restart"|"reload" => {},
+    let cmd = match cmd {
+        "start"|"stop"|"restart"=> cmd.to_string(),
+        "reload" => "try-reload-or-restart".to_string(), // some services do not implement reload
         _ => bail!("unknown service command '{}'", cmd),
-    }
+    };
+    let service = service.to_string();
+
+    let upid = WorkerTask::new_thread(
+        &workerid,
+        Some(service.clone()),
+        auth_id.to_string(),
+        false,
+        move |_worker| {
+
+            if service == "proxmox-backup" && cmd == "stop" {
+                bail!("invalid service cmd '{} {}' cannot stop essential service!", service, cmd);
+            }
 
-    if service == "proxmox-backup" {
-        if cmd != "restart" {
-           bail!("invalid service cmd '{} {}'", service, cmd);
-        }
-    }
+            let real_service_name = real_service_name(&service);
 
-    let real_service_name = real_service_name(service);
+            let status = Command::new("systemctl")
+                .args(&[&cmd, real_service_name])
+                .status()?;
 
-    let status = Command::new("/bin/systemctl")
-        .args(&[cmd, real_service_name])
-        .status()?;
+            if !status.success() {
+                bail!("systemctl {} failed with {}", cmd, status);
+            }
 
-    if !status.success() {
-        bail!("systemctl {} failed with {}", cmd, status);
-    }
+            Ok(())
+        }
+    )?;
 
-    Ok(Value::Null)
+    Ok(upid.into())
 }
 
+#[api(
+    protected: true,
+    input: {
+        properties: {
+            node: {
+                schema: NODE_SCHEMA,
+            },
+            service: {
+                schema: SERVICE_ID_SCHEMA,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
+    },
+)]
+/// Start service.
 fn start_service(
-    param: Value,
-    _info: &ApiMethod,
-    _rpcenv: &mut RpcEnvironment,
+    service: String,
+    _param: Value,
+    rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<Value, Error> {
 
-    let service = tools::required_string_param(&param, "service")?;
+    let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
 
     log::info!("starting service {}", service);
 
-    run_service_command(service, "start")
+    run_service_command(&service, "start", auth_id)
 }
 
+#[api(
+    protected: true,
+    input: {
+        properties: {
+            node: {
+                schema: NODE_SCHEMA,
+            },
+            service: {
+                schema: SERVICE_ID_SCHEMA,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
+    },
+)]
+/// Stop service.
 fn stop_service(
-    param: Value,
-    _info: &ApiMethod,
-    _rpcenv: &mut RpcEnvironment,
-) -> Result<Value, Error> {
+    service: String,
+    _param: Value,
+    rpcenv: &mut dyn RpcEnvironment,
+ ) -> Result<Value, Error> {
 
-    let service = tools::required_string_param(&param, "service")?;
+    let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
 
-    log::info!("stoping service {}", service);
+    log::info!("stopping service {}", service);
 
-    run_service_command(service, "stop")
+    run_service_command(&service, "stop", auth_id)
 }
 
+#[api(
+    protected: true,
+    input: {
+        properties: {
+            node: {
+                schema: NODE_SCHEMA,
+            },
+            service: {
+                schema: SERVICE_ID_SCHEMA,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
+    },
+)]
+/// Retart service.
 fn restart_service(
-    param: Value,
-    _info: &ApiMethod,
-    _rpcenv: &mut RpcEnvironment,
+    service: String,
+    _param: Value,
+    rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<Value, Error> {
 
-    let service = tools::required_string_param(&param, "service")?;
+    let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
 
     log::info!("re-starting service {}", service);
 
-    if service == "proxmox-backup-proxy" {
+    if &service == "proxmox-backup-proxy" {
         // special case, avoid aborting running tasks
-        run_service_command(service, "reload")
+        run_service_command(&service, "reload", auth_id)
     } else {
-        run_service_command(service, "restart")
+        run_service_command(&service, "restart", auth_id)
     }
 }
 
+#[api(
+    protected: true,
+    input: {
+        properties: {
+            node: {
+                schema: NODE_SCHEMA,
+            },
+            service: {
+                schema: SERVICE_ID_SCHEMA,
+            },
+        },
+    },
+    access: {
+        permission: &Permission::Privilege(&["system", "services", "{service}"], PRIV_SYS_MODIFY, false),
+    },
+)]
+/// Reload service.
 fn reload_service(
-    param: Value,
-    _info: &ApiMethod,
-    _rpcenv: &mut RpcEnvironment,
+    service: String,
+    _param: Value,
+    rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<Value, Error> {
 
-    let service = tools::required_string_param(&param, "service")?;
+    let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
 
     log::info!("reloading service {}", service);
 
-    run_service_command(service, "reload")
+    run_service_command(&service, "reload", auth_id)
 }
 
-pub fn router() -> Router {
-
-    let service_id_schema : Arc<Schema> = Arc::new(
-        StringSchema::new("Service ID.")
-            .max_length(256)
-            .into()
-    );
-
-    let service_api = Router::new()
-        .subdir(
-            "state",
-            Router::new()
-                .get(ApiMethod::new(
-                    get_service_state,
-                    ObjectSchema::new("Read service properties.")
-                        .required("node", crate::api2::node::NODE_SCHEMA.clone())
-                       .required("service", service_id_schema.clone()))
-                )
-        )
-        .subdir(
-            "start",
-            Router::new()
-                .post(
-                    ApiMethod::new(
-                        start_service,
-                        ObjectSchema::new("Start service.")
-                            .required("node", crate::api2::node::NODE_SCHEMA.clone())
-                            .required("service", service_id_schema.clone())
-                    ).protected(true)
-                )
-        )
-        .subdir(
-            "stop",
-            Router::new()
-                .post(
-                    ApiMethod::new(
-                        stop_service,
-                        ObjectSchema::new("Stop service.")
-                            .required("node", crate::api2::node::NODE_SCHEMA.clone())
-                            .required("service", service_id_schema.clone())
-                    ).protected(true)
-                )
-        )
-        .subdir(
-            "restart",
-            Router::new()
-                .post(
-                    ApiMethod::new(
-                        restart_service,
-                        ObjectSchema::new("Restart service.")
-                            .required("node", crate::api2::node::NODE_SCHEMA.clone())
-                            .required("service", service_id_schema.clone())
-                    ).protected(true)
-                )
-        )
-        .subdir(
-            "reload",
-            Router::new()
-                .post(
-                    ApiMethod::new(
-                        reload_service,
-                        ObjectSchema::new("Reload service.")
-                            .required("node", crate::api2::node::NODE_SCHEMA.clone())
-                            .required("service", service_id_schema.clone())
-                    ).protected(true)
-                )
-        )
-        .list_subdirs();
-
-    let route = Router::new()
-        .get(
-            ApiMethod::new(
-                list_services,
-                ObjectSchema::new("Service list.")
-                    .required("node", crate::api2::node::NODE_SCHEMA.clone())
-            ).returns(
-                ArraySchema::new(
-                    "Returns a list of systemd services.",
-                    ObjectSchema::new("Service details.")
-                        .required("service", service_id_schema.clone())
-                        .required("name", StringSchema::new("systemd service name."))
-                        .required("desc", StringSchema::new("systemd service description."))
-                        .required("state", StringSchema::new("systemd service 'SubState'."))
-                        .into()
-                )
-            )
-        )
-        .match_all("service", service_api);
-
-    route
-}
+#[sortable]
+const SERVICE_SUBDIRS: SubdirMap = &sorted!([
+    (
+        "reload", &Router::new()
+            .post(&API_METHOD_RELOAD_SERVICE)
+    ),
+    (
+        "restart", &Router::new()
+            .post(&API_METHOD_RESTART_SERVICE)
+    ),
+    (
+        "start", &Router::new()
+            .post(&API_METHOD_START_SERVICE)
+    ),
+    (
+        "state", &Router::new()
+            .get(&API_METHOD_GET_SERVICE_STATE)
+    ),
+    (
+        "stop", &Router::new()
+            .post(&API_METHOD_STOP_SERVICE)
+    ),
+]);
+
+const SERVICE_ROUTER: Router = Router::new()
+    .get(&list_subdirs_api_method!(SERVICE_SUBDIRS))
+    .subdirs(SERVICE_SUBDIRS);
+
+pub const ROUTER: Router = Router::new()
+    .get(&API_METHOD_LIST_SERVICES)
+    .match_all("service", &SERVICE_ROUTER);