From 99a304db097f31f7810198dd1d0f5cd47ab4033a Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 9 Sep 2016 10:49:44 +0200 Subject: Support optional fields in config for backwards compatibility --- README.md | 82 ++++---- docs/README.adoc | 7 - run/sota.toml.env | 13 +- run/sota.toml.template | 5 +- src/datatype/config.rs | 441 ++++++++++++++++++++++++++++++++++++-------- src/datatype/error.rs | 2 + src/datatype/mod.rs | 8 +- src/datatype/network.rs | 137 ++++++++++++++ src/datatype/shell.rs | 12 ++ src/datatype/system_info.rs | 41 ---- src/datatype/url.rs | 106 ----------- src/gateway/http.rs | 15 +- src/http/auth_client.rs | 2 +- src/interpreter.rs | 19 +- src/main.rs | 185 +++++++++++-------- src/rvi/edge.rs | 16 +- tests/config_tests.rs | 36 ++++ tests/genivi.sota.toml | 40 ---- tests/sota.toml | 45 ----- tests/sota_client_tests.rs | 134 -------------- tests/toml/default.toml | 46 +++++ tests/toml/genivi.toml | 30 +++ tests/toml/old.toml | 45 +++++ tests/toml/polling.toml | 5 + 24 files changed, 859 insertions(+), 613 deletions(-) delete mode 100644 docs/README.adoc create mode 100644 src/datatype/network.rs create mode 100644 src/datatype/shell.rs delete mode 100644 src/datatype/system_info.rs delete mode 100644 src/datatype/url.rs create mode 100644 tests/config_tests.rs delete mode 100644 tests/genivi.sota.toml delete mode 100644 tests/sota.toml delete mode 100644 tests/sota_client_tests.rs create mode 100644 tests/toml/default.toml create mode 100644 tests/toml/genivi.toml create mode 100644 tests/toml/old.toml create mode 100644 tests/toml/polling.toml diff --git a/README.md b/README.md index 673e199..5f3c089 100644 --- a/README.md +++ b/README.md @@ -2,36 +2,23 @@ The client source repository for [Software Over The Air](http://advancedtelematic.github.io/rvi_sota_server/) updates. +Click [here](http://advancedtelematic.github.io/rvi_sota_server/cli/client-commands-and-events-reference.html) for the complete `Command` and `Event` API reference used for communicating with the client. + ## Prerequisites The simplest way to get started is via [Docker](http://www.docker.com), which is used for compiling and running the client. Alternatively (and optionally), local compilation requires a stable installation of Rust and Cargo. The easiest way to install both is via [Rustup](https://www.rustup.rs). -## Running the client - -With Docker installed, `make run` will start the client. - -### Makefile targets +## Configuration -Run `make help` to see the full list of targets, which are: +See [here](http://advancedtelematic.github.io/rvi_sota_server/cli/client-startup-and-configuration.html) for full details on configuring the client on startup. -Target | Description --------------: | :---------- -run | Run the client inside a Docker container. -clean | Remove all compiled libraries, builds and temporary files. -test | Run all cargo tests. -doc | Generate documentation for the sota crate. -clippy | Run clippy lint checks using the nightly compiler. -client | Compile a new release build of the client. -image | Build a Docker image for running the client. -deb | Create an installable DEB package of the client. -rpm | Create an installable RPM package of the client. -version | Print the version that will be used for building packages. +### Makefile configuration -## Configuration +Run `make help` to see the full list of targets. -You can configure how the client starts with `make run` by setting the following environment variables: +With Docker installed, `make run` will start the client. You can configure how the client starts by setting the following environment variables: Variable | Default value | Description -------------------: | :------------------------ | :------------------ @@ -49,55 +36,52 @@ Variable | Default value | Description For example, running `CONFIG_ONLY=true make run` will output a newly generated `sota.toml` to stdout then quit. -### Further customization - -Every value in the generated `sota.toml` config file can be overwritten in the `run/sota.toml.env` file. +Every value in the generated `sota.toml` config file can be overwritten in the `run/sota.toml.env` file. In addition, each config value is available as a command line flag when starting the client. Command line flags take precedence over the values set in the config file. Run `sota_client --help` to see a full list. -In addition, each config value is available as a command line flag when starting the client. Command line flags take precedence over the values set in the config file. Run `sota_client --help` to see a full list. - -## Testing on GENIVI Development Platform over RVI - -### Starting the SOTA Server +## GENIVI Development Platform over RVI See the full documentation at [rvi_sota_server](http://advancedtelematic.github.io/rvi_sota_server/). -Here is a quickstart: +### Starting the SOTA Server - git clone git@github.com:advancedtelematic/rvi_sota_server.git rvi_sota_server - cd rvi_sota_server - ./sbt docker:publishLocal - cd deploy/docker-compose - docker-compose -f docker-compose.yml -f core-rvi.yml -f client-rvi.yml up -d +To get started: -Login to the UI and create a new Device/Vehicle. Copy the newly generated Device UUID (e.g. "9ea653bc-3486-44cd-aa86-d936bd957e52") into the `client-rvi.yml` file as environment variable `DEVICE_ID`: + git clone https://github.com/advancedtelematic/rvi_sota_server rvi_sota_server + cd rvi_sota_server + ./sbt docker:publishLocal + cd deploy/docker-compose + docker-compose -f docker-compose.yml -f core-rvi.yml -f client-rvi.yml up -d -``` - environment: - RVI_BACKEND: "rvi_backend" - DEVICE_ID: "9ea653bc-3486-44cd-aa86-d936bd957e52" -``` +Log in to the UI and create a new Device/Vehicle. Click the device name to view it then copy the UUID from the URL (e.g. "9ea653bc-3486-44cd-aa86-d936bd957e52"). -Restart the RVI device node with the new DEVICE_ID by re-running: +In the `client-rvi.yml` file, replace the `DEVICE_ID` environment variable with the UUID copied above. Now restart the RVI device node with: docker-compose -f docker-compose.yml -f core-rvi.yml -f client-rvi.yml up -d ### Configuring sota.toml -The `uuid` field in the `[device]` section must match the DEVICE_ID of the RVI node (e.g. "9ea653bc-3486-44cd-aa86-d936bd957e52"). +See `tests/toml/genivi.toml` for a sample config. -The `rvi` and `dbus` fields in the `[gateway]` section must be `true`. +The `uuid` field in the `[device]` section must match the DEVICE_ID of the RVI node (e.g. "9ea653bc-3486-44cd-aa86-d936bd957e52"). In addition, set the `rvi` and `dbus` fields in the `[gateway]` section to `true`. As the RVI device node is running inside a docker container (and thus cannot access 127.0.0.1 on the host), all URI fields should contain non-loopback IP addresses. -See `tests/genivi.sota.toml` for a sample config. See full documentation for details. - -Now you can run the `sota_client`: +Now you can start the client: make client - RUST_LOG=debug ./run/sota_client --config tests/genivi.sota.toml + RUST_LOG=debug run/sota_client --config tests/toml/genivi.sota.toml ### GENIVI Software Loading Manager -See [genivi_swm](https://github.com/GENIVI/genivi_swm) on how to run the Software Loading Manager demo. It also contains instructions for creating an update image, which can be uploaded as a package to the SOTA Server. +See [genivi_swm](https://github.com/GENIVI/genivi_swm) for complete instructions on how to run the Software Loading Manager (SWM) demo, including instructions on creating a new update image to upload to the SOTA Server. + +To get started: + + git clone https://github.com/GENIVI/genivi_swm + cd genivi_swm + export PYTHONPATH="${PWD}/common/" + python software_loading_manager/software_loading_manager.py + +As the genivi_swm demo runs as root, remember to run the `sota_client` as root as well so that they can communicate on the same system bus. -Now you can create an update campaign on the SOTA Server, using the same update_id as the uuid in the update image you created. Also, as the genivi_swm demo runs as root, remember to run the `sota_client` as root as well so that they can communicate on the same system bus. +Now you can create an update campaign on the SOTA Server using the same update_id as the uuid in the update image you created. diff --git a/docs/README.adoc b/docs/README.adoc deleted file mode 100644 index 9a48874..0000000 --- a/docs/README.adoc +++ /dev/null @@ -1,7 +0,0 @@ -== Client configuration - -All the information that used to be here has been moved to the main SOTA docs site. - -http://advancedtelematic.github.io/rvi_sota_server/cli/client-startup-and-configuration.html[Client Startup and Configuration] - -http://advancedtelematic.github.io/rvi_sota_server/cli/client-commands-and-events-reference.html[Client commands and events API reference] diff --git a/run/sota.toml.env b/run/sota.toml.env index cd7d460..08841aa 100644 --- a/run/sota.toml.env +++ b/run/sota.toml.env @@ -2,6 +2,8 @@ AUTH_SERVER=http://127.0.0.1:9001 AUTH_CREDENTIALS_FILE=/opt/sota/credentials.toml CORE_SERVER=http://127.0.0.1:8080 +CORE_POLLING=true +CORE_POLLING_SEC=10 DBUS_NAME=org.genivi.SotaClient DBUS_PATH=/org/genivi/SotaClient @@ -11,20 +13,19 @@ DBUS_SOFTWARE_MANAGER_PATH=/org/genivi/SoftwareLoadingManager DBUS_TIMEOUT=60 DEVICE_PACKAGES_DIR=/tmp/ -DEVICE_PACKAGE_MANAGER=deb -DEVICE_POLLING_INTERVAL=10 +DEVICE_PACKAGE_MANAGER=off DEVICE_CERTIFICATES_PATH=/etc/sota_certificates -DEVICE_SYSTEM_INFO=./system_info.sh +DEVICE_SYSTEM_INFO=system_info.sh GATEWAY_CONSOLE=false GATEWAY_DBUS=false GATEWAY_HTTP=false GATEWAY_RVI=false GATEWAY_SOCKET=false -GATEWAY_WEBSOCKET=true +GATEWAY_WEBSOCKET=false -NETWORK_HTTP_SERVER=http://127.0.0.1:8888 -NETWORK_RVI_EDGE_SERVER=http://127.0.0.1:9080 +NETWORK_HTTP_SERVER=127.0.0.1:8888 +NETWORK_RVI_EDGE_SERVER=127.0.0.1:9080 NETWORK_SOCKET_COMMANDS_PATH=/tmp/sota-commands.socket NETWORK_SOCKET_EVENTS_PATH=/tmp/sota-events.socket NETWORK_WEBSOCKET_SERVER=127.0.0.1:3012 diff --git a/run/sota.toml.template b/run/sota.toml.template index 1a68ac6..06e4e71 100644 --- a/run/sota.toml.template +++ b/run/sota.toml.template @@ -6,6 +6,8 @@ credentials_file = "${AUTH_CREDENTIALS_FILE}" [core] server = "${CORE_SERVER}" +polling = ${CORE_POLLING} +polling_sec = ${CORE_POLLING_SEC} [dbus] name = "${DBUS_NAME}" @@ -20,9 +22,8 @@ uuid = "${DEVICE_UUID}" vin = "${DEVICE_VIN}" packages_dir = "${DEVICE_PACKAGES_DIR}" package_manager = "${DEVICE_PACKAGE_MANAGER}" -system_info = "${DEVICE_SYSTEM_INFO}" -polling_interval = ${DEVICE_POLLING_INTERVAL} certificates_path = "${DEVICE_CERTIFICATES_PATH}" +system_info = "${DEVICE_SYSTEM_INFO}" [gateway] console = ${GATEWAY_CONSOLE} diff --git a/src/datatype/config.rs b/src/datatype/config.rs index c9905f5..ad80c42 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -6,13 +6,13 @@ use std::os::unix::fs::PermissionsExt; use std::io::prelude::*; use std::path::Path; use toml; -use toml::{Decoder, Parser, Table, Value}; +use toml::{Decoder, Parser, Table}; -use datatype::{Error, SystemInfo, Url}; +use datatype::{Error, SocketAddr, Url}; use package_manager::PackageManager; -/// An aggregation of all the configuration options parsed at startup. +/// A container for all parsed configs. #[derive(Default, PartialEq, Eq, Debug, Clone)] pub struct Config { pub auth: Option, @@ -25,6 +25,8 @@ pub struct Config { } impl Config { + /// Read in a toml configuration file using default values for missing + /// sections or fields. pub fn load(path: &str) -> Result { info!("Loading config file: {}", path); let mut file = try!(File::open(path).map_err(Error::Io)); @@ -33,36 +35,34 @@ impl Config { Config::parse(&toml) } + /// Parse a toml configuration string using default values for missing + /// sections or fields while retaining backwards compatibility. pub fn parse(toml: &str) -> Result { let table = try!(parse_table(&toml)); - let auth_cfg = if let Some(auth) = table.get("auth") { - let parsed = try!(decode_section(auth.clone())); - Some(try!(bootstrap_credentials(parsed))) - } else { - None - }; - - let dbus_cfg = if let Some(dbus) = table.get("dbus") { - Some(try!(decode_section(dbus.clone()))) - } else { - None - }; - - let rvi_cfg = if let Some(rvi) = table.get("rvi") { - Some(try!(decode_section(rvi.clone()))) - } else { - None - }; + let mut auth: Option = try!(maybe_parse_section(&table, "auth")); + let mut core: ParsedCoreConfig = try!(parse_section(&table, "core")); + let mut dbus: Option = try!(maybe_parse_section(&table, "dbus")); + let mut device: ParsedDeviceConfig = try!(parse_section(&table, "device")); + let mut gateway: ParsedGatewayConfig = try!(parse_section(&table, "gateway")); + let mut network: ParsedNetworkConfig = try!(parse_section(&table, "network")); + let mut rvi: Option = try!(maybe_parse_section(&table, "rvi")); + + if let Some(cfg) = auth { + auth = Some(try!(bootstrap_credentials(cfg))); + } + + try!(apply_transformations(&mut auth, &mut core, &mut dbus, &mut device, + &mut gateway, &mut network, &mut rvi)); Ok(Config { - auth: auth_cfg, - core: try!(read_section(&table, "core")), - dbus: dbus_cfg, - device: try!(read_section(&table, "device")), - gateway: try!(read_section(&table, "gateway")), - network: try!(read_section(&table, "network")), - rvi: rvi_cfg, + auth: auth.map(|mut cfg| cfg.defaultify()), + core: core.defaultify(), + dbus: dbus.map(|mut cfg| cfg.defaultify()), + device: device.defaultify(), + gateway: gateway.defaultify(), + network: network.defaultify(), + rvi: rvi.map(|mut cfg| cfg.defaultify()) }) } } @@ -72,15 +72,15 @@ fn parse_table(toml: &str) -> Result { Ok(try!(parser.parse().ok_or_else(move || parser.errors))) } -fn read_section(table: &Table, section: &str) -> Result { - let part = try!(table.get(section) - .ok_or_else(|| Error::Parse(format!("invalid section: {}", section)))); - decode_section(part.clone()) +fn parse_section(table: &Table, section: &str) -> Result { + Ok(try!(maybe_parse_section(table, section)).unwrap_or(T::default())) } -fn decode_section(section: Value) -> Result { - let mut decoder = Decoder::new(section); - Ok(try!(T::decode(&mut decoder))) +fn maybe_parse_section(table: &Table, section: &str) -> Result, Error> { + table.get(section).map_or(Ok(None), |sect| { + let mut decoder = Decoder::new(sect.clone()); + Ok(Some(try!(T::decode(&mut decoder)))) + }) } @@ -92,8 +92,8 @@ struct CredentialsFile { // Read AuthConfig values from the credentials file if it exists, or write the // current AuthConfig values to a new credentials file otherwise. -fn bootstrap_credentials(auth_cfg: AuthConfig) -> Result { - let creds = auth_cfg.credentials_file.clone(); +fn bootstrap_credentials(auth: ParsedAuthConfig) -> Result { + let creds = auth.credentials_file.expect("couldn't get credentials_file"); let path = Path::new(&creds); debug!("bootstrap_credentials: {:?}", path); @@ -102,14 +102,16 @@ fn bootstrap_credentials(auth_cfg: AuthConfig) -> Result { let mut text = String::new(); try!(file.read_to_string(&mut text)); let table = try!(parse_table(&text)); - try!(read_section::(&table, "auth")) + let auth = try!(table.get("auth").ok_or(Error::Config("no [auth] section".to_string()))); + let mut decoder = Decoder::new(auth.clone()); + try!(CredentialsFile::decode(&mut decoder)) } Err(ref err) if err.kind() == ErrorKind::NotFound => { let mut table = Table::new(); let credentials = CredentialsFile { - client_id: auth_cfg.client_id, - client_secret: auth_cfg.client_secret + client_id: auth.client_id.expect("expected client_id"), + client_secret: auth.client_secret.expect("expected client_secret") }; table.insert("auth".to_string(), toml::encode(&credentials)); @@ -127,16 +129,52 @@ fn bootstrap_credentials(auth_cfg: AuthConfig) -> Result { Err(err) => return Err(Error::Io(err)) }; - Ok(AuthConfig { - server: auth_cfg.server, - client_id: credentials.client_id, - client_secret: credentials.client_secret, - credentials_file: auth_cfg.credentials_file, + Ok(ParsedAuthConfig { + server: auth.server, + client_id: Some(credentials.client_id), + client_secret: Some(credentials.client_secret), + credentials_file: Some(creds.clone()), }) } -/// A parsed representation of the [auth] configuration section. +// Apply transformations from old to new config fields for backwards compatibility. +fn apply_transformations(_: &mut Option, + core: &mut ParsedCoreConfig, + _: &mut Option, + device: &mut ParsedDeviceConfig, + _: &mut ParsedGatewayConfig, + _: &mut ParsedNetworkConfig, + _: &mut Option) -> Result<(), Error> { + + match (device.polling_interval, core.polling_sec) { + (Some(_), Some(_)) => { + return Err(Error::Config("core.polling_sec and device.polling_interval both set".to_string())) + } + + (Some(interval), None) => { + if interval > 0 { + core.polling = Some(true); + core.polling_sec = Some(interval); + } else { + core.polling = Some(false); + } + } + + _ => () + } + + Ok(()) +} + + +/// Trait used to overwrite any `None` fields in a config with its default value. +trait Defaultify { + fn defaultify(&mut self) -> T; +} + + +/// The [auth] configuration section. #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct AuthConfig { pub server: Url, @@ -146,7 +184,7 @@ pub struct AuthConfig { } impl Default for AuthConfig { - fn default() -> AuthConfig { + fn default() -> Self { AuthConfig { server: "http://127.0.0.1:9001".parse().unwrap(), client_id: "client-id".to_string(), @@ -156,23 +194,86 @@ impl Default for AuthConfig { } } +#[derive(RustcDecodable)] +struct ParsedAuthConfig { + server: Option, + client_id: Option, + client_secret: Option, + credentials_file: Option, +} + +impl Default for ParsedAuthConfig { + fn default() -> Self { + ParsedAuthConfig { + server: None, + client_id: None, + client_secret: None, + credentials_file: None + } + } +} + +impl Defaultify for ParsedAuthConfig { + fn defaultify(&mut self) -> AuthConfig { + let default = AuthConfig::default(); + AuthConfig { + server: self.server.take().unwrap_or(default.server), + client_id: self.client_id.take().unwrap_or(default.client_id), + client_secret: self.client_secret.take().unwrap_or(default.client_secret), + credentials_file: self.credentials_file.take().unwrap_or(default.credentials_file) + } + } +} + -/// A parsed representation of the [core] configuration section. +/// The [core] configuration section. #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct CoreConfig { - pub server: Url + pub server: Url, + pub polling: bool, + pub polling_sec: u64 } impl Default for CoreConfig { fn default() -> CoreConfig { CoreConfig { - server: "http://127.0.0.1:8080".parse().unwrap() + server: "http://127.0.0.1:8080".parse().unwrap(), + polling: true, + polling_sec: 10 } } } +#[derive(RustcDecodable)] +struct ParsedCoreConfig { + server: Option, + polling: Option, + polling_sec: Option +} + +impl Default for ParsedCoreConfig { + fn default() -> Self { + ParsedCoreConfig { + server: None, + polling: None, + polling_sec: None + } + } +} + +impl Defaultify for ParsedCoreConfig { + fn defaultify(&mut self) -> CoreConfig { + let default = CoreConfig::default(); + CoreConfig { + server: self.server.take().unwrap_or(default.server), + polling: self.polling.take().unwrap_or(default.polling), + polling_sec: self.polling_sec.take().unwrap_or(default.polling_sec) + } + } +} -/// A parsed representation of the [dbus] configuration section. + +/// The [dbus] configuration section. #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct DBusConfig { pub name: String, @@ -180,7 +281,7 @@ pub struct DBusConfig { pub interface: String, pub software_manager: String, pub software_manager_path: String, - pub timeout: i32, // dbus-rs expects a signed int + pub timeout: i32, } impl Default for DBusConfig { @@ -196,17 +297,53 @@ impl Default for DBusConfig { } } +#[derive(RustcDecodable)] +struct ParsedDBusConfig { + name: Option, + path: Option, + interface: Option, + software_manager: Option, + software_manager_path: Option, + timeout: Option, +} + +impl Default for ParsedDBusConfig { + fn default() -> Self { + ParsedDBusConfig { + name: None, + path: None, + interface: None, + software_manager: None, + software_manager_path: None, + timeout: None + } + } +} + +impl Defaultify for ParsedDBusConfig { + fn defaultify(&mut self) -> DBusConfig { + let default = DBusConfig::default(); + DBusConfig { + name: self.name.take().unwrap_or(default.name), + path: self.path.take().unwrap_or(default.path), + interface: self.interface.take().unwrap_or(default.interface), + software_manager: self.software_manager.take().unwrap_or(default.software_manager), + software_manager_path: self.software_manager_path.take().unwrap_or(default.software_manager_path), + timeout: self.timeout.take().unwrap_or(default.timeout) + } + } +} + -/// A parsed representation of the [device] configuration section. +/// The [device] configuration section. #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct DeviceConfig { pub uuid: String, pub vin: String, pub packages_dir: String, pub package_manager: PackageManager, - pub system_info: Option, - pub polling_interval: u64, pub certificates_path: String, + pub system_info: Option, } impl Default for DeviceConfig { @@ -215,16 +352,54 @@ impl Default for DeviceConfig { uuid: "123e4567-e89b-12d3-a456-426655440000".to_string(), vin: "V1234567890123456".to_string(), packages_dir: "/tmp/".to_string(), - package_manager: PackageManager::Deb, - system_info: Some(SystemInfo::default()), - polling_interval: 10, - certificates_path: "/tmp/sota_certificates".to_string() + package_manager: PackageManager::Off, + certificates_path: "/tmp/sota_certificates".to_string(), + system_info: Some("system_info.sh".to_string()) } } } +#[derive(RustcDecodable)] +struct ParsedDeviceConfig { + pub uuid: Option, + pub vin: Option, + pub packages_dir: Option, + pub package_manager: Option, + pub polling_interval: Option, + pub certificates_path: Option, + pub system_info: Option, +} -/// A parsed representation of the [gateway] configuration section. +impl Default for ParsedDeviceConfig { + fn default() -> Self { + ParsedDeviceConfig { + uuid: None, + vin: None, + packages_dir: None, + package_manager: None, + polling_interval: None, + certificates_path: None, + system_info: None, + } + } +} + +impl Defaultify for ParsedDeviceConfig { + fn defaultify(&mut self) -> DeviceConfig { + let default = DeviceConfig::default(); + DeviceConfig { + uuid: self.uuid.take().unwrap_or(default.uuid), + vin: self.vin.take().unwrap_or(default.vin), + packages_dir: self.packages_dir.take().unwrap_or(default.packages_dir), + package_manager: self.package_manager.take().unwrap_or(default.package_manager), + certificates_path: self.certificates_path.take().unwrap_or(default.certificates_path), + system_info: self.system_info.take().or(default.system_info), + } + } +} + + +/// The [gateway] configuration section. #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct GatewayConfig { pub console: bool, @@ -243,17 +418,54 @@ impl Default for GatewayConfig { http: false, rvi: false, socket: false, - websocket: true, + websocket: false, } } } +#[derive(RustcDecodable)] +struct ParsedGatewayConfig { + console: Option, + dbus: Option, + http: Option, + rvi: Option, + socket: Option, + websocket: Option, +} -/// A parsed representation of the [network] configuration section. +impl Default for ParsedGatewayConfig { + fn default() -> Self { + ParsedGatewayConfig { + console: None, + dbus: None, + http: None, + rvi: None, + socket: None, + websocket: None + } + } +} + +impl Defaultify for ParsedGatewayConfig { + fn defaultify(&mut self) -> GatewayConfig { + let default = GatewayConfig::default(); + GatewayConfig { + console: self.console.take().unwrap_or(default.console), + dbus: self.dbus.take().unwrap_or(default.dbus), + http: self.http.take().unwrap_or(default.http), + rvi: self.rvi.take().unwrap_or(default.rvi), + socket: self.socket.take().unwrap_or(default.socket), + websocket: self.websocket.take().unwrap_or(default.websocket) + } + } +} + + +/// The [network] configuration section. #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct NetworkConfig { - pub http_server: String, - pub rvi_edge_server: String, + pub http_server: SocketAddr, + pub rvi_edge_server: SocketAddr, pub socket_commands_path: String, pub socket_events_path: String, pub websocket_server: String @@ -262,8 +474,8 @@ pub struct NetworkConfig { impl Default for NetworkConfig { fn default() -> NetworkConfig { NetworkConfig { - http_server: "http://127.0.0.1:8888".to_string(), - rvi_edge_server: "http://127.0.0.1:9080".to_string(), + http_server: "127.0.0.1:8888".parse().unwrap(), + rvi_edge_server: "127.0.0.1:9080".parse().unwrap(), socket_commands_path: "/tmp/sota-commands.socket".to_string(), socket_events_path: "/tmp/sota-events.socket".to_string(), websocket_server: "127.0.0.1:3012".to_string() @@ -271,8 +483,42 @@ impl Default for NetworkConfig { } } +#[derive(RustcDecodable)] +struct ParsedNetworkConfig { + http_server: Option, + rvi_edge_server: Option, + socket_commands_path: Option, + socket_events_path: Option, + websocket_server: Option +} + +impl Default for ParsedNetworkConfig { + fn default() -> Self { + ParsedNetworkConfig { + http_server: None, + rvi_edge_server: None, + socket_commands_path: None, + socket_events_path: None, + websocket_server: None + } + } +} + +impl Defaultify for ParsedNetworkConfig { + fn defaultify(&mut self) -> NetworkConfig { + let default = NetworkConfig::default(); + NetworkConfig { + http_server: self.http_server.take().unwrap_or(default.http_server), + rvi_edge_server: self.rvi_edge_server.take().unwrap_or(default.rvi_edge_server), + socket_commands_path: self.socket_commands_path.take().unwrap_or(default.socket_commands_path), + socket_events_path: self.socket_events_path.take().unwrap_or(default.socket_events_path), + websocket_server: self.websocket_server.take().unwrap_or(default.websocket_server) + } + } +} -/// A parsed representation of the [rvi] configuration section. + +/// The [rvi] configuration section. #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct RviConfig { pub client: Url, @@ -285,7 +531,35 @@ impl Default for RviConfig { RviConfig { client: "http://127.0.0.1:8901".parse().unwrap(), storage_dir: "/var/sota".to_string(), - timeout: Some(20), + timeout: None, + } + } +} + +#[derive(RustcDecodable)] +struct ParsedRviConfig { + client: Option, + storage_dir: Option, + timeout: Option, +} + +impl Default for ParsedRviConfig { + fn default() -> Self { + ParsedRviConfig { + client: None, + storage_dir: None, + timeout: None + } + } +} + +impl Defaultify for ParsedRviConfig { + fn defaultify(&mut self) -> RviConfig { + let default = RviConfig::default(); + RviConfig { + client: self.client.take().unwrap_or(default.client), + storage_dir: self.storage_dir.take().unwrap_or(default.storage_dir), + timeout: self.timeout.take().or(default.timeout) } } } @@ -309,6 +583,8 @@ mod tests { r#" [core] server = "http://127.0.0.1:8080" + polling = true + polling_sec = 10 "#; const DBUS_CONFIG: &'static str = @@ -327,11 +603,10 @@ mod tests { [device] uuid = "123e4567-e89b-12d3-a456-426655440000" vin = "V1234567890123456" - system_info = "./system_info.sh" - polling_interval = 10 packages_dir = "/tmp/" - package_manager = "deb" + package_manager = "off" certificates_path = "/tmp/sota_certificates" + system_info = "system_info.sh" "#; const GATEWAY_CONFIG: &'static str = @@ -342,14 +617,14 @@ mod tests { http = false rvi = false socket = false - websocket = true + websocket = false "#; const NETWORK_CONFIG: &'static str = r#" [network] - http_server = "http://127.0.0.1:8888" - rvi_edge_server = "http://127.0.0.1:9080" + http_server = "127.0.0.1:8888" + rvi_edge_server = "127.0.0.1:9080" socket_commands_path = "/tmp/sota-commands.socket" socket_events_path = "/tmp/sota-events.socket" websocket_server = "127.0.0.1:3012" @@ -365,7 +640,12 @@ mod tests { #[test] - fn parse_default_config() { + fn empty_config() { + assert_eq!(Config::parse("").unwrap(), Config::default()); + } + + #[test] + fn basic_config() { let config = String::new() + CORE_CONFIG + DEVICE_CONFIG @@ -375,7 +655,7 @@ mod tests { } #[test] - fn parse_example_config() { + fn default_config() { let config = String::new() + AUTH_CONFIG + CORE_CONFIG @@ -384,6 +664,13 @@ mod tests { + GATEWAY_CONFIG + NETWORK_CONFIG + RVI_CONFIG; - assert_eq!(Config::load("tests/sota.toml").unwrap(), Config::parse(&config).unwrap()); + assert_eq!(Config::load("tests/toml/default.toml").unwrap(), Config::parse(&config).unwrap()); + } + + #[test] + fn backwards_compatible_config() { + let config = Config::load("tests/toml/old.toml").unwrap(); + assert_eq!(config.core.polling, true); + assert_eq!(config.core.polling_sec, 10); } } diff --git a/src/datatype/error.rs b/src/datatype/error.rs index bb0ab4e..9503e2c 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -23,6 +23,7 @@ use ws::Error as WebsocketError; pub enum Error { Client(String), Command(String), + Config(String), FromUtf8(FromUtf8Error), Http(ResponseData), HttpAuth(ResponseData), @@ -95,6 +96,7 @@ impl Display for Error { let inner: String = match *self { Error::Client(ref s) => format!("Http client error: {}", s.clone()), Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), + Error::Config(ref s) => format!("Bad Config: {}", s.clone()), Error::FromUtf8(ref e) => format!("From utf8 error: {}", e.clone()), Error::Http(ref r) => format!("HTTP client error: {}", r.clone()), Error::HttpAuth(ref r) => format!("HTTP authorization error: {}", r.clone()), diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 017868c..71e7e0a 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -5,10 +5,10 @@ pub mod dbus; pub mod error; pub mod event; pub mod json_rpc; -pub mod system_info; +pub mod network; +pub mod shell; pub mod update_report; pub mod update_request; -pub mod url; pub use self::auth::{AccessToken, Auth, ClientCredentials}; pub use self::command::Command; @@ -17,11 +17,11 @@ pub use self::config::{AuthConfig, CoreConfig, Config, DBusConfig, DeviceConfig, pub use self::error::Error; pub use self::event::Event; pub use self::json_rpc::{RpcRequest, RpcOk, RpcErr}; -pub use self::system_info::SystemInfo; +pub use self::network::{Method, SocketAddr, Url}; +pub use self::shell::system_info; pub use self::update_report::{DeviceReport, InstalledFirmware, InstalledPackage, InstalledSoftware, OperationResult, UpdateResultCode, UpdateReport}; pub use self::update_request::{ChunkReceived, DownloadComplete, DownloadFailed, DownloadStarted, Package, UpdateAvailable, UpdateRequest, UpdateRequestId, UpdateRequestStatus}; -pub use self::url::{Method, Url}; diff --git a/src/datatype/network.rs b/src/datatype/network.rs new file mode 100644 index 0000000..56f88ae --- /dev/null +++ b/src/datatype/network.rs @@ -0,0 +1,137 @@ +use hyper::method; +use rustc_serialize::{Decoder, Decodable}; +use std::borrow::Cow; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::net::{SocketAddr as StdSocketAddr}; +use std::ops::Deref; +use std::str::FromStr; +use url; + +use datatype::Error; + + +/// Encapsulate a socket address for implementing additional traits. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SocketAddr(pub StdSocketAddr); + +impl Decodable for SocketAddr { + fn decode(d: &mut D) -> Result { + let addr = try!(d.read_str()); + addr.parse().or_else(|err| Err(d.error(&format!("{}", err)))) + } +} + +impl FromStr for SocketAddr { + type Err = Error; + + fn from_str(s: &str) -> Result { + match StdSocketAddr::from_str(s) { + Ok(addr) => Ok(SocketAddr(addr)), + Err(err) => Err(Error::Parse(format!("couldn't parse SocketAddr: {}", err))) + } + } +} + +impl Deref for SocketAddr { + type Target = StdSocketAddr; + + fn deref(&self) -> &StdSocketAddr { + &self.0 + } +} + +impl Display for SocketAddr { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{}", self.0) + } +} + + +/// Encapsulate a url with additional methods and traits. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Url(pub url::Url); + +impl Url { + /// Append the string suffix to this URL. + pub fn join(&self, suffix: &str) -> Result { + let url = try!(self.0.join(suffix)); + Ok(Url(url)) + } +} + +impl<'a> Into> for Url { + fn into(self) -> Cow<'a, Url> { + Cow::Owned(self) + } +} + +impl FromStr for Url { + type Err = Error; + + fn from_str(s: &str) -> Result { + let url = try!(url::Url::parse(s)); + Ok(Url(url)) + } +} + +impl Decodable for Url { + fn decode(d: &mut D) -> Result { + let s = try!(d.read_str()); + s.parse().map_err(|e: Error| d.error(&e.to_string())) + } +} + +impl Deref for Url { + type Target = url::Url; + + fn deref(&self) -> &url::Url { + &self.0 + } +} + +impl Display for Url { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let host = self.0.host_str().unwrap_or("localhost"); + if let Some(port) = self.0.port() { + write!(f, "{}://{}:{}{}", self.0.scheme(), host, port, self.0.path()) + } else { + write!(f, "{}://{}{}", self.0.scheme(), host, self.0.path()) + } + } +} + + +/// Enumerate the supported HTTP methods. +#[derive(Clone, Debug)] +pub enum Method { + Get, + Post, + Put, +} + +impl Into for Method { + fn into(self) -> method::Method { + match self { + Method::Get => method::Method::Get, + Method::Post => method::Method::Post, + Method::Put => method::Method::Put, + } + } +} + +impl<'a> Into> for Method { + fn into(self) -> Cow<'a, Method> { + Cow::Owned(self) + } +} + +impl Display for Method { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let method = match *self { + Method::Get => "GET".to_string(), + Method::Post => "POST".to_string(), + Method::Put => "PUT".to_string(), + }; + write!(f, "{}", method) + } +} diff --git a/src/datatype/shell.rs b/src/datatype/shell.rs new file mode 100644 index 0000000..d7cd4af --- /dev/null +++ b/src/datatype/shell.rs @@ -0,0 +1,12 @@ +use std::process::Command; + +use datatype::Error; + + +/// Generate a new system information report. +pub fn system_info(cmd: &str) -> Result { + Command::new(cmd) + .output() + .map_err(|err| Error::SystemInfo(err.to_string())) + .and_then(|info| String::from_utf8(info.stdout).map_err(Error::FromUtf8)) +} diff --git a/src/datatype/system_info.rs b/src/datatype/system_info.rs deleted file mode 100644 index 987da3b..0000000 --- a/src/datatype/system_info.rs +++ /dev/null @@ -1,41 +0,0 @@ -use rustc_serialize::{Decoder, Decodable}; -use std::process::Command; - -use datatype::Error; - - -/// A reference to the command that will report on the system information. -#[derive(PartialEq, Eq, Debug, Clone)] -pub struct SystemInfo { - command: String -} - -impl SystemInfo { - /// Instantiate a new type to report on the system information. - pub fn new(command: &str) -> Option { - if command == "" { - None - } else { - Some(SystemInfo { command: command.to_string() }) - } - } - - /// Generate a new report of the system information. - pub fn report(&self) -> Result { - Command::new(&self.command) - .output().map_err(|err| Error::SystemInfo(err.to_string())) - .and_then(|info| String::from_utf8(info.stdout).map_err(Error::FromUtf8)) - } -} - -impl Default for SystemInfo { - fn default() -> SystemInfo { - SystemInfo::new("./system_info.sh").expect("couldn't build command") - } -} - -impl Decodable for SystemInfo { - fn decode(d: &mut D) -> Result { - d.read_str().and_then(|s| SystemInfo::new(&s).ok_or(d.error("bad SystemInfo command path"))) - } -} diff --git a/src/datatype/url.rs b/src/datatype/url.rs deleted file mode 100644 index 5a9c97a..0000000 --- a/src/datatype/url.rs +++ /dev/null @@ -1,106 +0,0 @@ -use hyper::method; -use rustc_serialize::{Decoder, Decodable}; -use std::borrow::Cow; -use std::fmt::{Display, Formatter, Result as FmtResult}; -use std::io; -use std::net::ToSocketAddrs; -use std::str::FromStr; -use url; -use url::SocketAddrs; - -use datatype::Error; - - -/// Encapsulate a single crate URL with additional methods and traits. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct Url(pub url::Url); - -impl Url { - /// Append the string suffix to this URL. - pub fn join(&self, suffix: &str) -> Result { - let url = try!(self.0.join(suffix)); - Ok(Url(url)) - } - - /// Return the encapsulated crate URL. - pub fn inner(&self) -> url::Url { - self.0.clone() - } -} - -impl<'a> Into> for Url { - fn into(self) -> Cow<'a, Url> { - Cow::Owned(self) - } -} - -impl FromStr for Url { - type Err = Error; - - fn from_str(s: &str) -> Result { - let url = try!(url::Url::parse(s)); - Ok(Url(url)) - } -} - -impl Decodable for Url { - fn decode(d: &mut D) -> Result { - let s = try!(d.read_str()); - s.parse().map_err(|e: Error| d.error(&e.to_string())) - } -} - -impl ToSocketAddrs for Url { - type Iter = SocketAddrs; - - fn to_socket_addrs(&self) -> io::Result { - self.0.to_socket_addrs() - } -} - -impl Display for Url { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let host = self.0.host_str().unwrap_or("localhost"); - if let Some(port) = self.0.port() { - write!(f, "{}://{}:{}{}", self.0.scheme(), host, port, self.0.path()) - } else { - write!(f, "{}://{}{}", self.0.scheme(), host, self.0.path()) - } - } -} - - -/// Enumerate the supported HTTP methods. -#[derive(Clone, Debug)] -pub enum Method { - Get, - Post, - Put, -} - -impl Into for Method { - fn into(self) -> method::Method { - match self { - Method::Get => method::Method::Get, - Method::Post => method::Method::Post, - Method::Put => method::Method::Put, - } - } -} - -impl<'a> Into> for Method { - fn into(self) -> Cow<'a, Method> { - Cow::Owned(self) - } -} - -impl Display for Method { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let method = match *self { - Method::Get => "GET".to_string(), - Method::Post => "POST".to_string(), - Method::Put => "PUT".to_string(), - }; - write!(f, "{}", method) - } -} diff --git a/src/gateway/http.rs b/src/gateway/http.rs index f397630..52a271c 100644 --- a/src/gateway/http.rs +++ b/src/gateway/http.rs @@ -4,6 +4,7 @@ use hyper::StatusCode; use hyper::net::{HttpStream, Transport}; use hyper::server::{Server as HyperServer, Request as HyperRequest}; use rustc_serialize::json; +use std::net::SocketAddr; use std::thread; use std::sync::{Arc, Mutex}; @@ -14,16 +15,16 @@ use http::{Server, ServerHandler}; /// The `Http` gateway parses `Command`s from the body of incoming requests. pub struct Http { - pub server: String, + pub server: SocketAddr } impl Gateway for Http { fn initialize(&mut self, itx: Sender) -> Result<(), String> { - let itx = Arc::new(Mutex::new(itx)); - let server = match HyperServer::http(&self.server.parse().expect("couldn't parse http address")) { - Ok(server) => server, - Err(err) => return Err(format!("couldn't start http gateway: {}", err)) - }; + let itx = Arc::new(Mutex::new(itx)); + let server = try!(HyperServer::http(&self.server).map_err(|err| { + format!("couldn't start http gateway: {}", err) + })); + thread::spawn(move || { let (_, server) = server.handle(move |_| HttpHandler::new(itx.clone())).unwrap(); server.run(); @@ -101,7 +102,7 @@ mod tests { let (etx, erx) = chan::sync::(0); let (itx, irx) = chan::sync::(0); - thread::spawn(move || Http { server: "127.0.0.1:8888".to_string() }.start(itx, erx)); + thread::spawn(move || Http { server: "127.0.0.1:8888".parse().unwrap() }.start(itx, erx)); thread::spawn(move || { let _ = etx; // move into this scope loop { diff --git a/src/http/auth_client.rs b/src/http/auth_client.rs index 7d10e5b..3121c21 100644 --- a/src/http/auth_client.rs +++ b/src/http/auth_client.rs @@ -51,7 +51,7 @@ impl AuthClient { impl Client for AuthClient { fn chan_request(&self, req: Request, resp_tx: Sender) { info!("{} {}", req.method, req.url); - let _ = self.client.request(req.url.inner(), AuthHandler { + let _ = self.client.request((*req.url).clone(), AuthHandler { auth: self.auth.clone(), req: req, timeout: Duration::from_secs(20), diff --git a/src/interpreter.rs b/src/interpreter.rs index b15c37e..d2d3f99 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -6,7 +6,8 @@ use std::time::Duration; use time; use datatype::{AccessToken, Auth, ClientCredentials, Command, Config, Error, Event, - Package, UpdateReport, UpdateRequestStatus as Status, UpdateResultCode}; + Package, UpdateReport, UpdateRequestStatus as Status, UpdateResultCode, + system_info}; use gateway::Interpret; use http::{AuthClient, Client}; use oauth2::authenticate; @@ -42,8 +43,8 @@ pub trait Interpreter { /// The `EventInterpreter` listens for `Event`s and optionally responds with /// `Command`s that may be sent to the `CommandInterpreter`. pub struct EventInterpreter { - pub pacman: PackageManager, - pub send_sysinfo: bool, + pub pacman: PackageManager, + pub sysinfo: Option, } impl Interpreter for EventInterpreter { @@ -58,9 +59,7 @@ impl Interpreter for EventInterpreter { }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err)); } - if self.send_sysinfo { - ctx.send(Command::SendSystemInfo); - } + self.sysinfo.as_ref().map(|_| ctx.send(Command::SendSystemInfo)); } Event::NotAuthenticated => { @@ -205,8 +204,8 @@ impl<'t> GlobalInterpreter<'t> { } Command::ListSystemInfo => { - let sysinfo = self.config.device.system_info.as_ref().expect("SystemInfo command not set"); - etx.send(Event::FoundSystemInfo(try!(sysinfo.report()))); + let cmd = self.config.device.system_info.as_ref().expect("system_info command not set"); + etx.send(Event::FoundSystemInfo(try!(system_info(&cmd)))); } Command::SendInstalledPackages(packages) => { @@ -222,8 +221,8 @@ impl<'t> GlobalInterpreter<'t> { } Command::SendSystemInfo => { - let sysinfo = self.config.device.system_info.as_ref().expect("SystemInfo command not set"); - try!(sota.send_system_info(&try!(sysinfo.report()))); + let cmd = self.config.device.system_info.as_ref().expect("system_info command not set"); + try!(sota.send_system_info(&try!(system_info(&cmd)))); etx.send(Event::SystemInfoSent); } diff --git a/src/main.rs b/src/main.rs index fc001b7..9821b2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,13 +14,13 @@ use chan_signal::Signal; use env_logger::LogBuilder; use getopts::Options; use log::{LogLevelFilter, LogRecord}; -use std::{env, thread}; +use std::{env, process, thread}; use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, Mutex}; use std::time::Duration; -use sota::datatype::{Command, Config, Event, SystemInfo}; +use sota::datatype::{Command, Config, Event}; use sota::gateway::{Console, DBus, Gateway, Interpret, Http, Socket, Websocket}; use sota::broadcast::Broadcast; use sota::http::{AuthClient, set_ca_certificates}; @@ -29,55 +29,44 @@ use sota::rvi::{Edge, Services}; macro_rules! exit { - ($fmt:expr, $($arg:tt)*) => {{ + ($code:expr, $fmt:expr, $($arg:tt)*) => {{ print!(concat!($fmt, "\n"), $($arg)*); - std::process::exit(1); + process::exit($code); }} } -fn start_signal_handler(signals: Receiver) { - loop { - match signals.recv() { - Some(Signal::INT) | Some(Signal::TERM) => std::process::exit(0), - _ => () - } - } -} - -fn start_update_poller(interval: u64, itx: Sender, wg: WaitGroup) { - info!("Polling for new updates every {} seconds.", interval); - let (etx, erx) = chan::async::(); - let wait = Duration::from_secs(interval); - loop { - wg.wait(); // wait until not busy - thread::sleep(wait); // then wait `interval` seconds - itx.send(Interpret { - command: Command::GetUpdateRequests, - response_tx: Some(Arc::new(Mutex::new(etx.clone()))) - }); // then request new updates - let _ = erx.recv(); // then wait for the response - } -} - fn main() { - setup_logging(); - let config = build_config(); + let version = start_logging(); + let config = build_config(&version); + set_ca_certificates(Path::new(&config.device.certificates_path)); let (etx, erx) = chan::async::(); let (ctx, crx) = chan::async::(); let (itx, irx) = chan::async::(); + let mut broadcast = Broadcast::new(erx); let wg = WaitGroup::new(); ctx.send(Command::Authenticate(None)); crossbeam::scope(|scope| { - // Must subscribe to the signal before spawning ANY other threads + // subscribe to signals first let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); scope.spawn(move || start_signal_handler(signals)); + if config.core.polling { + let poll_tick = config.core.polling_sec; + let poll_itx = itx.clone(); + let poll_wg = wg.clone(); + scope.spawn(move || start_update_poller(poll_tick, poll_itx, poll_wg)); + } + + // + // start gateways + // + if config.gateway.console { let cons_itx = itx.clone(); let cons_sub = broadcast.subscribe(); @@ -85,7 +74,7 @@ fn main() { } if config.gateway.dbus { - let dbus_cfg = config.dbus.as_ref().unwrap_or_else(|| exit!("{}", "dbus config required for dbus gateway")); + let dbus_cfg = config.dbus.as_ref().unwrap_or_else(|| exit!(1, "{}", "dbus config required for dbus gateway")); let dbus_itx = itx.clone(); let dbus_sub = broadcast.subscribe(); let mut dbus = DBus { dbus_cfg: dbus_cfg.clone(), itx: itx.clone() }; @@ -95,23 +84,19 @@ fn main() { if config.gateway.http { let http_itx = itx.clone(); let http_sub = broadcast.subscribe(); - let mut http = Http { server: config.network.http_server.clone() }; + let mut http = Http { server: *config.network.http_server }; scope.spawn(move || http.start(http_itx, http_sub)); } let rvi_services = if config.gateway.rvi { - let _ = config.dbus.as_ref().unwrap_or_else(|| exit!("{}", "dbus config required for rvi gateway")); - let rvi_cfg = config.rvi.as_ref().unwrap_or_else(|| exit!("{}", "rvi config required for rvi gateway")); + let _ = config.dbus.as_ref().unwrap_or_else(|| exit!(1, "{}", "dbus config required for rvi gateway")); + let rvi_cfg = config.rvi.as_ref().unwrap_or_else(|| exit!(1, "{}", "rvi config required for rvi gateway")); let rvi_edge = config.network.rvi_edge_server.clone(); let services = Services::new(rvi_cfg.clone(), config.device.uuid.clone(), etx.clone()); let mut edge = Edge::new(services.clone(), rvi_edge, rvi_cfg.client.clone()); scope.spawn(move || edge.start()); Some(services) } else { - let poll_tick = config.device.polling_interval; - let poll_itx = itx.clone(); - let poll_wg = wg.clone(); - scope.spawn(move || start_update_poller(poll_tick, poll_itx, poll_wg)); None }; @@ -133,14 +118,18 @@ fn main() { scope.spawn(move || ws.start(ws_itx, ws_sub)); } + // + // start interpreters + // + let event_sub = broadcast.subscribe(); let event_ctx = ctx.clone(); let event_mgr = config.device.package_manager.clone(); - let event_sys = config.device.system_info.is_some(); + let event_sys = config.device.system_info.clone(); let event_wg = wg.clone(); scope.spawn(move || EventInterpreter { - pacman: event_mgr, - send_sysinfo: event_sys, + pacman: event_mgr, + sysinfo: event_sys, }.run(event_sub, event_ctx, event_wg)); let cmd_itx = itx.clone(); @@ -158,26 +147,54 @@ fn main() { }); } -fn setup_logging() { - let version = option_env!("SOTA_VERSION").unwrap_or("?"); +fn start_logging() -> String { + let version = option_env!("SOTA_VERSION").unwrap_or("unknown"); + let mut builder = LogBuilder::new(); builder.format(move |record: &LogRecord| { let timestamp = format!("{}", time::now_utc().rfc3339()); format!("{} ({}): {} - {}", timestamp, version, record.level(), record.args()) }); builder.filter(Some("hyper"), LogLevelFilter::Info); + builder.parse(&env::var("RUST_LOG").unwrap_or("INFO".to_string())); + builder.init().expect("builder already initialized"); + + version.to_string() +} - let _ = env::var("RUST_LOG").map(|level| builder.parse(&level)); - builder.init().expect("env_logger::init() called twice, blame the programmers."); +fn start_signal_handler(signals: Receiver) { + loop { + match signals.recv() { + Some(Signal::INT) | Some(Signal::TERM) => process::exit(0), + _ => () + } + } } -fn build_config() -> Config { +fn start_update_poller(interval: u64, itx: Sender, wg: WaitGroup) { + info!("Polling for new updates every {} seconds.", interval); + let (etx, erx) = chan::async::(); + let wait = Duration::from_secs(interval); + loop { + wg.wait(); // wait until not busy + thread::sleep(wait); // then wait `interval` seconds + itx.send(Interpret { + command: Command::GetUpdateRequests, + response_tx: Some(Arc::new(Mutex::new(etx.clone()))) + }); // then request new updates + let _ = erx.recv(); // then wait for the response + } +} + +fn build_config(version: &str) -> Config { let args = env::args().collect::>(); let program = args[0].clone(); let mut opts = Options::new(); - opts.optflag("h", "help", "print this help menu"); - opts.optopt("", "config", "change config path", "PATH"); + opts.optflag("h", "help", "print this help menu then quit"); + opts.optflag("p", "print", "print the parsed config then quit"); + opts.optflag("v", "version", "print the version then quit"); + opts.optopt("c", "config", "change config path", "PATH"); opts.optopt("", "auth-server", "change the auth server", "URL"); opts.optopt("", "auth-client-id", "change the auth client id", "ID"); @@ -185,6 +202,8 @@ fn build_config() -> Config { opts.optopt("", "auth-credentials-file", "change the auth credentials file", "PATH"); opts.optopt("", "core-server", "change the core server", "URL"); + opts.optopt("", "core-polling", "toggle polling the core server for updates", "BOOL"); + opts.optopt("", "core-polling-sec", "change the core polling interval", "SECONDS"); opts.optopt("", "dbus-name", "change the dbus registration name", "NAME"); opts.optopt("", "dbus-path", "change the dbus path", "PATH"); @@ -197,7 +216,6 @@ fn build_config() -> Config { opts.optopt("", "device-vin", "change the device vin", "VIN"); opts.optopt("", "device-packages-dir", "change downloaded directory for packages", "PATH"); opts.optopt("", "device-package-manager", "change the package manager", "MANAGER"); - opts.optopt("", "device-polling-interval", "change the package polling interval", "INTERVAL"); opts.optopt("", "device-certificates-path", "change the OpenSSL CA certificates file", "PATH"); opts.optopt("", "device-system-info", "change the system information command", "PATH"); @@ -219,25 +237,37 @@ fn build_config() -> Config { opts.optopt("", "rvi-timeout", "change the rvi timeout", "TIMEOUT"); let matches = opts.parse(&args[1..]).unwrap_or_else(|err| panic!(err.to_string())); - if matches.opt_present("h") { - exit!("{}", opts.usage(&format!("Usage: {} [options]", program))); + + if matches.opt_present("help") { + exit!(0, "{}", opts.usage(&format!("Usage: {} [options]", program))); + } else if matches.opt_present("version") { + exit!(0, "{}", version); } - let config_file = matches.opt_str("config").unwrap_or_else(|| { - env::var("SOTA_CONFIG").unwrap_or_else(|_| exit!("{}", "No config file provided.")) - }); - let mut config = Config::load(&config_file).unwrap_or_else(|err| exit!("{}", err)); + let mut config = match matches.opt_str("config").or(env::var("SOTA_CONFIG").ok()) { + Some(file) => Config::load(&file).unwrap_or_else(|err| exit!(1, "{}", err)), + None => { + warn!("No config file given. Falling back to defaults."); + Config::default() + } + }; config.auth.as_mut().map(|auth_cfg| { matches.opt_str("auth-client-id").map(|id| auth_cfg.client_id = id); matches.opt_str("auth-client-secret").map(|secret| auth_cfg.client_secret = secret); matches.opt_str("auth-server").map(|text| { - auth_cfg.server = text.parse().unwrap_or_else(|err| exit!("Invalid auth-server URL: {}", err)); + auth_cfg.server = text.parse().unwrap_or_else(|err| exit!(1, "Invalid auth-server URL: {}", err)); }); }); matches.opt_str("core-server").map(|text| { - config.core.server = text.parse().unwrap_or_else(|err| exit!("Invalid core-server URL: {}", err)); + config.core.server = text.parse().unwrap_or_else(|err| exit!(1, "Invalid core-server URL: {}", err)); + }); + matches.opt_str("core-polling").map(|polling| { + config.core.polling = polling.parse().unwrap_or_else(|err| exit!(1, "Invalid core-polling boolean: {}", err)); + }); + matches.opt_str("core-polling-sec").map(|secs| { + config.core.polling_sec = secs.parse().unwrap_or_else(|err| exit!(1, "Invalid core-polling-sec: {}", err)); }); config.dbus.as_mut().map(|dbus_cfg| { @@ -247,7 +277,7 @@ fn build_config() -> Config { matches.opt_str("dbus-software-manager").map(|mgr| dbus_cfg.software_manager = mgr); matches.opt_str("dbus-software-manager-path").map(|mgr_path| dbus_cfg.software_manager_path = mgr_path); matches.opt_str("dbus-timeout").map(|timeout| { - dbus_cfg.timeout = timeout.parse().unwrap_or_else(|err| exit!("Invalid dbus timeout: {}", err)); + dbus_cfg.timeout = timeout.parse().unwrap_or_else(|err| exit!(1, "Invalid dbus-timeout: {}", err)); }); }); @@ -255,48 +285,55 @@ fn build_config() -> Config { matches.opt_str("device-vin").map(|vin| config.device.vin = vin); matches.opt_str("device-packages-dir").map(|path| config.device.packages_dir = path); matches.opt_str("device-package-manager").map(|text| { - config.device.package_manager = text.parse().unwrap_or_else(|err| exit!("Invalid device package manager: {}", err)); - }); - matches.opt_str("device-polling-interval").map(|interval| { - config.device.polling_interval = interval.parse().unwrap_or_else(|err| exit!("Invalid device polling interval: {}", err)); + config.device.package_manager = text.parse().unwrap_or_else(|err| exit!(1, "Invalid device-package-manager: {}", err)); }); matches.opt_str("device-certificates-path").map(|certs| config.device.certificates_path = certs); - matches.opt_str("device-system-info").map(|cmd| config.device.system_info = SystemInfo::new(&cmd)); + matches.opt_str("device-system-info").map(|cmd| { + config.device.system_info = if cmd.len() > 0 { Some(cmd) } else { None } + }); matches.opt_str("gateway-console").map(|console| { - config.gateway.console = console.parse().unwrap_or_else(|err| exit!("Invalid console gateway boolean: {}", err)); + config.gateway.console = console.parse().unwrap_or_else(|err| exit!(1, "Invalid gateway-console boolean: {}", err)); }); matches.opt_str("gateway-dbus").map(|dbus| { - config.gateway.dbus = dbus.parse().unwrap_or_else(|err| exit!("Invalid dbus gateway boolean: {}", err)); + config.gateway.dbus = dbus.parse().unwrap_or_else(|err| exit!(1, "Invalid gateway-dbus boolean: {}", err)); }); matches.opt_str("gateway-http").map(|http| { - config.gateway.http = http.parse().unwrap_or_else(|err| exit!("Invalid http gateway boolean: {}", err)); + config.gateway.http = http.parse().unwrap_or_else(|err| exit!(1, "Invalid gateway-http boolean: {}", err)); }); matches.opt_str("gateway-rvi").map(|rvi| { - config.gateway.rvi = rvi.parse().unwrap_or_else(|err| exit!("Invalid rvi gateway boolean: {}", err)); + config.gateway.rvi = rvi.parse().unwrap_or_else(|err| exit!(1, "Invalid gateway-rvi boolean: {}", err)); }); matches.opt_str("gateway-socket").map(|socket| { - config.gateway.socket = socket.parse().unwrap_or_else(|err| exit!("Invalid socket gateway boolean: {}", err)); + config.gateway.socket = socket.parse().unwrap_or_else(|err| exit!(1, "Invalid gateway-socket boolean: {}", err)); }); matches.opt_str("gateway-websocket").map(|websocket| { - config.gateway.websocket = websocket.parse().unwrap_or_else(|err| exit!("Invalid websocket gateway boolean: {}", err)); + config.gateway.websocket = websocket.parse().unwrap_or_else(|err| exit!(1, "Invalid gateway-websocket boolean: {}", err)); }); - matches.opt_str("network-http-server").map(|server| config.network.http_server = server); - matches.opt_str("network-rvi-edge-server").map(|server| config.network.rvi_edge_server = server); + matches.opt_str("network-http-server").map(|addr| { + config.network.http_server = addr.parse().unwrap_or_else(|err| exit!(1, "Invalid network-http-server: {}", err)); + }); + matches.opt_str("network-rvi-edge-server").map(|addr| { + config.network.rvi_edge_server = addr.parse().unwrap_or_else(|err| exit!(1, "Invalid network-rvi-edge-server: {}", err)); + }); matches.opt_str("network-socket-commands-path").map(|path| config.network.socket_commands_path = path); matches.opt_str("network-socket-events-path").map(|path| config.network.socket_events_path = path); matches.opt_str("network-websocket-server").map(|server| config.network.websocket_server = server); config.rvi.as_mut().map(|rvi_cfg| { matches.opt_str("rvi-client").map(|url| { - rvi_cfg.client = url.parse().unwrap_or_else(|err| exit!("Invalid rvi-client URL: {}", err)); + rvi_cfg.client = url.parse().unwrap_or_else(|err| exit!(1, "Invalid rvi-client URL: {}", err)); }); matches.opt_str("rvi-storage-dir").map(|dir| rvi_cfg.storage_dir = dir); matches.opt_str("rvi-timeout").map(|timeout| { - rvi_cfg.timeout = Some(timeout.parse().unwrap_or_else(|err| exit!("Invalid rvi timeout: {}", err))); + rvi_cfg.timeout = Some(timeout.parse().unwrap_or_else(|err| exit!(1, "Invalid rvi-timeout: {}", err))); }); }); + if matches.opt_present("print") { + exit!(0, "{:#?}", config); + } + config } diff --git a/src/rvi/edge.rs b/src/rvi/edge.rs index cadea74..85d46df 100644 --- a/src/rvi/edge.rs +++ b/src/rvi/edge.rs @@ -4,25 +4,24 @@ use hyper::server::{Server as HyperServer, Request as HyperRequest}; use rustc_serialize::json; use rustc_serialize::json::Json; use std::{mem, str}; -use std::net::ToSocketAddrs; -use datatype::{RpcRequest, RpcOk, RpcErr, Url}; +use datatype::{RpcRequest, RpcOk, RpcErr, SocketAddr, Url}; use http::{Server, ServerHandler}; use super::services::Services; /// The HTTP server endpoint for `RVI` client communication. pub struct Edge { - rvi_edge: Url, + rvi_edge: SocketAddr, services: Services, } impl Edge { /// Create a new `Edge` by registering each `RVI` service. - pub fn new(mut services: Services, rvi_edge: String, rvi_client: Url) -> Self { + pub fn new(mut services: Services, rvi_edge: SocketAddr, rvi_client: Url) -> Self { services.register_services(|service| { let req = RpcRequest::new("register_service", RegisterServiceRequest { - network_address: rvi_edge.clone(), + network_address: format!("http://{}", rvi_edge), service: service.to_string(), }); let resp = req.send(rvi_client.clone()) @@ -32,14 +31,12 @@ impl Edge { rpc_ok.result.expect("expected rpc_ok result").service }); - Edge { rvi_edge: rvi_edge.parse().expect("couldn't parse edge server as url"), services: services } + Edge { rvi_edge: rvi_edge, services: services } } /// Start the HTTP server listening for incoming RVI client connections. pub fn start(&mut self) { - let mut addrs = self.rvi_edge.to_socket_addrs() - .unwrap_or_else(|err| panic!("couldn't parse edge url: {}", err)); - let server = HyperServer::http(&addrs.next().expect("no SocketAddr found")) + let server = HyperServer::http(&*self.rvi_edge) .unwrap_or_else(|err| panic!("couldn't start rvi edge server: {}", err)); let (addr, server) = server.handle(move |_| EdgeHandler::new(self.services.clone())).unwrap(); info!("RVI server edge listening at http://{}.", addr); @@ -61,7 +58,6 @@ struct RegisterServiceResponse { } - struct EdgeHandler { services: Services, resp_code: StatusCode, diff --git a/tests/config_tests.rs b/tests/config_tests.rs new file mode 100644 index 0000000..21dcb4e --- /dev/null +++ b/tests/config_tests.rs @@ -0,0 +1,36 @@ +use std::env; +use std::path::Path; +use std::process::{Command, Output}; + + +fn run_client(config: &str) -> Output { + let out_dir = env::var("OUT_DIR").expect("expected OUT_DIR environment variable"); + let bin_dir = Path::new(&out_dir).parent().unwrap().parent().unwrap().parent().unwrap(); + + Command::new(format!("{}/sota_client", bin_dir.to_str().unwrap())) + .arg("--print") + .arg(format!("--config={}", config)) + .output() + .unwrap_or_else(|err| panic!("couldn't start client: {}", err)) +} + + +#[test] +fn default_config() { + assert!(run_client("tests/toml/default.toml").status.success()); +} + +#[test] +fn genivi_config() { + assert!(run_client("tests/toml/genivi.toml").status.success()); +} + +#[test] +fn old_config() { + assert!(run_client("tests/toml/old.toml").status.success()); +} + +#[test] +fn polling_config() { + assert!(run_client("tests/toml/polling.toml").status.success() != true); +} diff --git a/tests/genivi.sota.toml b/tests/genivi.sota.toml deleted file mode 100644 index 3ee4f81..0000000 --- a/tests/genivi.sota.toml +++ /dev/null @@ -1,40 +0,0 @@ -# Replace 192.168.1.40 with your IP address -# -[core] -server = "http://192.168.1.40:8080" - -[dbus] -name = "org.genivi.SotaClient" -path = "/org/genivi/SotaClient" -interface = "org.genivi.SotaClient" -software_manager = "org.genivi.SoftwareLoadingManager" -software_manager_path = "/org/genivi/SoftwareLoadingManager" -timeout = 60 - -[device] -uuid = "9ea653bc-3486-44cd-aa86-d936bd957e52" -vin = "" -packages_dir = "/tmp/" -package_manager = "off" -polling_interval = 1000 -system_info = "" -certificates_path = "run/sota_certificates" - -[gateway] -console = false -dbus = true -http = false -rvi = true -socket = false -websocket = false - -[network] -http_server = "" -rvi_edge_server = "http://192.168.1.40:9080" -socket_path = "" -websocket_server = "" - -[rvi] -client = "http://192.168.1.40:8901" -storage_dir = "/tmp" -timeout = 20 diff --git a/tests/sota.toml b/tests/sota.toml deleted file mode 100644 index 1c58c16..0000000 --- a/tests/sota.toml +++ /dev/null @@ -1,45 +0,0 @@ -[auth] -server = "http://127.0.0.1:9001" -client_id = "client-id" -client_secret = "client-secret" -credentials_file = "/tmp/sota_credentials.toml" - -[core] -server = "http://127.0.0.1:8080" - -[dbus] -name = "org.genivi.SotaClient" -path = "/org/genivi/SotaClient" -interface = "org.genivi.SotaClient" -software_manager = "org.genivi.SoftwareLoadingManager" -software_manager_path = "/org/genivi/SoftwareLoadingManager" -timeout = 60 - -[device] -uuid = "123e4567-e89b-12d3-a456-426655440000" -vin = "V1234567890123456" -packages_dir = "/tmp/" -package_manager = "deb" -system_info = "./system_info.sh" -polling_interval = 10 -certificates_path = "/tmp/sota_certificates" - -[gateway] -console = false -dbus = false -http = false -rvi = false -socket = false -websocket = true - -[network] -http_server = "http://127.0.0.1:8888" -rvi_edge_server = "http://127.0.0.1:9080" -socket_commands_path = "/tmp/sota-commands.socket" -socket_events_path = "/tmp/sota-events.socket" -websocket_server = "127.0.0.1:3012" - -[rvi] -client = "http://127.0.0.1:8901" -storage_dir = "/var/sota" -timeout = 20 diff --git a/tests/sota_client_tests.rs b/tests/sota_client_tests.rs deleted file mode 100644 index fcdbd0d..0000000 --- a/tests/sota_client_tests.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::Path; -use std::process::Command; - - -fn run_client(args: &[&str]) -> String { - let output = Command::new(format!("{}/sota_client", bin_dir())) - .args(args) - .output() - .unwrap_or_else(|err| panic!("failed to execute child: {}", err)); - String::from_utf8(output.stdout).unwrap() -} - -fn run_client_with_config(filename: &str, args: &[&str], config: &str) -> String { - let mut file = File::create(filename.to_string()).expect("couldn't create test config file"); - let _ = file.write_all(config.as_bytes()).unwrap(); - let _ = file.flush().unwrap(); - let arg = "--config=".to_string() + filename; - let mut args = args.to_vec(); - args.push(&arg); - run_client(&args) -} - -fn bin_dir() -> String { - let out_dir = env::var("OUT_DIR").unwrap(); - let bin_dir = Path::new(&out_dir) - .parent().unwrap() - .parent().unwrap() - .parent().unwrap(); - String::from(bin_dir.to_str().unwrap()) -} - - -#[test] -fn help() { - assert_eq!(run_client(&["-h"]), - format!(r#"Usage: {}/sota_client [options] - -Options: - -h, --help print this help menu - --config PATH change config path - --auth-server URL - change the auth server - --auth-client-id ID - change the auth client id - --auth-client-secret SECRET - change the auth client secret - --auth-credentials-file PATH - change the auth credentials file - --core-server URL - change the core server - --dbus-name NAME - change the dbus registration name - --dbus-path PATH - change the dbus path - --dbus-interface INTERFACE - change the dbus interface name - --dbus-software-manager NAME - change the dbus software manager name - --dbus-software-manager-path PATH - change the dbus software manager path - --dbus-timeout TIMEOUT - change the dbus installation timeout - --device-uuid UUID - change the device uuid - --device-vin VIN - change the device vin - --device-packages-dir PATH - change downloaded directory for packages - --device-package-manager MANAGER - change the package manager - --device-polling-interval INTERVAL - change the package polling interval - --device-certificates-path PATH - change the OpenSSL CA certificates file - --device-system-info PATH - change the system information command - --gateway-console BOOL - toggle the console gateway - --gateway-dbus BOOL - toggle the dbus gateway - --gateway-http BOOL - toggle the http gateway - --gateway-rvi BOOL - toggle the rvi gateway - --gateway-socket BOOL - toggle the unix domain socket gateway - --gateway-websocket BOOL - toggle the websocket gateway - --network-http-server ADDR - change the http server gateway address - --network-rvi-edge-server ADDR - change the rvi edge server gateway address - --network-socket-commands-path PATH - change the socket path for reading commands - --network-socket-events-path PATH - change the socket path for sending events - --network-websocket-server ADDR - change the websocket gateway address - --rvi-client URL - change the rvi client URL - --rvi-storage-dir PATH - change the rvi storage directory - --rvi-timeout TIMEOUT - change the rvi timeout - -"#, bin_dir())); -} - -#[test] -fn bad_ota_server_url() { - assert_eq!(run_client(&["--config", "tests/sota.toml", "--core-server", "bad-url"]), - "Invalid core-server URL: Url parse error: relative URL without a base\n") -} - -#[test] -fn bad_section() { - assert_eq!(run_client_with_config("/tmp/sota-test-config-1", &[""], "[foo]\n"), - "Parse error: invalid section: core\n") -} - -#[test] -fn bad_toml() { - assert_eq!(run_client_with_config("/tmp/sota-test-config-2", &[""], "auth]"), - "Toml parser errors: [ParserError { lo: 4, hi: 5, desc: \"expected `=`, but found `]`\" }]\n") -} - -#[test] -fn bad_path_dir() { - assert_eq!(run_client(&["--config=/"]), - "IO error: Is a directory (os error 21)\n") -} diff --git a/tests/toml/default.toml b/tests/toml/default.toml new file mode 100644 index 0000000..77a663b --- /dev/null +++ b/tests/toml/default.toml @@ -0,0 +1,46 @@ +[auth] +server = "http://127.0.0.1:9001" +client_id = "client-id" +client_secret = "client-secret" +credentials_file = "/tmp/sota_credentials.toml" + +[core] +server = "http://127.0.0.1:8080" +polling = true +polling_sec = 10 + +[dbus] +name = "org.genivi.SotaClient" +path = "/org/genivi/SotaClient" +interface = "org.genivi.SotaClient" +software_manager = "org.genivi.SoftwareLoadingManager" +software_manager_path = "/org/genivi/SoftwareLoadingManager" +timeout = 60 + +[device] +uuid = "123e4567-e89b-12d3-a456-426655440000" +vin = "V1234567890123456" +packages_dir = "/tmp/" +package_manager = "off" +certificates_path = "/tmp/sota_certificates" +system_info = "system_info.sh" + +[gateway] +console = false +dbus = false +http = false +rvi = false +socket = false +websocket = false + +[network] +http_server = "127.0.0.1:8888" +rvi_edge_server = "127.0.0.1:9080" +socket_commands_path = "/tmp/sota-commands.socket" +socket_events_path = "/tmp/sota-events.socket" +websocket_server = "127.0.0.1:3012" + +[rvi] +client = "http://127.0.0.1:8901" +storage_dir = "/var/sota" +timeout = 20 diff --git a/tests/toml/genivi.toml b/tests/toml/genivi.toml new file mode 100644 index 0000000..5c93d8a --- /dev/null +++ b/tests/toml/genivi.toml @@ -0,0 +1,30 @@ +# Replace 192.168.1.40 with your IP address +[core] +server = "http://192.168.1.40:8080" +polling = false + +[dbus] +name = "org.genivi.SotaClient" +path = "/org/genivi/SotaClient" +interface = "org.genivi.SotaClient" +software_manager = "org.genivi.SoftwareLoadingManager" +software_manager_path = "/org/genivi/SoftwareLoadingManager" +timeout = 60 + +[device] +uuid = "9ea653bc-3486-44cd-aa86-d936bd957e52" +packages_dir = "/tmp/" +package_manager = "off" +certificates_path = "run/sota_certificates" + +[gateway] +dbus = true +rvi = true + +[network] +rvi_edge_server = "192.168.1.40:9080" + +[rvi] +client = "http://192.168.1.40:8901" +storage_dir = "/tmp" +timeout = 20 diff --git a/tests/toml/old.toml b/tests/toml/old.toml new file mode 100644 index 0000000..21447cd --- /dev/null +++ b/tests/toml/old.toml @@ -0,0 +1,45 @@ +[auth] +server = "http://127.0.0.1:9001" +client_id = "client-id" +client_secret = "client-secret" +credentials_file = "/tmp/sota_credentials.toml" + +[core] +server = "http://127.0.0.1:8080" + +[dbus] +name = "org.genivi.SotaClient" +path = "/org/genivi/SotaClient" +interface = "org.genivi.SotaClient" +software_manager = "org.genivi.SoftwareLoadingManager" +software_manager_path = "/org/genivi/SoftwareLoadingManager" +timeout = 60 + +[device] +uuid = "123e4567-e89b-12d3-a456-426655440000" +vin = "V1234567890123456" +packages_dir = "/tmp/" +package_manager = "off" +certificates_path = "/tmp/sota_certificates" +system_info = "system_info.sh" +polling_interval = 10 + +[gateway] +console = false +dbus = false +http = false +rvi = false +socket = false +websocket = false + +[network] +http_server = "127.0.0.1:8888" +rvi_edge_server = "127.0.0.1:9080" +socket_commands_path = "/tmp/sota-commands.socket" +socket_events_path = "/tmp/sota-events.socket" +websocket_server = "127.0.0.1:3012" + +[rvi] +client = "http://127.0.0.1:8901" +storage_dir = "/var/sota" +timeout = 20 diff --git a/tests/toml/polling.toml b/tests/toml/polling.toml new file mode 100644 index 0000000..a0e29d6 --- /dev/null +++ b/tests/toml/polling.toml @@ -0,0 +1,5 @@ +[core] +polling_sec = 1 + +[device] +polling_interval = 2 -- cgit v1.2.1