From dbe8b5900f1929537309067f02f0996e7691c764 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Thu, 1 Sep 2016 18:58:31 +0200 Subject: Add quickstart instructions for RVI/DBUS in README --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/genivi.sota.toml | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 tests/genivi.sota.toml diff --git a/README.md b/README.md index 3750675..31cb67c 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,51 @@ For example, running `CONFIG_ONLY=true make run` will output a newly generated ` 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. + +## Testing on GENIVI Development Platform over RVI + +### Starting the SOTA Server + +See the full documentation at [rvi_sota_server](http://advancedtelematic.github.io/rvi_sota_server/). + +Here is a quickstart: + + 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 + +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`: + +``` + environment: + RVI_BACKEND: "rvi_backend" + DEVICE_ID: "9ea653bc-3486-44cd-aa86-d936bd957e52" +``` + +Restart the RVI device node with the new DEVICE_ID by re-running: + + 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"). + +The `rvi` and `dbus` fields in the `[gateway]` section must be `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`: + + make client + ./run/sota_client --config tests/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. + +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. + diff --git a/tests/genivi.sota.toml b/tests/genivi.sota.toml new file mode 100644 index 0000000..3ee4f81 --- /dev/null +++ b/tests/genivi.sota.toml @@ -0,0 +1,40 @@ +# 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 -- cgit v1.2.1 From bef4ae17ae22f413eab221355793cf2caff2a45d Mon Sep 17 00:00:00 2001 From: Jon Oster Date: Fri, 2 Sep 2016 16:22:35 +0200 Subject: PRO-1185 add SendInstalledPackages to client integration docs --- docs/client-guide.adoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/client-guide.adoc b/docs/client-guide.adoc index 5b1ea91..4ec3103 100644 --- a/docs/client-guide.adoc +++ b/docs/client-guide.adoc @@ -149,6 +149,7 @@ Currently, only the core functionality of making software updates available and - It must listen for `DownloadComplete` events on the events socket. At the moment, the only events published on the events socket are DownloadComplete events, but in future other types of events may be published. The SWLM should be capable of filtering for only the type of events it is interested in. - It must send a `SendUpdateReport` command on the command socket, with a status code, when the update finishes. +- It must send a `SendInstalledPackages` command on the command socket, listing the names and versions of installed packages, after a successful package install. ==== Configuration @@ -206,3 +207,23 @@ TIP: Over D-Bus, it is also possible to sent a longer textual description of the |19 | GENERAL_ERROR | Other error |=== +==== SendInstalledPackages + +This command is used to notify the OTA client of what packages are installed on the system. It _must_ be sent (to the command socket) after each `SendUpdateReport`, and also _may_ be sent at any other time. ATS recommends sending it on system startup, at a minimum. + +The command syntax is simply this: + +---- +SendInstalledPackages package1_name package1_version package2_name package2_version [...] packageN_name packageN_version +---- + +Package names and versions can't contain spaces, but there are no other character restrictions. For example, all of the package/versions listed here are valid: + +---- +SendInstalledPackages gcc 7.63 Movie&MusicPlayer rc2-beta3 ECU9274927BF82-firmware gitID-2fab572 +---- + +Note, however, that all packages must have a version. + + + -- cgit v1.2.1 From 7dd15fb89b405dc7a88c3439acaf10c5e9c4180d Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 5 Sep 2016 11:28:22 +0200 Subject: Send packages and system info after successful authentication --- Makefile | 8 +------- run/sota.toml.env | 2 +- src/datatype/command.rs | 46 ++++++++++++++++++++++++++++------------------ src/datatype/config.rs | 4 ++-- src/datatype/event.rs | 13 +++++++++---- src/http/auth_client.rs | 2 +- src/interpreter.rs | 43 +++++++++++++++++++++++++++---------------- src/main.rs | 15 +-------------- src/rvi/parameters.rs | 1 - tests/sota.toml | 2 +- 10 files changed, 71 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index cd07a78..9750616 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ define make-pkg endef -.PHONY: help run clean test doc client image deb rpm version for-meta-rust +.PHONY: help run clean test doc client image deb rpm version .DEFAULT_GOAL := help help: @@ -83,12 +83,6 @@ rpm: client ## Create a new RPM package of the client. version: ## Print the version that will be used for building packages. @echo $(PACKAGE_VERSION) -for-meta-rust: - $(RUST_IN_DOCKER) /bin/bash -c "\ - /root/.cargo/bin/rustup override set 1.7.0 && \ - cargo clean && \ - cargo test" - rust-openssl: @git clone https://github.com/sfackler/rust-openssl $@ @cd $@ && git checkout df30e9e700225fb981d8a3cdfaf0b359722a4c9a diff --git a/run/sota.toml.env b/run/sota.toml.env index 595f905..1ef5619 100644 --- a/run/sota.toml.env +++ b/run/sota.toml.env @@ -27,7 +27,7 @@ NETWORK_HTTP_SERVER=http://127.0.0.1:8888 NETWORK_RVI_EDGE_SERVER=http://127.0.0.1:9080 NETWORK_SOCKET_COMMANDS_PATH=/tmp/sota-commands.socket NETWORK_SOCKET_EVENTS_PATH=/tmp/sota-events.socket -NETWORK_WEBSOCKET_SERVER=ws://127.0.0.1:3012 +NETWORK_WEBSOCKET_SERVER=127.0.0.1:3012 RVI_CLIENT=http://127.0.0.1:8901 RVI_STORAGE_DIR=/var/sota diff --git a/src/datatype/command.rs b/src/datatype/command.rs index d449bb6..0f59b1a 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -20,8 +20,8 @@ pub enum Command { GetNewUpdates, /// List the installed packages on the system. ListInstalledPackages, - /// Get the latest system information, and optionally publish it to Core. - RefreshSystemInfo(bool), + /// List the system information. + ListSystemInfo, /// Start downloading one or more updates. StartDownload(Vec), @@ -32,6 +32,8 @@ pub enum Command { SendInstalledPackages(Vec), /// Send a list of all packages and firmware to the Core server. SendInstalledSoftware(InstalledSoftware), + /// Send the system information to the Core server. + SendSystemInfo, /// Send a package update report to the Core server. SendUpdateReport(UpdateReport), } @@ -63,14 +65,16 @@ named!(command <(Command, Vec<&str>)>, chain!( => { |_| Command::GetNewUpdates } | alt_complete!(tag!("ListInstalledPackages") | tag!("ls")) => { |_| Command::ListInstalledPackages } - | alt_complete!(tag!("RefreshSystemInfo") | tag!("info")) - => { |_| Command::RefreshSystemInfo(false) } + | alt_complete!(tag!("ListSystemInfo") | tag!("info")) + => { |_| Command::ListSystemInfo } | alt_complete!(tag!("Shutdown") | tag!("shutdown")) => { |_| Command::Shutdown } | alt_complete!(tag!("SendInstalledPackages") | tag!("sendpack")) => { |_| Command::SendInstalledPackages(Vec::new()) } | alt_complete!(tag!("SendInstalledSoftware") | tag!("sendinst")) => { |_| Command::SendInstalledSoftware(InstalledSoftware::default()) } + | alt_complete!(tag!("SendSystemInfo") | tag!("sendinfo")) + => { |_| Command::SendSystemInfo } | alt_complete!(tag!("SendUpdateReport") | tag!("sendup")) => { |_| Command::SendUpdateReport(UpdateReport::default()) } | alt_complete!(tag!("StartDownload") | tag!("dl")) @@ -118,16 +122,9 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { _ => Err(Error::Command(format!("unexpected ListInstalledPackages args: {:?}", args))), }, - Command::RefreshSystemInfo(_) => match args.len() { - 0 => Ok(Command::RefreshSystemInfo(false)), - 1 => { - if let Ok(b) = args[0].parse::() { - Ok(Command::RefreshSystemInfo(b)) - } else { - Err(Error::Command("couldn't parse 1st argument as boolean".to_string())) - } - } - _ => Err(Error::Command(format!("unexpected RefreshSystemInfo args: {:?}", args))), + Command::ListSystemInfo => match args.len() { + 0 => Ok(Command::ListSystemInfo), + _ => Err(Error::Command(format!("unexpected ListSystemInfo args: {:?}", args))), }, Command::SendInstalledPackages(_) => match args.len() { @@ -150,6 +147,11 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { _ => Err(Error::Command(format!("unexpected SendInstalledSoftware args: {:?}", args))), }, + Command::SendSystemInfo => match args.len() { + 0 => Ok(Command::SendSystemInfo), + _ => Err(Error::Command(format!("unexpected SendSystemInfo args: {:?}", args))), + }, + Command::SendUpdateReport(_) => match args.len() { 0 | 1 => Err(Error::Command("usage: sendup ".to_string())), 2 => { @@ -238,10 +240,10 @@ mod tests { } #[test] - fn refresh_system_info_test() { - assert_eq!("RefreshSystemInfo true".parse::().unwrap(), Command::RefreshSystemInfo(true)); - assert_eq!("info".parse::().unwrap(), Command::RefreshSystemInfo(false)); - assert!("RefreshSystemInfo 1 2".parse::().is_err()); + fn list_system_info_test() { + assert_eq!("ListSystemInfo".parse::().unwrap(), Command::ListSystemInfo); + assert_eq!("info".parse::().unwrap(), Command::ListSystemInfo); + assert!("ListSystemInfo 1 2".parse::().is_err()); assert!("info please".parse::().is_err()); } @@ -270,6 +272,14 @@ mod tests { assert!("sendsoft some".parse::().is_err()); } + #[test] + fn send_system_info_test() { + assert_eq!("SendSystemInfo".parse::().unwrap(), Command::SendSystemInfo); + assert_eq!("sendinfo".parse::().unwrap(), Command::SendSystemInfo); + assert!("SendSystemInfo 1 2".parse::().is_err()); + assert!("sendinfo please".parse::().is_err()); + } + #[test] fn send_update_report_test() { assert_eq!("SendUpdateReport myid OK".parse::().unwrap(), Command::SendUpdateReport( diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 5db0f12..650008b 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -266,7 +266,7 @@ impl Default for NetworkConfig { rvi_edge_server: "http://127.0.0.1:9080".to_string(), socket_commands_path: "/tmp/sota-commands.socket".to_string(), socket_events_path: "/tmp/sota-events.socket".to_string(), - websocket_server: "ws://127.0.0.1:3012".to_string() + websocket_server: "127.0.0.1:3012".to_string() } } } @@ -352,7 +352,7 @@ mod tests { 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 = "ws://127.0.0.1:3012" + websocket_server = "127.0.0.1:3012" "#; const RVI_CONFIG: &'static str = diff --git a/src/datatype/event.rs b/src/datatype/event.rs index e3f84ca..63ae850 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -26,10 +26,6 @@ pub enum Event { FoundInstalledPackages(Vec), /// An update on the system information was received. FoundSystemInfo(String), - /// A list of installed packages was sent to the Core server. - InstalledPackagesSent, - /// An update report was sent to the Core server. - UpdateReportSent, /// Downloading an update. DownloadingUpdate(UpdateRequestId), @@ -45,6 +41,15 @@ pub enum Event { /// The installation of an update failed. InstallFailed(UpdateReport), + /// An update report was sent to the Core server. + UpdateReportSent, + /// A list of installed packages was sent to the Core server. + InstalledPackagesSent, + /// A list of installed software was sent to the Core server. + InstalledSoftwareSent, + /// The system information was sent to the Core server. + SystemInfoSent, + /// A broadcast event requesting an update on externally installed software. InstalledSoftwareNeeded, } diff --git a/src/http/auth_client.rs b/src/http/auth_client.rs index f4ad38b..f97f4d7 100644 --- a/src/http/auth_client.rs +++ b/src/http/auth_client.rs @@ -88,7 +88,7 @@ impl AuthHandler { match resp.headers().get::() { Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| { debug!("redirecting to {}", url); - // drop Authentication Header on redirect + // drop Authorization Header on redirect let client = AuthClient::default(); let resp_rx = client.send_request(Request { url: url, diff --git a/src/interpreter.rs b/src/interpreter.rs index b286ba5..7d95007 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -36,6 +36,16 @@ impl Interpreter for EventInterpreter { fn interpret(&mut self, event: Event, ctx: &Sender) { info!("Event received: {}", event); match event { + Event::Authenticated => { + ctx.send(Command::SendSystemInfo); + + if self.package_manager != PackageManager::Off { + self.package_manager.installed_packages().map(|packages| { + ctx.send(Command::SendInstalledPackages(packages)); + }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err)); + } + } + Event::NotAuthenticated => { info!("Trying to authenticate again..."); ctx.send(Command::Authenticate(None)); @@ -88,8 +98,7 @@ pub struct GlobalInterpreter<'t> { pub config: Config, pub token: Option>, pub http_client: Box, - pub rvi: Option, - pub loopback_tx: Sender, + pub rvi: Option } impl<'t> Interpreter for GlobalInterpreter<'t> { @@ -159,13 +168,9 @@ impl<'t> GlobalInterpreter<'t> { etx.send(Event::FoundInstalledPackages(packages)); } - Command::RefreshSystemInfo(post) => { + Command::ListSystemInfo => { let info = try!(self.config.device.system_info.report()); - etx.send(Event::FoundSystemInfo(info.clone())); - if post { - let _ = sota.send_system_info(&info) - .map_err(|err| etx.send(Event::Error(format!("{}", err)))); - } + etx.send(Event::FoundSystemInfo(info)); } Command::SendInstalledPackages(packages) => { @@ -178,6 +183,14 @@ impl<'t> GlobalInterpreter<'t> { if let Some(ref rvi) = self.rvi { let _ = rvi.remote.lock().unwrap().send_installed_software(sw); } + etx.send(Event::InstalledSoftwareSent); + } + + Command::SendSystemInfo => { + let info = try!(self.config.device.system_info.report()); + let _ = sota.send_system_info(&info) + .map_err(|err| error!("couldn't send system info: {}", err)); + etx.send(Event::SystemInfoSent); } Command::SendUpdateReport(report) => { @@ -193,7 +206,6 @@ impl<'t> GlobalInterpreter<'t> { Command::StartDownload(ref ids) => { for id in ids { etx.send(Event::DownloadingUpdate(id.clone())); - if let Some(ref rvi) = self.rvi { let _ = rvi.remote.lock().unwrap().send_download_started(id.clone()); } else { @@ -231,10 +243,11 @@ impl<'t> GlobalInterpreter<'t> { Command::GetNewUpdates | Command::ListInstalledPackages | - Command::RefreshSystemInfo(_) | + Command::ListSystemInfo | Command::SendInstalledPackages(_) | Command::SendInstalledSoftware(_) | Command::SendUpdateReport(_) | + Command::SendSystemInfo | Command::StartDownload(_) | Command::StartInstall(_) => etx.send(Event::NotAuthenticated), @@ -270,15 +283,13 @@ mod tests { fn new_interpreter(replies: Vec, pkg_mgr: PackageManager) -> (Sender, Receiver) { let (etx, erx) = chan::sync::(0); let (ctx, crx) = chan::sync::(0); - let (itx, _) = chan::sync::(0); thread::spawn(move || { let mut gi = GlobalInterpreter { config: Config::default(), token: Some(AccessToken::default().into()), http_client: Box::new(TestClient::from(replies)), - rvi: None, - loopback_tx: itx, + rvi: None }; gi.config.device.package_manager = pkg_mgr; @@ -327,7 +338,7 @@ mod tests { } #[test] - fn install_update() { + fn install_update_success() { let replies = vec!["[]".to_string(); 10]; let pkg_mgr = PackageManager::new_tpm(true); let (ctx, erx) = new_interpreter(replies, pkg_mgr); @@ -345,7 +356,7 @@ mod tests { } #[test] - fn failed_installation() { + fn install_update_failed() { let replies = vec!["[]".to_string(); 10]; let pkg_mgr = PackageManager::new_tpm(false); let (ctx, erx) = new_interpreter(replies, pkg_mgr); @@ -358,7 +369,7 @@ mod tests { assert_rx(erx, &[ Event::InstallFailed( UpdateReport::single("1".to_string(), UpdateResultCode::INSTALL_FAILED, "failed".to_string()) - ) + ), ]); } } diff --git a/src/main.rs b/src/main.rs index 61fa02a..3fd5667 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,6 @@ use sota::gateway::{Console, DBus, Gateway, Interpret, Http, Socket, Websocket}; use sota::broadcast::Broadcast; use sota::http::{AuthClient, set_ca_certificates}; use sota::interpreter::{EventInterpreter, CommandInterpreter, Interpreter, GlobalInterpreter}; -use sota::package_manager::PackageManager; use sota::rvi::{Edge, Services}; @@ -59,17 +58,6 @@ fn start_update_poller(interval: u64, itx: Sender) { } } -fn send_startup_commands(config: &Config, ctx: &Sender) { - ctx.send(Command::Authenticate(None)); - ctx.send(Command::RefreshSystemInfo(true)); - - if config.device.package_manager != PackageManager::Off { - config.device.package_manager.installed_packages().map(|packages| { - ctx.send(Command::SendInstalledPackages(packages)); - }).unwrap_or_else(|err| exit!("Couldn't get list of packages: {}", err)); - } -} - fn main() { setup_logging(); @@ -81,7 +69,7 @@ fn main() { let (itx, irx) = chan::async::(); let mut broadcast = Broadcast::new(erx); - send_startup_commands(&config, &ctx); + ctx.send(Command::Authenticate(None)); crossbeam::scope(|scope| { // Must subscribe to the signal before spawning ANY other threads @@ -157,7 +145,6 @@ fn main() { token: None, http_client: Box::new(AuthClient::default()), rvi: rvi, - loopback_tx: itx, }.run(irx, etx)); scope.spawn(move || broadcast.start()); diff --git a/src/rvi/parameters.rs b/src/rvi/parameters.rs index 1fa1a87..f48ea44 100644 --- a/src/rvi/parameters.rs +++ b/src/rvi/parameters.rs @@ -1,4 +1,3 @@ -use std::str; use std::sync::Mutex; use datatype::{ChunkReceived, Event, DownloadComplete, UpdateRequestId, UpdateAvailable}; diff --git a/tests/sota.toml b/tests/sota.toml index 0801509..87cf144 100644 --- a/tests/sota.toml +++ b/tests/sota.toml @@ -37,7 +37,7 @@ 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 = "ws://127.0.0.1:3012" +websocket_server = "127.0.0.1:3012" [rvi] client = "http://127.0.0.1:8901" -- cgit v1.2.1 From 2a9797aaa9b69249097fcfd9940a8e68043eaae6 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 5 Sep 2016 17:06:36 +0200 Subject: Send InstallingUpdate Event before an install --- src/interpreter.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 7d95007..3495def 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -217,6 +217,7 @@ impl<'t> GlobalInterpreter<'t> { } Command::StartInstall(dl) => { + etx.send(Event::InstallingUpdate(dl.update_id.clone())); let _ = sota.install_update(dl) .map(|report| etx.send(Event::InstallComplete(report))) .map_err(|report| etx.send(Event::InstallFailed(report))); @@ -349,6 +350,7 @@ mod tests { signature: "".to_string() })); assert_rx(erx, &[ + Event::InstallingUpdate("1".to_string()), Event::InstallComplete( UpdateReport::single("1".to_string(), UpdateResultCode::OK, "".to_string()) ) @@ -367,9 +369,10 @@ mod tests { signature: "".to_string() })); assert_rx(erx, &[ + Event::InstallingUpdate("1".to_string()), Event::InstallFailed( UpdateReport::single("1".to_string(), UpdateResultCode::INSTALL_FAILED, "failed".to_string()) - ), + ) ]); } } -- cgit v1.2.1 From 2ddb6762cd20ae0c16e040c95a961d3a7d0a6e62 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Tue, 6 Sep 2016 14:55:57 +0200 Subject: Wrap socket JSON output with Event name --- docs/client-guide.adoc | 21 ++++++++++++--------- src/gateway/socket.rs | 23 +++++++++++++++++++---- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/docs/client-guide.adoc b/docs/client-guide.adoc index 4ec3103..bda8174 100644 --- a/docs/client-guide.adoc +++ b/docs/client-guide.adoc @@ -162,14 +162,20 @@ Once the SOTA client has successfully downloaded an update ordered by an ATS Gar [source,json] ---- { - "update_id": "string", <1> - "update_image": "string", <2> - "signature": "string" <3> + "version": "0.1", <1> + "event": "DownloadComplete", <2> + "data": { + "update_id": "string", <3> + "update_image": "string", <4> + "signature": "string" <5> + } } ---- -<1> A unique ID for the update. The SWLM will need to reference this ID when reporting on the status of the install. -<2> The location of the delivered update file. -<3> A cryptographic signature; may be blank if the package uploader chose not to supply one. The SWLM *may* implement signature verification, but is not required to do so. +<1> The API version of the response. +<2> The Event type of the message. +<3> A unique ID for the update. The SWLM will need to reference this ID when reporting on the status of the install. +<4> The location of the delivered update file. +<5> A cryptographic signature; may be blank if the package uploader chose not to supply one. The SWLM *may* implement signature verification, but is not required to do so. ==== SendUpdateReport @@ -224,6 +230,3 @@ SendInstalledPackages gcc 7.63 Movie&MusicPlayer rc2-beta3 ECU9274927BF82-firmwa ---- Note, however, that all packages must have a version. - - - diff --git a/src/gateway/socket.rs b/src/gateway/socket.rs index f252e7c..295cf6e 100644 --- a/src/gateway/socket.rs +++ b/src/gateway/socket.rs @@ -6,7 +6,7 @@ use std::net::Shutdown; use std::sync::{Arc, Mutex}; use std::{fs, thread}; -use datatype::{Command, Error, Event}; +use datatype::{Command, DownloadComplete, Error, Event}; use super::{Gateway, Interpret}; use unix_socket::{UnixListener, UnixStream}; @@ -56,7 +56,12 @@ impl Gateway for Socket { match event { Event::DownloadComplete(dl) => { let _ = UnixStream::connect(&self.events_path).map(|mut stream| { - stream.write_all(&json::encode(&dl).expect("couldn't encode Event").into_bytes()) + let output = DownloadCompleteEvent { + version: "0.1".to_string(), + event: "DownloadComplete".to_string(), + data: dl + }; + stream.write_all(&json::encode(&output).expect("couldn't encode Event").into_bytes()) .unwrap_or_else(|err| error!("couldn't write to events socket: {}", err)); stream.shutdown(Shutdown::Write) .unwrap_or_else(|err| error!("couldn't close events socket: {}", err)); @@ -84,6 +89,14 @@ fn handle_client(stream: &mut UnixStream, itx: Arc>>) -> erx.recv().ok_or(Error::Socket("internal receiver error".to_string())) } +// FIXME(PRO-1322): create a proper JSON api +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub struct DownloadCompleteEvent { + pub version: String, + pub event: String, + pub data: DownloadComplete +} + #[cfg(test)] mod tests { @@ -126,8 +139,10 @@ mod tests { let (mut stream, _) = server.accept().expect("couldn't read from events socket"); let mut text = String::new(); stream.read_to_string(&mut text).unwrap(); - let receive: DownloadComplete = json::decode(&text).expect("couldn't decode DownloadComplete message"); - assert_eq!(send, receive); + let receive: DownloadCompleteEvent = json::decode(&text).expect("couldn't decode DownloadComplete message"); + assert_eq!(receive.version, "0.1".to_string()); + assert_eq!(receive.event, "DownloadComplete".to_string()); + assert_eq!(receive.data, send); thread::spawn(move || { let _ = etx; // move into this scope -- cgit v1.2.1 From e873ec294be40781543f9b07ee09f32212ea586a Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 7 Sep 2016 16:09:33 +0200 Subject: Check for in-flight updates --- Cargo.toml | 6 +++ Makefile | 4 +- src/datatype/command.rs | 81 +++++++++++++++++++++++++--------------- src/datatype/event.rs | 21 +++++++---- src/gateway/dbus.rs | 4 +- src/gateway/http.rs | 6 +-- src/gateway/socket.rs | 4 +- src/gateway/websocket.rs | 4 +- src/interpreter.rs | 92 ++++++++++++++++++++++++++-------------------- src/main.rs | 2 +- src/package_manager/deb.rs | 2 +- src/rvi/parameters.rs | 2 +- src/sota.rs | 82 ++++++++++++++++++++--------------------- 13 files changed, 178 insertions(+), 132 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cbc8b2a..179e4fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,9 @@ toml = "0.1.30" unix_socket = "0.5.0" url = "1.1.1" ws = "0.5.0" + +[profile.dev] +panic = 'abort' + +[profile.release] +panic = 'abort' diff --git a/Makefile b/Makefile index 9750616..8cdbfa4 100644 --- a/Makefile +++ b/Makefile @@ -68,8 +68,8 @@ clippy: ## Run clippy lint checks using the nightly compiler. rustup run nightly cargo clippy -- -Dclippy client: rust-openssl src/ ## Compile a new release build of the client. - $(CARGO) build --release - @cp target/release/sota_client run/ + $(CARGO) build --release --target=x86_64-unknown-linux-gnu + @cp target/x86_64-unknown-linux-gnu/release/sota_client run/ image: client ## Build a Docker image for running the client. @docker build --tag advancedtelematic/sota-client run diff --git a/src/datatype/command.rs b/src/datatype/command.rs index 0f59b1a..11707ea 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -3,9 +3,8 @@ use std::str; use std::str::FromStr; use nom::{IResult, space, eof}; -use datatype::{ClientCredentials, ClientId, ClientSecret, DownloadComplete, Error, - InstalledSoftware, Package, UpdateReport, UpdateRequestId, - UpdateResultCode}; +use datatype::{ClientCredentials, ClientId, ClientSecret, Error, InstalledSoftware, + Package, UpdateReport, UpdateRequestId, UpdateResultCode}; /// System-wide commands that are sent to the interpreter. @@ -16,17 +15,20 @@ pub enum Command { /// Shutdown the client immediately. Shutdown, - /// Check for any new updates. - GetNewUpdates, + /// Check for any pending updates. + GetPendingUpdates, + /// Check for any in-flight updates. + GetInFlightUpdates, + /// List the installed packages on the system. ListInstalledPackages, /// List the system information. ListSystemInfo, - /// Start downloading one or more updates. - StartDownload(Vec), - /// Start installing an update - StartInstall(DownloadComplete), + /// Start downloading an update. + StartDownload(UpdateRequestId), + /// Start installing an update. + StartInstall(UpdateRequestId), /// Send a list of packages to the Core server. SendInstalledPackages(Vec), @@ -40,7 +42,11 @@ pub enum Command { impl Display for Command { fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{:?}", self) + let text = match *self { + Command::SendInstalledPackages(_) => "SendInstalledPackages(...)".to_string(), + _ => format!("{:?}", self) + }; + write!(f, "{}", text) } } @@ -61,8 +67,10 @@ named!(command <(Command, Vec<&str>)>, chain!( ~ cmd: alt!( alt_complete!(tag!("Authenticate") | tag!("auth")) => { |_| Command::Authenticate(None) } - | alt_complete!(tag!("GetNewUpdates") | tag!("new")) - => { |_| Command::GetNewUpdates } + | alt_complete!(tag!("GetPendingUpdates") | tag!("getpend")) + => { |_| Command::GetPendingUpdates } + | alt_complete!(tag!("GetInFlightUpdates") | tag!("getflight")) + => { |_| Command::GetInFlightUpdates } | alt_complete!(tag!("ListInstalledPackages") | tag!("ls")) => { |_| Command::ListInstalledPackages } | alt_complete!(tag!("ListSystemInfo") | tag!("info")) @@ -78,9 +86,9 @@ named!(command <(Command, Vec<&str>)>, chain!( | alt_complete!(tag!("SendUpdateReport") | tag!("sendup")) => { |_| Command::SendUpdateReport(UpdateReport::default()) } | alt_complete!(tag!("StartDownload") | tag!("dl")) - => { |_| Command::StartDownload(Vec::new()) } + => { |_| Command::StartDownload("".to_string()) } | alt_complete!(tag!("StartInstall") | tag!("inst")) - => { |_| Command::StartInstall(DownloadComplete::default()) } + => { |_| Command::StartInstall("".to_string()) } ) ~ args: arguments ~ alt!(eof | tag!("\r") | tag!("\n") | tag!(";")), @@ -112,9 +120,14 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { _ => Err(Error::Command(format!("unexpected Authenticate args: {:?}", args))), }, - Command::GetNewUpdates => match args.len() { - 0 => Ok(Command::GetNewUpdates), - _ => Err(Error::Command(format!("unexpected GetNewUpdates args: {:?}", args))), + Command::GetPendingUpdates => match args.len() { + 0 => Ok(Command::GetPendingUpdates), + _ => Err(Error::Command(format!("unexpected GetPendingUpdates args: {:?}", args))), + }, + + Command::GetInFlightUpdates => match args.len() { + 0 => Ok(Command::GetInFlightUpdates), + _ => Err(Error::Command(format!("unexpected GetInFlightUpdates args: {:?}", args))), }, Command::ListInstalledPackages => match args.len() { @@ -170,12 +183,14 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { }, Command::StartDownload(_) => match args.len() { - 0 => Err(Error::Command("usage: dl []".to_string())), - _ => Ok(Command::StartDownload(args.iter().map(|arg| String::from(*arg)).collect())), + 0 => Err(Error::Command("usage: dl ".to_string())), + 1 => Ok(Command::StartDownload(args[0].to_string())), + _ => Err(Error::Command(format!("unexpected StartInstall args: {:?}", args))), }, Command::StartInstall(_) => match args.len() { - // FIXME(PRO-1160): args + 0 => Err(Error::Command("usage: inst ".to_string())), + 1 => Ok(Command::StartInstall(args[0].to_string())), _ => Err(Error::Command(format!("unexpected StartInstall args: {:?}", args))), }, @@ -196,7 +211,7 @@ mod tests { assert_eq!(command(&b"auth foo bar"[..]), IResult::Done(&b""[..], (Command::Authenticate(None), vec!["foo", "bar"]))); assert_eq!(command(&b"dl 1"[..]), - IResult::Done(&b""[..], (Command::StartDownload(Vec::new()), vec!["1"]))); + IResult::Done(&b""[..], (Command::StartDownload("".to_string()), vec!["1"]))); assert_eq!(command(&b"ls;\n"[..]), IResult::Done(&b"\n"[..], (Command::ListInstalledPackages, Vec::new()))); } @@ -226,10 +241,17 @@ mod tests { } #[test] - fn get_new_updates_test() { - assert_eq!("GetNewUpdates".parse::().unwrap(), Command::GetNewUpdates); - assert_eq!("new".parse::().unwrap(), Command::GetNewUpdates); - assert!("new old".parse::().is_err()); + fn get_pending_updates_test() { + assert_eq!("GetPendingUpdates".parse::().unwrap(), Command::GetPendingUpdates); + assert_eq!("getpend".parse::().unwrap(), Command::GetPendingUpdates); + assert!("getpend old".parse::().is_err()); + } + + #[test] + fn get_in_flight_updates_test() { + assert_eq!("GetInFlightUpdates".parse::().unwrap(), Command::GetInFlightUpdates); + assert_eq!("getflight".parse::().unwrap(), Command::GetInFlightUpdates); + assert!("getflight old".parse::().is_err()); } #[test] @@ -301,15 +323,16 @@ mod tests { #[test] fn start_download_test() { - assert_eq!("StartDownload this".parse::().unwrap(), - Command::StartDownload(vec!["this".to_string()])); - assert_eq!("dl some more".parse::().unwrap(), - Command::StartDownload(vec!["some".to_string(), "more".to_string()])); + assert_eq!("StartDownload this".parse::().unwrap(), Command::StartDownload("this".to_string())); + assert_eq!("dl that".parse::().unwrap(), Command::StartDownload("that".to_string())); + assert!("StartDownload this and that".parse::().is_err()); assert!("dl".parse::().is_err()); } #[test] fn start_install_test() { + assert_eq!("StartInstall 123".parse::().unwrap(), Command::StartInstall("123".to_string())); + assert_eq!("inst this".parse::().unwrap(), Command::StartInstall("this".to_string())); assert!("StartInstall".parse::().is_err()); assert!("inst more than one".parse::().is_err()); } diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 63ae850..6a493f0 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; -use datatype::{DownloadComplete, Package, UpdateAvailable, UpdateReport, - UpdateRequestId}; +use datatype::{DownloadComplete, Package, PendingUpdateRequest, UpdateAvailable, + UpdateReport, UpdateRequestId}; /// System-wide events that are broadcast to all interested parties. @@ -15,12 +15,17 @@ pub enum Event { /// An operation failed because we are not currently authenticated. NotAuthenticated, - /// There are new updates available. - NewUpdatesReceived(Vec), - /// A notification from RVI of a new update. - NewUpdateAvailable(UpdateAvailable), - /// There are no new updates available. - NoNewUpdates, + /// There are pending updates available. + PendingUpdatesReceived(Vec), + /// A notification from RVI of a pending update. + PendingUpdateAvailable(UpdateAvailable), + /// There are no pending updates. + NoPendingUpdates, + + /// There are in-flight updates available. + InFlightUpdatesReceived(Vec), + /// There are no in-flight updates. + NoInFlightUpdates, /// The following packages are installed on the device. FoundInstalledPackages(Vec), diff --git a/src/gateway/dbus.rs b/src/gateway/dbus.rs index 3bd41ea..ea807f6 100644 --- a/src/gateway/dbus.rs +++ b/src/gateway/dbus.rs @@ -47,7 +47,7 @@ impl Gateway for DBus { fn pulse(&self, event: Event) { match event { - Event::NewUpdateAvailable(avail) => { + Event::PendingUpdateAvailable(avail) => { let msg = self.new_message("updateAvailable", &[ MessageItem::from(avail.update_id), MessageItem::from(avail.signature), @@ -144,7 +144,7 @@ fn handle_initiate_download(itx: &Sender, msg: &mut Message) -> Metho let mut args = msg.get_items().into_iter(); let arg_id = try!(args.next().ok_or(dbus::missing_arg())); let update_id: &String = try!(FromMessageItem::from(&arg_id).or(Err(dbus::malformed_arg()))); - send(itx, Command::StartDownload(vec![update_id.clone()])); + send(itx, Command::StartDownload(update_id.clone())); Ok(vec![]) } diff --git a/src/gateway/http.rs b/src/gateway/http.rs index 990a1fc..6ccc2b5 100644 --- a/src/gateway/http.rs +++ b/src/gateway/http.rs @@ -107,9 +107,9 @@ mod tests { loop { let interpret = irx.recv().expect("itx is closed"); match interpret.command { - Command::StartDownload(ids) => { + Command::StartDownload(id) => { let tx = interpret.response_tx.unwrap(); - tx.lock().unwrap().send(Event::FoundSystemInfo(ids.first().unwrap().to_owned())); + tx.lock().unwrap().send(Event::FoundSystemInfo(id)); } _ => panic!("expected AcceptUpdates"), } @@ -119,7 +119,7 @@ mod tests { crossbeam::scope(|scope| { for id in 0..10 { scope.spawn(move || { - let cmd = Command::StartDownload(vec!(format!("{}", id))); + let cmd = Command::StartDownload(format!("{}", id)); let client = AuthClient::default(); let url = "http://127.0.0.1:8888".parse().unwrap(); let body = json::encode(&cmd).unwrap(); diff --git a/src/gateway/socket.rs b/src/gateway/socket.rs index 295cf6e..af496c9 100644 --- a/src/gateway/socket.rs +++ b/src/gateway/socket.rs @@ -149,9 +149,9 @@ mod tests { loop { let interpret = irx.recv().expect("gtx is closed"); match interpret.command { - Command::StartDownload(ids) => { + Command::StartDownload(id) => { let tx = interpret.response_tx.unwrap(); - tx.lock().unwrap().send(Event::FoundSystemInfo(ids.first().unwrap().to_owned())); + tx.lock().unwrap().send(Event::FoundSystemInfo(id)); } _ => panic!("expected AcceptUpdates"), } diff --git a/src/gateway/websocket.rs b/src/gateway/websocket.rs index eb5e040..72e0889 100644 --- a/src/gateway/websocket.rs +++ b/src/gateway/websocket.rs @@ -140,9 +140,9 @@ mod tests { loop { let interpret = irx.recv().expect("gtx is closed"); match interpret.command { - Command::StartDownload(ids) => { + Command::StartDownload(id) => { let tx = interpret.response_tx.unwrap(); - tx.lock().unwrap().send(Event::FoundSystemInfo(ids.first().unwrap().to_owned())); + tx.lock().unwrap().send(Event::FoundSystemInfo(id)); } _ => panic!("expected AcceptUpdates"), } diff --git a/src/interpreter.rs b/src/interpreter.rs index 3495def..9797456 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -4,7 +4,7 @@ use std; use std::borrow::Cow; use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config, - Error, Event, Package, UpdateRequestId}; + Error, Event, Package, UpdateReport, UpdateRequestId, UpdateResultCode}; use gateway::Interpret; use http::{AuthClient, Client}; use oauth2::authenticate; @@ -51,17 +51,35 @@ impl Interpreter for EventInterpreter { ctx.send(Command::Authenticate(None)); } - Event::NewUpdatesReceived(ids) => { - ctx.send(Command::StartDownload(ids)); + Event::InFlightUpdatesReceived(requests) => { + if self.package_manager != PackageManager::Off { + self.package_manager.installed_packages().map(|packages| { + for request in requests { + let id = request.requestId.clone(); + if packages.contains(&request.packageId) { + let report = UpdateReport::single(id, UpdateResultCode::OK, "".to_string()); + ctx.send(Command::SendUpdateReport(report)); + } else { + ctx.send(Command::StartDownload(id)); + } + } + }).unwrap_or_else(|err| error!("couldn't get a list of packages: {}", err)); + } + } + + Event::PendingUpdatesReceived(ids) => { + for id in ids { + ctx.send(Command::StartDownload(id)); + } } Event::DownloadComplete(dl) => { if self.package_manager != PackageManager::Off { - ctx.send(Command::StartInstall(dl)); + ctx.send(Command::StartInstall(dl.update_id.clone())); } } - Event::InstallComplete(report) => { + Event::InstallComplete(report) | Event::InstallFailed(report) => { ctx.send(Command::SendUpdateReport(report)); } @@ -149,14 +167,23 @@ impl<'t> GlobalInterpreter<'t> { match cmd { Command::Authenticate(_) => etx.send(Event::Authenticated), - Command::GetNewUpdates => { + Command::GetInFlightUpdates => { + let updates = try!(sota.get_in_flight_updates()); + if updates.is_empty() { + etx.send(Event::NoInFlightUpdates); + } else { + etx.send(Event::InFlightUpdatesReceived(updates)); + } + } + + Command::GetPendingUpdates => { let mut updates = try!(sota.get_pending_updates()); if updates.is_empty() { - etx.send(Event::NoNewUpdates); + etx.send(Event::NoPendingUpdates); } else { updates.sort_by_key(|u| u.installPos); let ids = updates.iter().map(|u| u.requestId.clone()).collect::>(); - etx.send(Event::NewUpdatesReceived(ids)) + etx.send(Event::PendingUpdatesReceived(ids)); } } @@ -203,22 +230,20 @@ impl<'t> GlobalInterpreter<'t> { etx.send(Event::UpdateReportSent); } - Command::StartDownload(ref ids) => { - for id in ids { - etx.send(Event::DownloadingUpdate(id.clone())); - if let Some(ref rvi) = self.rvi { - let _ = rvi.remote.lock().unwrap().send_download_started(id.clone()); - } else { - let _ = sota.download_update(id.clone()) - .map(|dl| etx.send(Event::DownloadComplete(dl))) - .map_err(|err| etx.send(Event::DownloadFailed(id.clone(), format!("{}", err)))); - } + Command::StartDownload(id) => { + etx.send(Event::DownloadingUpdate(id.clone())); + if let Some(ref rvi) = self.rvi { + let _ = rvi.remote.lock().unwrap().send_download_started(id); + } else { + let _ = sota.download_update(id.clone()) + .map(|dl| etx.send(Event::DownloadComplete(dl))) + .map_err(|err| etx.send(Event::DownloadFailed(id, format!("{}", err)))); } } - Command::StartInstall(dl) => { - etx.send(Event::InstallingUpdate(dl.update_id.clone())); - let _ = sota.install_update(dl) + Command::StartInstall(id) => { + etx.send(Event::InstallingUpdate(id.clone())); + let _ = sota.install_update(id) .map(|report| etx.send(Event::InstallComplete(report))) .map_err(|report| etx.send(Event::InstallFailed(report))); } @@ -242,7 +267,8 @@ impl<'t> GlobalInterpreter<'t> { etx.send(Event::Authenticated); } - Command::GetNewUpdates | + Command::GetInFlightUpdates | + Command::GetPendingUpdates | Command::ListInstalledPackages | Command::ListSystemInfo | Command::SendInstalledPackages(_) | @@ -321,20 +347,14 @@ mod tests { let pkg_mgr = PackageManager::new_tpm(true); let (ctx, erx) = new_interpreter(replies, pkg_mgr); - ctx.send(Command::StartDownload(vec!["1".to_string(), "2".to_string()])); + ctx.send(Command::StartDownload("1".to_string())); assert_rx(erx, &[ Event::DownloadingUpdate("1".to_string()), Event::DownloadComplete(DownloadComplete { update_id: "1".to_string(), update_image: "/tmp/1".to_string(), signature: "".to_string() - }), - Event::DownloadingUpdate("2".to_string()), - Event::DownloadComplete(DownloadComplete { - update_id: "2".to_string(), - update_image: "/tmp/2".to_string(), - signature: "".to_string() - }), + }) ]); } @@ -344,11 +364,7 @@ mod tests { let pkg_mgr = PackageManager::new_tpm(true); let (ctx, erx) = new_interpreter(replies, pkg_mgr); - ctx.send(Command::StartInstall(DownloadComplete { - update_id: "1".to_string(), - update_image: "/tmp/1".to_string(), - signature: "".to_string() - })); + ctx.send(Command::StartInstall("1".to_string())); assert_rx(erx, &[ Event::InstallingUpdate("1".to_string()), Event::InstallComplete( @@ -363,11 +379,7 @@ mod tests { let pkg_mgr = PackageManager::new_tpm(false); let (ctx, erx) = new_interpreter(replies, pkg_mgr); - ctx.send(Command::StartInstall(DownloadComplete { - update_id: "1".to_string(), - update_image: "/tmp/1".to_string(), - signature: "".to_string() - })); + ctx.send(Command::StartInstall("1".to_string())); assert_rx(erx, &[ Event::InstallingUpdate("1".to_string()), Event::InstallFailed( diff --git a/src/main.rs b/src/main.rs index 3fd5667..3fd7797 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,7 @@ fn start_update_poller(interval: u64, itx: Sender) { loop { let _ = tick.recv(); itx.send(Interpret { - command: Command::GetNewUpdates, + command: Command::GetPendingUpdates, response_tx: Some(Arc::new(Mutex::new(etx.clone()))) }); let _ = erx.recv(); diff --git a/src/package_manager/deb.rs b/src/package_manager/deb.rs index bba86e6..845c2b8 100644 --- a/src/package_manager/deb.rs +++ b/src/package_manager/deb.rs @@ -5,7 +5,7 @@ use package_manager::package_manager::{InstallOutcome, parse_package}; /// Returns a list of installed DEB packages with -/// `dpkg-query -f='${Package} ${Version}\n -W`. +/// `dpkg-query -f='${Package} ${Version}\n' -W`. pub fn installed_packages() -> Result, Error> { Command::new("dpkg-query").arg("-f='${Package} ${Version}\n'").arg("-W") .output() diff --git a/src/rvi/parameters.rs b/src/rvi/parameters.rs index f48ea44..fc5d663 100644 --- a/src/rvi/parameters.rs +++ b/src/rvi/parameters.rs @@ -22,7 +22,7 @@ pub struct Notify { impl Parameter for Notify { fn handle(&self, remote: &Mutex, _: &Mutex) -> Result, String> { remote.lock().unwrap().backend = Some(self.services.clone()); - Ok(Some(Event::NewUpdateAvailable(self.update_available.clone()))) + Ok(Some(Event::PendingUpdateAvailable(self.update_available.clone()))) } } diff --git a/src/sota.rs b/src/sota.rs index 3dec33d..5759b9f 100644 --- a/src/sota.rs +++ b/src/sota.rs @@ -22,39 +22,43 @@ impl<'c, 'h> Sota<'c, 'h> { } /// Takes a path and returns a new endpoint of the format - /// `/api/v1/device_updates//`. - pub fn endpoint(&self, path: &str) -> Url { - let endpoint = if path.is_empty() { - format!("/api/v1/device_updates/{}", self.config.device.uuid) - } else { - format!("/api/v1/device_updates/{}/{}", self.config.device.uuid, path) - }; + /// `/api/v1/device_updates/$path`. + fn endpoint(&self, path: &str) -> Url { + let endpoint = format!("/api/v1/device_updates/{}{}", self.config.device.uuid, path); self.config.core.server.join(&endpoint).expect("couldn't build endpoint url") } - /// Query the Core server to identify any new package updates available. + /// Returns the path to a package on the device. + fn package_path(&self, id: UpdateRequestId) -> Result { + let mut path = PathBuf::new(); + path.push(&self.config.device.packages_dir); + path.push(id); + Ok(try!(path.to_str().ok_or(Error::Parse(format!("Path is not valid UTF-8: {:?}", path)))).to_string()) + } + + /// Query the Core server for any pending package updates. pub fn get_pending_updates(&mut self) -> Result, Error> { let resp_rx = self.client.get(self.endpoint(""), None); - let resp = resp_rx.recv().expect("no get_package_updates response received"); - let data = try!(resp); - let text = try!(String::from_utf8(data)); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get pending updates".to_string()))); + let text = try!(String::from_utf8(try!(resp))); + Ok(try!(json::decode::>(&text))) + } + + /// Query the Core server for any in-flight package updates. + pub fn get_in_flight_updates(&mut self) -> Result, Error> { + let resp_rx = self.client.get(self.endpoint("/queued"), None); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get in-flight updates".to_string()))); + let text = try!(String::from_utf8(try!(resp))); Ok(try!(json::decode::>(&text))) } /// Download a specific update from the Core server. pub fn download_update(&mut self, id: UpdateRequestId) -> Result { - let resp_rx = self.client.get(self.endpoint(&format!("{}/download", id)), None); - let resp = resp_rx.recv().expect("no download_package_update response received"); - let data = try!(resp); - - let mut path = PathBuf::new(); - path.push(&self.config.device.packages_dir); - path.push(id.clone()); // TODO: Use Content-Disposition filename from request? - let mut file = try!(File::create(path.as_path())); - - let _ = io::copy(&mut &*data, &mut file); - let path = try!(path.to_str().ok_or(Error::Parse(format!("Path is not valid UTF-8: {:?}", path)))); - + let resp_rx = self.client.get(self.endpoint(&format!("/{}/download", id)), None); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string()))); + let path = try!(self.package_path(id.clone())); + let mut file = try!(File::create(&path)); + let _ = io::copy(&mut &*try!(resp), &mut file); Ok(DownloadComplete { update_id: id, update_image: path.to_string(), @@ -63,26 +67,22 @@ impl<'c, 'h> Sota<'c, 'h> { } /// Install an update using the package manager. - pub fn install_update(&mut self, download: DownloadComplete) -> Result { + pub fn install_update(&mut self, id: UpdateRequestId) -> Result { let ref pacman = self.config.device.package_manager; - pacman.install_package(&download.update_image).and_then(|(code, output)| { - Ok(UpdateReport::single(download.update_id.clone(), code, output)) + let path = self.package_path(id.clone()).expect("install_update expects a valid path"); + pacman.install_package(&path).and_then(|(code, output)| { + Ok(UpdateReport::single(id.clone(), code, output)) }).or_else(|(code, output)| { - Err(UpdateReport::single(download.update_id.clone(), code, output)) + Err(UpdateReport::single(id.clone(), code, output)) }) } - /// Get a list of the currently installed packages from the package manager. - pub fn get_installed_packages(&mut self) -> Result, Error> { - Ok(try!(self.config.device.package_manager.installed_packages())) - } - /// Send a list of the currently installed packages to the Core server. pub fn send_installed_packages(&mut self, packages: &Vec) -> Result<(), Error> { let body = try!(json::encode(packages)); - let resp_rx = self.client.put(self.endpoint("installed"), Some(body.into_bytes())); - let _ = resp_rx.recv().expect("no update_installed_packages response received") - .map_err(|err| error!("update_installed_packages failed: {}", err)); + let resp_rx = self.client.put(self.endpoint("/installed"), Some(body.into_bytes())); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send installed packages".to_string()))); + let _ = resp.map_err(|err| error!("send_installed_packages failed: {}", err)); Ok(()) } @@ -90,18 +90,18 @@ impl<'c, 'h> Sota<'c, 'h> { pub fn send_update_report(&mut self, update_report: &UpdateReport) -> Result<(), Error> { let report = DeviceReport::new(&self.config.device.uuid, update_report); let body = try!(json::encode(&report)); - let url = self.endpoint(report.device); + let url = self.endpoint(&format!("/{}", report.device)); let resp_rx = self.client.post(url, Some(body.into_bytes())); - let resp = resp_rx.recv().expect("no send_install_report response received"); - let _ = try!(resp); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send update report".to_string()))); + let _ = resp.map_err(|err| error!("send_update_report failed: {}", err)); Ok(()) } /// Send system information from the device to the Core server. pub fn send_system_info(&mut self, body: &str) -> Result<(), Error> { - let resp_rx = self.client.put(self.endpoint("system_info"), Some(body.as_bytes().to_vec())); - let resp = resp_rx.recv().expect("no send_system_info response received"); - let _ = try!(resp); + let resp_rx = self.client.put(self.endpoint("/system_info"), Some(body.as_bytes().to_vec())); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send system info".to_string()))); + let _ = resp.map_err(|err| error!("send_system_info failed: {}", err)); Ok(()) } } -- cgit v1.2.1 From 5b69a479dd4ebf6f8c6fddb3ef48823b59bb0a17 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Thu, 8 Sep 2016 14:55:33 +0200 Subject: Send an UpdateReport after a DownloadFailed Event --- Makefile | 7 ++++--- run/sota.toml.env | 4 ++-- src/gateway/socket.rs | 56 +++++++++++++++++++++++++++++++++------------------ src/interpreter.rs | 5 +++++ 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 8cdbfa4..d06e41a 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ RUST_IN_DOCKER := \ advancedtelematic/rust:latest CARGO := $(RUST_IN_DOCKER) cargo +TARGET := x86_64-unknown-linux-gnu # function for building new packages define make-pkg @@ -58,7 +59,7 @@ clean: ## Remove all compiled libraries, builds and temporary files. @rm -rf rust-openssl .cargo test: rust-openssl ## Run all cargo tests. - $(CARGO) test + $(CARGO) test --target=$(TARGET) doc: ## Generate documentation for the sota crate. $(CARGO) doc --lib --no-deps --release @@ -68,8 +69,8 @@ clippy: ## Run clippy lint checks using the nightly compiler. rustup run nightly cargo clippy -- -Dclippy client: rust-openssl src/ ## Compile a new release build of the client. - $(CARGO) build --release --target=x86_64-unknown-linux-gnu - @cp target/x86_64-unknown-linux-gnu/release/sota_client run/ + $(CARGO) build --release --target=$(TARGET) + @cp target/$(TARGET)/release/sota_client run/ image: client ## Build a Docker image for running the client. @docker build --tag advancedtelematic/sota-client run diff --git a/run/sota.toml.env b/run/sota.toml.env index 1ef5619..ecf15b5 100644 --- a/run/sota.toml.env +++ b/run/sota.toml.env @@ -23,8 +23,8 @@ GATEWAY_RVI=false GATEWAY_SOCKET=false GATEWAY_WEBSOCKET=true -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/src/gateway/socket.rs b/src/gateway/socket.rs index af496c9..4022c86 100644 --- a/src/gateway/socket.rs +++ b/src/gateway/socket.rs @@ -1,12 +1,12 @@ use chan; use chan::Sender; -use rustc_serialize::json; +use rustc_serialize::{Encodable, json}; use std::io::{BufReader, Read, Write}; use std::net::Shutdown; use std::sync::{Arc, Mutex}; use std::{fs, thread}; -use datatype::{Command, DownloadComplete, Error, Event}; +use datatype::{Command, Error, Event}; use super::{Gateway, Interpret}; use unix_socket::{UnixListener, UnixStream}; @@ -53,23 +53,32 @@ impl Gateway for Socket { } fn pulse(&self, event: Event) { - match event { + let output = match event { Event::DownloadComplete(dl) => { - let _ = UnixStream::connect(&self.events_path).map(|mut stream| { - let output = DownloadCompleteEvent { - version: "0.1".to_string(), - event: "DownloadComplete".to_string(), - data: dl - }; - stream.write_all(&json::encode(&output).expect("couldn't encode Event").into_bytes()) - .unwrap_or_else(|err| error!("couldn't write to events socket: {}", err)); - stream.shutdown(Shutdown::Write) - .unwrap_or_else(|err| error!("couldn't close events socket: {}", err)); - }).map_err(|err| error!("couldn't open events socket: {}", err)); + json::encode(&EventWrapper { + version: "0.1".to_string(), + event: "DownloadComplete".to_string(), + data: dl + }).expect("couldn't encode DownloadComplete event") + } + + Event::DownloadFailed(id, reason) => { + json::encode(&EventWrapper { + version: "0.1".to_string(), + event: "DownloadFailed".to_string(), + data: DownloadFailedWrapper { update_id: id, reason: reason } + }).expect("couldn't encode DownloadFailed event") } - _ => () - } + _ => return + }; + + let _ = UnixStream::connect(&self.events_path).map(|mut stream| { + stream.write_all(&output.into_bytes()) + .unwrap_or_else(|err| error!("couldn't write to events socket: {}", err)); + stream.shutdown(Shutdown::Write) + .unwrap_or_else(|err| error!("couldn't close events socket: {}", err)); + }).map_err(|err| debug!("couldn't open events socket: {}", err)); } } @@ -89,12 +98,19 @@ fn handle_client(stream: &mut UnixStream, itx: Arc>>) -> erx.recv().ok_or(Error::Socket("internal receiver error".to_string())) } + // FIXME(PRO-1322): create a proper JSON api -#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] -pub struct DownloadCompleteEvent { +#[derive(RustcEncodable, RustcDecodable, PartialEq, Eq, Debug)] +pub struct EventWrapper { pub version: String, pub event: String, - pub data: DownloadComplete + pub data: E +} + +#[derive(RustcEncodable, RustcDecodable, PartialEq, Eq, Debug)] +pub struct DownloadFailedWrapper { + pub update_id: String, + pub reason: String } @@ -139,7 +155,7 @@ mod tests { let (mut stream, _) = server.accept().expect("couldn't read from events socket"); let mut text = String::new(); stream.read_to_string(&mut text).unwrap(); - let receive: DownloadCompleteEvent = json::decode(&text).expect("couldn't decode DownloadComplete message"); + let receive: EventWrapper = json::decode(&text).expect("couldn't decode Event"); assert_eq!(receive.version, "0.1".to_string()); assert_eq!(receive.event, "DownloadComplete".to_string()); assert_eq!(receive.data, send); diff --git a/src/interpreter.rs b/src/interpreter.rs index 9797456..c009d3c 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -79,6 +79,11 @@ impl Interpreter for EventInterpreter { } } + Event::DownloadFailed(id, reason) => { + let report = UpdateReport::single(id, UpdateResultCode::GENERAL_ERROR, reason); + ctx.send(Command::SendUpdateReport(report)); + } + Event::InstallComplete(report) | Event::InstallFailed(report) => { ctx.send(Command::SendUpdateReport(report)); } -- cgit v1.2.1 From 3b56557d70893122575be2bd4422e5b5d65f7f7f Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 9 Sep 2016 12:35:01 +0200 Subject: Handle the returned UpdateRequestStatus from Core --- Cargo.toml | 6 --- docs/client-guide.adoc | 2 +- src/datatype/command.rs | 38 +++++---------- src/datatype/config.rs | 8 ++-- src/datatype/event.rs | 19 +++----- src/datatype/mod.rs | 7 +-- src/datatype/package.rs | 79 -------------------------------- src/datatype/update_request.rs | 84 ++++++++++++++++++++++++++++++++++ src/gateway/dbus.rs | 2 +- src/gateway/socket.rs | 10 +--- src/http/auth_client.rs | 3 +- src/interpreter.rs | 63 +++++++++---------------- src/main.rs | 2 +- src/package_manager/package_manager.rs | 6 +++ src/rvi/parameters.rs | 2 +- src/sota.rs | 32 ++++++------- tests/sota.toml | 4 +- 17 files changed, 161 insertions(+), 206 deletions(-) delete mode 100644 src/datatype/package.rs create mode 100644 src/datatype/update_request.rs diff --git a/Cargo.toml b/Cargo.toml index 179e4fd..cbc8b2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,9 +34,3 @@ toml = "0.1.30" unix_socket = "0.5.0" url = "1.1.1" ws = "0.5.0" - -[profile.dev] -panic = 'abort' - -[profile.release] -panic = 'abort' diff --git a/docs/client-guide.adoc b/docs/client-guide.adoc index bda8174..b5dab43 100644 --- a/docs/client-guide.adoc +++ b/docs/client-guide.adoc @@ -86,7 +86,7 @@ http_server = "127.0.0.1:8080" <1> socket_commands_path = "/tmp/sota-commands.socket" <2> socket_events_path = "/tmp/sota-events.socket" <3> websocket_server = "https://sota-core.gw.prod01.advancedtelematic.com" <4> -rvi_edge_server = "http://127.0.0.1:9080" <5> +rvi_edge_server = "127.0.0.1:9080" <5> ---- <1> The path to the http-only core server, if the http gateway is enabled in the [gateway] section. <2> The name of the unix domain socket to be used for sending commands, if the socket gateway is enabled in the [gateway] section. diff --git a/src/datatype/command.rs b/src/datatype/command.rs index 11707ea..71567da 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -15,10 +15,8 @@ pub enum Command { /// Shutdown the client immediately. Shutdown, - /// Check for any pending updates. - GetPendingUpdates, - /// Check for any in-flight updates. - GetInFlightUpdates, + /// Check for any pending or in-flight updates. + GetUpdateRequests, /// List the installed packages on the system. ListInstalledPackages, @@ -67,10 +65,8 @@ named!(command <(Command, Vec<&str>)>, chain!( ~ cmd: alt!( alt_complete!(tag!("Authenticate") | tag!("auth")) => { |_| Command::Authenticate(None) } - | alt_complete!(tag!("GetPendingUpdates") | tag!("getpend")) - => { |_| Command::GetPendingUpdates } - | alt_complete!(tag!("GetInFlightUpdates") | tag!("getflight")) - => { |_| Command::GetInFlightUpdates } + | alt_complete!(tag!("GetUpdateRequests") | tag!("getreq")) + => { |_| Command::GetUpdateRequests } | alt_complete!(tag!("ListInstalledPackages") | tag!("ls")) => { |_| Command::ListInstalledPackages } | alt_complete!(tag!("ListSystemInfo") | tag!("info")) @@ -120,14 +116,9 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { _ => Err(Error::Command(format!("unexpected Authenticate args: {:?}", args))), }, - Command::GetPendingUpdates => match args.len() { - 0 => Ok(Command::GetPendingUpdates), - _ => Err(Error::Command(format!("unexpected GetPendingUpdates args: {:?}", args))), - }, - - Command::GetInFlightUpdates => match args.len() { - 0 => Ok(Command::GetInFlightUpdates), - _ => Err(Error::Command(format!("unexpected GetInFlightUpdates args: {:?}", args))), + Command::GetUpdateRequests => match args.len() { + 0 => Ok(Command::GetUpdateRequests), + _ => Err(Error::Command(format!("unexpected GetUpdateRequests args: {:?}", args))), }, Command::ListInstalledPackages => match args.len() { @@ -241,17 +232,10 @@ mod tests { } #[test] - fn get_pending_updates_test() { - assert_eq!("GetPendingUpdates".parse::().unwrap(), Command::GetPendingUpdates); - assert_eq!("getpend".parse::().unwrap(), Command::GetPendingUpdates); - assert!("getpend old".parse::().is_err()); - } - - #[test] - fn get_in_flight_updates_test() { - assert_eq!("GetInFlightUpdates".parse::().unwrap(), Command::GetInFlightUpdates); - assert_eq!("getflight".parse::().unwrap(), Command::GetInFlightUpdates); - assert!("getflight old".parse::().is_err()); + fn get_update_requests_test() { + assert_eq!("GetUpdateRequests".parse::().unwrap(), Command::GetUpdateRequests); + assert_eq!("getreq".parse::().unwrap(), Command::GetUpdateRequests); + assert!("getreq now".parse::().is_err()); } #[test] diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 650008b..c9c7708 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -262,8 +262,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".to_string(), + rvi_edge_server: "127.0.0.1:9080".to_string(), 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() @@ -348,8 +348,8 @@ mod tests { 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" diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 6a493f0..2d6f895 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; -use datatype::{DownloadComplete, Package, PendingUpdateRequest, UpdateAvailable, - UpdateReport, UpdateRequestId}; +use datatype::{DownloadComplete, Package, UpdateAvailable, UpdateReport, + UpdateRequest, UpdateRequestId}; /// System-wide events that are broadcast to all interested parties. @@ -15,17 +15,12 @@ pub enum Event { /// An operation failed because we are not currently authenticated. NotAuthenticated, - /// There are pending updates available. - PendingUpdatesReceived(Vec), + /// A notification from Core of pending or in-flight updates. + UpdatesReceived(Vec), /// A notification from RVI of a pending update. - PendingUpdateAvailable(UpdateAvailable), - /// There are no pending updates. - NoPendingUpdates, - - /// There are in-flight updates available. - InFlightUpdatesReceived(Vec), - /// There are no in-flight updates. - NoInFlightUpdates, + UpdateAvailable(UpdateAvailable), + /// There are no outstanding update requests. + NoUpdateRequests, /// The following packages are installed on the device. FoundInstalledPackages(Vec), diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 8a9ca4e..6516422 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -5,9 +5,9 @@ pub mod dbus; pub mod error; pub mod event; pub mod json_rpc; -pub mod package; pub mod system_info; pub mod update_report; +pub mod update_request; pub mod url; pub use self::auth::{AccessToken, Auth, ClientId, ClientSecret, ClientCredentials}; @@ -17,10 +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::package::{ChunkReceived, DownloadStarted, DownloadComplete, Package, - PendingUpdateRequest, UpdateAvailable, UpdateRequestId}; pub use self::system_info::SystemInfo; 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/package.rs b/src/datatype/package.rs deleted file mode 100644 index 146ff06..0000000 --- a/src/datatype/package.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::fmt::{Display, Formatter, Result as FmtResult}; - -use rvi::services::LocalServices; - - -/// Encapsulate a `String` type used to represent the `Package` version. -pub type Version = String; - -/// Encodes the name and version of a specific package. -#[derive(Debug, PartialEq, Eq, RustcEncodable, RustcDecodable, Clone)] -pub struct Package { - pub name: String, - pub version: Version -} - -impl Display for Package { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{} {}", self.name, self.version) - } -} - - -/// Encapsulate a `String` type as the id of a specific update request. -pub type UpdateRequestId = String; - -/// A single pending update request to be installed by the client. -#[allow(non_snake_case)] -#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct PendingUpdateRequest { - pub requestId: UpdateRequestId, - pub installPos: i32, - pub packageId: Package, - pub createdAt: String -} - -/// A notification from RVI that a new update is available. -#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] -pub struct UpdateAvailable { - pub update_id: String, - pub signature: String, - pub description: String, - pub request_confirmation: bool, - pub size: u64 -} - -/// A JSON-RPC request type to notify RVI that a new package download has started. -#[derive(RustcDecodable, RustcEncodable)] -pub struct DownloadStarted { - pub device: String, - pub update_id: UpdateRequestId, - pub services: LocalServices, -} - -/// A JSON-RPC request type to notify RVI that a new package chunk was received. -#[derive(RustcDecodable, RustcEncodable)] -pub struct ChunkReceived { - pub device: String, - pub update_id: UpdateRequestId, - pub chunks: Vec, -} - -/// A notification to indicate to any external package manager that the package -/// download has successfully completed. -#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] -pub struct DownloadComplete { - pub update_id: String, - pub update_image: String, - pub signature: String -} - -impl Default for DownloadComplete { - fn default() -> Self { - DownloadComplete { - update_id: "".to_string(), - update_image: "".to_string(), - signature: "".to_string() - } - } -} diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs new file mode 100644 index 0000000..7398848 --- /dev/null +++ b/src/datatype/update_request.rs @@ -0,0 +1,84 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; + +use rvi::services::LocalServices; + + +/// Encapsulate a `String` type as the id of a specific update request. +pub type UpdateRequestId = String; + +/// A device update request from Core to be installed by the client. +#[allow(non_snake_case)] +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub struct UpdateRequest { + pub requestId: UpdateRequestId, + pub status: UpdateRequestStatus, + pub packageId: Package, + pub installPos: i32, + pub createdAt: String, +} + +/// The status of an `UpdateRequest` from Core. +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub enum UpdateRequestStatus { + Pending, + InFlight, + Canceled, + Failed, + Finished +} + + +/// Encodes the name and version of a specific package. +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub struct Package { + pub name: String, + pub version: String +} + +impl Display for Package { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{} {}", self.name, self.version) + } +} + + +/// A notification from RVI that a new update is available. +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub struct UpdateAvailable { + pub update_id: String, + pub signature: String, + pub description: String, + pub request_confirmation: bool, + pub size: u64 +} + +/// A JSON-RPC request type to notify RVI that a new package download has started. +#[derive(RustcDecodable, RustcEncodable)] +pub struct DownloadStarted { + pub device: String, + pub update_id: UpdateRequestId, + pub services: LocalServices, +} + +/// A JSON-RPC request type to notify RVI that a new package chunk was received. +#[derive(RustcDecodable, RustcEncodable)] +pub struct ChunkReceived { + pub device: String, + pub update_id: UpdateRequestId, + pub chunks: Vec, +} + +/// A notification to an external package manager that the package was downloaded. +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub struct DownloadComplete { + pub update_id: String, + pub update_image: String, + pub signature: String +} + +/// A notification to an external package manager that the package download failed. +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub struct DownloadFailed { + pub update_id: String, + pub reason: String +} diff --git a/src/gateway/dbus.rs b/src/gateway/dbus.rs index ea807f6..07a3b9c 100644 --- a/src/gateway/dbus.rs +++ b/src/gateway/dbus.rs @@ -47,7 +47,7 @@ impl Gateway for DBus { fn pulse(&self, event: Event) { match event { - Event::PendingUpdateAvailable(avail) => { + Event::UpdateAvailable(avail) => { let msg = self.new_message("updateAvailable", &[ MessageItem::from(avail.update_id), MessageItem::from(avail.signature), diff --git a/src/gateway/socket.rs b/src/gateway/socket.rs index 4022c86..571e34c 100644 --- a/src/gateway/socket.rs +++ b/src/gateway/socket.rs @@ -6,7 +6,7 @@ use std::net::Shutdown; use std::sync::{Arc, Mutex}; use std::{fs, thread}; -use datatype::{Command, Error, Event}; +use datatype::{Command, DownloadFailed, Error, Event}; use super::{Gateway, Interpret}; use unix_socket::{UnixListener, UnixStream}; @@ -66,7 +66,7 @@ impl Gateway for Socket { json::encode(&EventWrapper { version: "0.1".to_string(), event: "DownloadFailed".to_string(), - data: DownloadFailedWrapper { update_id: id, reason: reason } + data: DownloadFailed { update_id: id, reason: reason } }).expect("couldn't encode DownloadFailed event") } @@ -107,12 +107,6 @@ pub struct EventWrapper { pub data: E } -#[derive(RustcEncodable, RustcDecodable, PartialEq, Eq, Debug)] -pub struct DownloadFailedWrapper { - pub update_id: String, - pub reason: String -} - #[cfg(test)] mod tests { diff --git a/src/http/auth_client.rs b/src/http/auth_client.rs index f97f4d7..d9e28cb 100644 --- a/src/http/auth_client.rs +++ b/src/http/auth_client.rs @@ -197,7 +197,8 @@ impl Handler for AuthHandler { } else if resp.status().is_redirection() { self.redirect_request(resp); Next::end() - } else if resp.status() == &StatusCode::Forbidden { + } else if resp.status() == &StatusCode::Unauthorized + || resp.status() == &StatusCode::Forbidden { self.resp_tx.send(Err(Error::Authorization(format!("{}", resp.status())))); Next::end() } else { diff --git a/src/interpreter.rs b/src/interpreter.rs index c009d3c..1b671d8 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -4,7 +4,8 @@ use std; use std::borrow::Cow; use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config, - Error, Event, Package, UpdateReport, UpdateRequestId, UpdateResultCode}; + Error, Event, Package, UpdateReport, UpdateRequestStatus as Status, + UpdateResultCode}; use gateway::Interpret; use http::{AuthClient, Client}; use oauth2::authenticate; @@ -51,25 +52,24 @@ impl Interpreter for EventInterpreter { ctx.send(Command::Authenticate(None)); } - Event::InFlightUpdatesReceived(requests) => { - if self.package_manager != PackageManager::Off { - self.package_manager.installed_packages().map(|packages| { - for request in requests { - let id = request.requestId.clone(); - if packages.contains(&request.packageId) { - let report = UpdateReport::single(id, UpdateResultCode::OK, "".to_string()); - ctx.send(Command::SendUpdateReport(report)); + Event::UpdatesReceived(requests) => { + for request in requests { + match (request.status, &self.package_manager) { + (Status::Pending, _) => ctx.send(Command::StartDownload(request.requestId.clone())), + + (Status::InFlight, &PackageManager::Off) => (), + + (Status::InFlight, _) => { + if self.package_manager.is_installed(&request.packageId) { + ctx.send(Command::SendUpdateReport(UpdateReport::single( + request.requestId.clone(), UpdateResultCode::OK, "".to_string()))); } else { - ctx.send(Command::StartDownload(id)); + ctx.send(Command::StartDownload(request.requestId.clone())); } } - }).unwrap_or_else(|err| error!("couldn't get a list of packages: {}", err)); - } - } - Event::PendingUpdatesReceived(ids) => { - for id in ids { - ctx.send(Command::StartDownload(id)); + _ => () + } } } @@ -172,23 +172,13 @@ impl<'t> GlobalInterpreter<'t> { match cmd { Command::Authenticate(_) => etx.send(Event::Authenticated), - Command::GetInFlightUpdates => { - let updates = try!(sota.get_in_flight_updates()); + Command::GetUpdateRequests => { + let mut updates = try!(sota.get_update_requests()); if updates.is_empty() { - etx.send(Event::NoInFlightUpdates); - } else { - etx.send(Event::InFlightUpdatesReceived(updates)); - } - } - - Command::GetPendingUpdates => { - let mut updates = try!(sota.get_pending_updates()); - if updates.is_empty() { - etx.send(Event::NoPendingUpdates); + etx.send(Event::NoUpdateRequests); } else { updates.sort_by_key(|u| u.installPos); - let ids = updates.iter().map(|u| u.requestId.clone()).collect::>(); - etx.send(Event::PendingUpdatesReceived(ids)); + etx.send(Event::UpdatesReceived(updates)); } } @@ -272,18 +262,9 @@ impl<'t> GlobalInterpreter<'t> { etx.send(Event::Authenticated); } - Command::GetInFlightUpdates | - Command::GetPendingUpdates | - Command::ListInstalledPackages | - Command::ListSystemInfo | - Command::SendInstalledPackages(_) | - Command::SendInstalledSoftware(_) | - Command::SendUpdateReport(_) | - Command::SendSystemInfo | - Command::StartDownload(_) | - Command::StartInstall(_) => etx.send(Event::NotAuthenticated), - Command::Shutdown => std::process::exit(0), + + _ => etx.send(Event::NotAuthenticated) } Ok(()) diff --git a/src/main.rs b/src/main.rs index 3fd7797..031734c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,7 @@ fn start_update_poller(interval: u64, itx: Sender) { loop { let _ = tick.recv(); itx.send(Interpret { - command: Command::GetPendingUpdates, + command: Command::GetUpdateRequests, response_tx: Some(Arc::new(Mutex::new(etx.clone()))) }); let _ = erx.recv(); diff --git a/src/package_manager/package_manager.rs b/src/package_manager/package_manager.rs index 09556a0..173a636 100644 --- a/src/package_manager/package_manager.rs +++ b/src/package_manager/package_manager.rs @@ -49,6 +49,12 @@ impl PackageManager { } } + /// Indicates whether a specific package is installed on the device. + pub fn is_installed(&self, package: &Package) -> bool { + self.installed_packages().map(|packages| packages.contains(package)) + .unwrap_or_else(|err| { error!("couldn't get a list of packages: {}", err); false }) + } + /// Returns a string representation of the package manager's extension. pub fn extension(&self) -> String { match *self { diff --git a/src/rvi/parameters.rs b/src/rvi/parameters.rs index fc5d663..e23ce9d 100644 --- a/src/rvi/parameters.rs +++ b/src/rvi/parameters.rs @@ -22,7 +22,7 @@ pub struct Notify { impl Parameter for Notify { fn handle(&self, remote: &Mutex, _: &Mutex) -> Result, String> { remote.lock().unwrap().backend = Some(self.services.clone()); - Ok(Some(Event::PendingUpdateAvailable(self.update_available.clone()))) + Ok(Some(Event::UpdateAvailable(self.update_available.clone()))) } } diff --git a/src/sota.rs b/src/sota.rs index 5759b9f..aacf7c4 100644 --- a/src/sota.rs +++ b/src/sota.rs @@ -4,7 +4,7 @@ use std::io; use std::path::PathBuf; use datatype::{Config, DeviceReport, DownloadComplete, Error, Package, - PendingUpdateRequest, UpdateRequestId, UpdateReport, Url}; + UpdateReport, UpdateRequest, UpdateRequestId, Url}; use http::Client; @@ -36,20 +36,13 @@ impl<'c, 'h> Sota<'c, 'h> { Ok(try!(path.to_str().ok_or(Error::Parse(format!("Path is not valid UTF-8: {:?}", path)))).to_string()) } - /// Query the Core server for any pending package updates. - pub fn get_pending_updates(&mut self) -> Result, Error> { - let resp_rx = self.client.get(self.endpoint(""), None); - let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get pending updates".to_string()))); - let text = try!(String::from_utf8(try!(resp))); - Ok(try!(json::decode::>(&text))) - } - - /// Query the Core server for any in-flight package updates. - pub fn get_in_flight_updates(&mut self) -> Result, Error> { + /// Query the Core server for any pending or in-flight package updates. + pub fn get_update_requests(&mut self) -> Result, Error> { + let _ = self.client.get(self.endpoint(""), None); // FIXME(PRO-1352): single endpoint let resp_rx = self.client.get(self.endpoint("/queued"), None); - let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get in-flight updates".to_string()))); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get new updates".to_string()))); let text = try!(String::from_utf8(try!(resp))); - Ok(try!(json::decode::>(&text))) + Ok(try!(json::decode::>(&text))) } /// Download a specific update from the Core server. @@ -112,29 +105,30 @@ mod tests { use rustc_serialize::json; use super::*; - use datatype::{Config, Package, PendingUpdateRequest}; + use datatype::{Config, Package, UpdateRequest, UpdateRequestStatus}; use http::TestClient; #[test] - fn test_get_pending_updates() { - let pending_update = PendingUpdateRequest { + fn test_get_update_requests() { + let pending_update = UpdateRequest { requestId: "someid".to_string(), - installPos: 0, + status: UpdateRequestStatus::Pending, packageId: Package { name: "fake-pkg".to_string(), version: "0.1.1".to_string() }, + installPos: 0, createdAt: "2010-01-01".to_string() }; let json = format!("[{}]", json::encode(&pending_update).unwrap()); let mut sota = Sota { config: &Config::default(), - client: &mut TestClient::from(vec![json.to_string()]), + client: &mut TestClient::from(vec![json.to_string(), "[]".to_string()]), }; - let updates: Vec = sota.get_pending_updates().unwrap(); + let updates: Vec = sota.get_update_requests().unwrap(); let ids: Vec = updates.iter().map(|p| p.requestId.clone()).collect(); assert_eq!(ids, vec!["someid".to_string()]) } diff --git a/tests/sota.toml b/tests/sota.toml index 87cf144..0444a71 100644 --- a/tests/sota.toml +++ b/tests/sota.toml @@ -33,8 +33,8 @@ socket = false websocket = true [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" -- cgit v1.2.1 From 5f27af3e555ff61c702710d7f66b0d1d9a692a0e Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 9 Sep 2016 17:51:22 +0200 Subject: Return interpreter Errors for NotAuthenticated Event handling. --- src/interpreter.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 1b671d8..75e6e8b 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -196,8 +196,7 @@ impl<'t> GlobalInterpreter<'t> { } Command::SendInstalledPackages(packages) => { - let _ = sota.send_installed_packages(&packages) - .map_err(|err| error!("couldn't send installed packages: {}", err)); + try!(sota.send_installed_packages(&packages)); etx.send(Event::InstalledPackagesSent); } @@ -210,8 +209,7 @@ impl<'t> GlobalInterpreter<'t> { Command::SendSystemInfo => { let info = try!(self.config.device.system_info.report()); - let _ = sota.send_system_info(&info) - .map_err(|err| error!("couldn't send system info: {}", err)); + try!(sota.send_system_info(&info)); etx.send(Event::SystemInfoSent); } @@ -219,8 +217,7 @@ impl<'t> GlobalInterpreter<'t> { if let Some(ref rvi) = self.rvi { let _ = rvi.remote.lock().unwrap().send_update_report(report); } else { - let _ = sota.send_update_report(&report) - .map_err(|err| error!("couldn't send update report: {}", err)); + try!(sota.send_update_report(&report)); } etx.send(Event::UpdateReportSent); } -- cgit v1.2.1 From b3379e0a3519149e97d17e218de0cb45b420707f Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 9 Sep 2016 18:07:38 +0200 Subject: Return Error from Sota methods rather than logging --- src/sota.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sota.rs b/src/sota.rs index aacf7c4..6c48424 100644 --- a/src/sota.rs +++ b/src/sota.rs @@ -75,7 +75,7 @@ impl<'c, 'h> Sota<'c, 'h> { let body = try!(json::encode(packages)); let resp_rx = self.client.put(self.endpoint("/installed"), Some(body.into_bytes())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send installed packages".to_string()))); - let _ = resp.map_err(|err| error!("send_installed_packages failed: {}", err)); + let _ = try!(resp); Ok(()) } @@ -86,7 +86,7 @@ impl<'c, 'h> Sota<'c, 'h> { let url = self.endpoint(&format!("/{}", report.device)); let resp_rx = self.client.post(url, Some(body.into_bytes())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send update report".to_string()))); - let _ = resp.map_err(|err| error!("send_update_report failed: {}", err)); + let _ = try!(resp); Ok(()) } @@ -94,7 +94,7 @@ impl<'c, 'h> Sota<'c, 'h> { pub fn send_system_info(&mut self, body: &str) -> Result<(), Error> { let resp_rx = self.client.put(self.endpoint("/system_info"), Some(body.as_bytes().to_vec())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send system info".to_string()))); - let _ = resp.map_err(|err| error!("send_system_info failed: {}", err)); + let _ = try!(resp); Ok(()) } } -- cgit v1.2.1 From 9d2d83bb849bf8227f9df724a5a4b9bb7ed75ba2 Mon Sep 17 00:00:00 2001 From: Jon Oster Date: Mon, 12 Sep 2016 13:46:15 +0200 Subject: Add info about the certificates file, events socket, and github repo --- docs/client-guide.adoc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/client-guide.adoc b/docs/client-guide.adoc index b5dab43..5968573 100644 --- a/docs/client-guide.adoc +++ b/docs/client-guide.adoc @@ -3,6 +3,12 @@ :toc: left :toclevels: 3 +== Introduction + +The SOTA client is fully open source. If you wish, you can download and build the absolute latest version directly from https://github.com/advancedtelematic/rvi_sota_client[its GitHub repo]. However, ATS recommends using the provided binaries or building from the `stable` branch, as the latest master may not always be fully compatible with ATS Garage. + +This document describes link:#_starting_the_sota_client[how to manually install and set up the SOTA client], provides link:#_sota_client_config_file_guide[a line-by-line guide to the config file], and then describes link:#_api_documentation[the UNIX Domain Sockets API] for the SOTA client to communicate with the software loading manager. + == Starting the SOTA client The SOTA client should be run at startup, as a service. We provide a systemd service file below as an example, but systemd is not a requirement. You can also, of course, run it from the command line to test. Setting the `RUST_LOG` environment variable will let you configure how much debug info you receive. @@ -74,7 +80,7 @@ certificates_path = "/tmp/sota_certificates" <7> <4> The frequency, in seconds, with which the SOTA client should poll the server for updates. <5> The location SOTA Client should use for temporary package storage until they are processed by the software loading manager. <6> The software loading manager backend to use. Possible values are `deb`, `rpm`, and `off`. If an external software loading manager is in use, this should be set to `off`. -<7> The certificate authorities SOTA Client trusts. Defaults taken from Mozilla Servo. +<7> The certificate authorities SOTA Client trusts. The recommended defaults are taken from Mozilla Servo, and the default certificates file can be downloaded from https://raw.githubusercontent.com/advancedtelematic/rvi_sota_client/master/run/sota_certificates[the SOTA client repo]. You may also use your own CA file or the operating system's default trusted certificates file, but ATS recommends using the provided certificate file. === [network] @@ -155,6 +161,8 @@ Currently, only the core functionality of making software updates available and To communicate with the SOTA Client over unix domain sockets, `socket = true` must be defined in the `[gateway]` section of the config file. Additionally, the names of the two sockets are configurable in the link:#__network[[network]] section. +The SOTA Client will create the commands socket, but the software loading manager must create the events socket. Note that it must be readable and writable by root, and be at the location configured in `sota.toml`. + ==== DownloadComplete Once the SOTA client has successfully downloaded an update ordered by an ATS Garage user, it will send a DownloadComplete event on the events socket with the following body: -- cgit v1.2.1 From b4d263c28fbc408d6dc2a437bd4a4affd5b6072e Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 26 Sep 2016 16:18:21 +0200 Subject: Return the HTTP Body when available --- run/system_info.sh | 2 +- src/datatype/auth.rs | 29 +++----- src/datatype/command.rs | 16 ++--- src/datatype/error.rs | 9 ++- src/datatype/json_rpc.rs | 10 ++- src/datatype/mod.rs | 2 +- src/gateway/http.rs | 10 ++- src/http/auth_client.rs | 162 ++++++++++++++++++++++----------------------- src/http/http_client.rs | 44 +++++++++++- src/http/mod.rs | 2 +- src/http/test_client.rs | 10 ++- src/interpreter.rs | 20 +++--- src/oauth2.rs | 11 +-- src/package_manager/rpm.rs | 2 +- src/sota.rs | 49 ++++++++++---- 15 files changed, 224 insertions(+), 154 deletions(-) diff --git a/run/system_info.sh b/run/system_info.sh index ccbca86..e47ef4a 100755 --- a/run/system_info.sh +++ b/run/system_info.sh @@ -14,4 +14,4 @@ if [ -f $SERVICE_HOSTNAME_PATH ]; then INFO=$(echo $SERVICE_HOSTNAME $INFO | jq -s add | jq -r .) fi -echo $INFO | jq -r . +echo $INFO | jq -c -r . diff --git a/src/datatype/auth.rs b/src/datatype/auth.rs index cbfd097..83c872a 100644 --- a/src/datatype/auth.rs +++ b/src/datatype/auth.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; #[derive(Clone, Debug)] pub enum Auth { None, - Credentials(ClientId, ClientSecret), + Credentials(ClientCredentials), Token(AccessToken), } @@ -16,8 +16,15 @@ impl<'a> Into> for Auth { } -/// For storage of the returned access token data following a successful -/// authentication. +/// Encapsulates the client id and secret used during authentication. +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct ClientCredentials { + pub client_id: String, + pub client_secret: String, +} + + +/// Stores the returned access token data following a successful authentication. #[derive(RustcDecodable, Debug, PartialEq, Clone, Default)] pub struct AccessToken { pub access_token: String, @@ -31,19 +38,3 @@ impl<'a> Into> for AccessToken { Cow::Owned(self) } } - - -/// Encapsulates a `String` type for use in `Auth::Credentials` -#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct ClientId(pub String); - -/// Encapsulates a `String` type for use in `Auth::Credentials` -#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct ClientSecret(pub String); - -/// Encapsulates the client id and secret used during authentication. -#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct ClientCredentials { - pub client_id: ClientId, - pub client_secret: ClientSecret, -} diff --git a/src/datatype/command.rs b/src/datatype/command.rs index 71567da..c88e8d5 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -3,8 +3,8 @@ use std::str; use std::str::FromStr; use nom::{IResult, space, eof}; -use datatype::{ClientCredentials, ClientId, ClientSecret, Error, InstalledSoftware, - Package, UpdateReport, UpdateRequestId, UpdateResultCode}; +use datatype::{ClientCredentials, Error, InstalledSoftware, Package, UpdateReport, + UpdateRequestId, UpdateResultCode}; /// System-wide commands that are sent to the interpreter. @@ -111,8 +111,9 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { 0 => Ok(Command::Authenticate(None)), 1 => Err(Error::Command("usage: auth ".to_string())), 2 => Ok(Command::Authenticate(Some(ClientCredentials { - client_id: ClientId(args[0].to_string()), - client_secret: ClientSecret(args[1].to_string())}))), + client_id: args[0].to_string(), + client_secret: args[1].to_string() + }))), _ => Err(Error::Command(format!("unexpected Authenticate args: {:?}", args))), }, @@ -192,8 +193,7 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { #[cfg(test)] mod tests { use super::{command, arguments}; - use datatype::{Command, ClientCredentials, ClientId, ClientSecret, Package, - UpdateReport, UpdateResultCode}; + use datatype::{Command, ClientCredentials, Package, UpdateReport, UpdateResultCode}; use nom::IResult; @@ -224,8 +224,8 @@ mod tests { assert_eq!("auth".parse::().unwrap(), Command::Authenticate(None)); assert_eq!("auth user pass".parse::().unwrap(), Command::Authenticate(Some(ClientCredentials { - client_id: ClientId("user".to_string()), - client_secret: ClientSecret("pass".to_string()), + client_id: "user".to_string(), + client_secret: "pass".to_string(), }))); assert!("auth one".parse::().is_err()); assert!("auth one two three".parse::().is_err()); diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 8267234..bb0ab4e 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -13,7 +13,7 @@ use toml::{ParserError as TomlParserError, DecodeError as TomlDecodeError}; use url::ParseError as UrlParseError; use datatype::Event; -use http::auth_client::AuthHandler; +use http::{AuthHandler, ResponseData}; use gateway::Interpret; use ws::Error as WebsocketError; @@ -21,10 +21,11 @@ use ws::Error as WebsocketError; /// System-wide errors that are returned from `Result` type failures. #[derive(Debug)] pub enum Error { - Authorization(String), Client(String), Command(String), FromUtf8(FromUtf8Error), + Http(ResponseData), + HttpAuth(ResponseData), Hyper(HyperError), HyperClient(HyperClientError), Io(IoError), @@ -76,6 +77,7 @@ derive_from!([ JsonEncoderError => JsonEncoder, JsonDecoderError => JsonDecoder, RecvError => Recv, + ResponseData => Http, TomlDecodeError => TomlDecode, UrlParseError => UrlParse, WebsocketError => Websocket @@ -92,9 +94,10 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { Error::Client(ref s) => format!("Http client error: {}", s.clone()), - Error::Authorization(ref s) => format!("Http client authorization error: {}", s.clone()), Error::Command(ref e) => format!("Unknown Command: {}", e.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()), Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), Error::HyperClient(ref e) => format!("Hyper client error: {}", e.clone()), Error::Io(ref e) => format!("IO error: {}", e.clone()), diff --git a/src/datatype/json_rpc.rs b/src/datatype/json_rpc.rs index 3eed9a2..e3a046a 100644 --- a/src/datatype/json_rpc.rs +++ b/src/datatype/json_rpc.rs @@ -1,7 +1,7 @@ use rustc_serialize::{json, Decodable, Encodable}; use time; -use http::{AuthClient, Client}; +use http::{AuthClient, Client, Response}; use super::Url; @@ -32,8 +32,12 @@ impl RpcRequest { let body = json::encode(self).expect("couldn't encode RpcRequest"); let resp_rx = client.post(url, Some(body.into_bytes())); let resp = resp_rx.recv().expect("no RpcRequest response received"); - let data = try!(resp.map_err(|err| format!("{}", err))); - String::from_utf8(data).map_err(|err| format!("{}", err)) + + match resp { + Response::Success(data) => String::from_utf8(data.body).or_else(|err| Err(format!("{}", err))), + Response::Failed(data) => Err(format!("{}", data)), + Response::Error(err) => Err(format!("{}", err)) + } } } diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 6516422..017868c 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -10,7 +10,7 @@ pub mod update_report; pub mod update_request; pub mod url; -pub use self::auth::{AccessToken, Auth, ClientId, ClientSecret, ClientCredentials}; +pub use self::auth::{AccessToken, Auth, ClientCredentials}; pub use self::command::Command; pub use self::config::{AuthConfig, CoreConfig, Config, DBusConfig, DeviceConfig, GatewayConfig, RviConfig}; diff --git a/src/gateway/http.rs b/src/gateway/http.rs index 6ccc2b5..f397630 100644 --- a/src/gateway/http.rs +++ b/src/gateway/http.rs @@ -91,7 +91,7 @@ mod tests { use super::*; use gateway::{Gateway, Interpret}; use datatype::{Command, Event}; - use http::{AuthClient, Client, set_ca_certificates}; + use http::{AuthClient, Client, Response, set_ca_certificates}; #[test] @@ -124,8 +124,12 @@ mod tests { let url = "http://127.0.0.1:8888".parse().unwrap(); let body = json::encode(&cmd).unwrap(); let resp_rx = client.post(url, Some(body.into_bytes())); - let resp = resp_rx.recv().unwrap().unwrap(); - let text = String::from_utf8(resp).unwrap(); + let resp = resp_rx.recv().unwrap(); + let text = match resp { + Response::Success(data) => String::from_utf8(data.body).unwrap(), + Response::Failed(data) => panic!("failed response: {}", data), + Response::Error(err) => panic!("error response: {}", err) + }; assert_eq!(json::decode::(&text).unwrap(), Event::FoundSystemInfo(format!("{}", id))); }); diff --git a/src/http/auth_client.rs b/src/http/auth_client.rs index d9e28cb..2e26464 100644 --- a/src/http/auth_client.rs +++ b/src/http/auth_client.rs @@ -14,7 +14,7 @@ use std::time::Duration; use time; use datatype::{Auth, Error}; -use http::{Client, get_openssl, Request, Response}; +use http::{Client, get_openssl, Request, Response, ResponseData}; /// The `AuthClient` will attach an `Authentication` header to each outgoing @@ -52,58 +52,30 @@ 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 { - auth: self.auth.clone(), - req: req, - timeout: Duration::from_secs(20), - started: None, - written: 0, - response: Vec::new(), - resp_tx: resp_tx.clone(), - }).map_err(|err| resp_tx.send(Err(Error::from(err)))); + auth: self.auth.clone(), + req: req, + timeout: Duration::from_secs(20), + started: None, + written: 0, + resp_code: StatusCode::InternalServerError, + resp_body: Vec::new(), + resp_tx: resp_tx.clone(), + }).map_err(|err| resp_tx.send(Response::Error(Error::from(err)))); } } /// The async handler for outgoing HTTP requests. -// FIXME: uncomment when yocto is at 1.8.0: #[derive(Debug)] +#[derive(Debug)] pub struct AuthHandler { - auth: Auth, - req: Request, - timeout: Duration, - started: Option, - written: usize, - response: Vec, - resp_tx: Sender, -} - -// FIXME: required for building on 1.7.0 only -impl ::std::fmt::Debug for AuthHandler { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "unimplemented") - } -} - -impl AuthHandler { - fn redirect_request(&mut self, resp: HyperResponse) { - match resp.headers().get::() { - Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| { - debug!("redirecting to {}", url); - // drop Authorization Header on redirect - let client = AuthClient::default(); - let resp_rx = client.send_request(Request { - url: url, - method: self.req.method.clone(), - body: mem::replace(&mut self.req.body, None), - }); - match resp_rx.recv().expect("no redirect_request response") { - Ok(data) => self.resp_tx.send(Ok(data)), - Err(err) => self.resp_tx.send(Err(Error::from(err))) - } - }).unwrap_or_else(|err| self.resp_tx.send(Err(Error::from(err)))), - - None => self.resp_tx.send(Err(Error::Client("redirect missing Location header".to_string()))) - } - } + auth: Auth, + req: Request, + timeout: Duration, + started: Option, + written: usize, + resp_code: StatusCode, + resp_body: Vec, + resp_tx: Sender, } /// The `AuthClient` may be used for both HTTP and HTTPS connections. @@ -125,15 +97,12 @@ impl Handler for AuthHandler { headers.set(ContentType(mime_json)); } - Auth::Credentials(_, _) if self.req.body.is_some() => { - panic!("no request body expected for Auth::Credentials"); - } - - Auth::Credentials(ref id, ref secret) => { - headers.set(Authorization(Basic { username: id.0.clone(), - password: Some(secret.0.clone()) })); + Auth::Credentials(ref cred) => { + headers.set(Authorization(Basic { + username: cred.client_id.clone(), + password: Some(cred.client_secret.clone()) + })); headers.set(ContentType(mime_form)); - self.req.body = Some(br#"grant_type=client_credentials"#.to_vec()); } Auth::Token(ref token) => { @@ -173,7 +142,7 @@ impl Handler for AuthHandler { Err(err) => { error!("unable to write request body: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::remove() } } @@ -186,32 +155,34 @@ impl Handler for AuthHandler { let latency = time::precise_time_ns() as f64 - started as f64; debug!("on_response latency: {}ms", (latency / 1e6) as u32); - if resp.status().is_success() { - if let Some(len) = resp.headers().get::() { - if **len > 0 { - return Next::read(); - } - } - self.resp_tx.send(Ok(Vec::new())); - Next::end() - } else if resp.status().is_redirection() { + self.resp_code = *resp.status(); + if resp.status().is_redirection() { self.redirect_request(resp); Next::end() - } else if resp.status() == &StatusCode::Unauthorized - || resp.status() == &StatusCode::Forbidden { - self.resp_tx.send(Err(Error::Authorization(format!("{}", resp.status())))); + } else if let None = resp.headers().get::() { Next::end() } else { - self.resp_tx.send(Err(Error::Client(format!("{}", resp.status())))); - Next::end() + Next::read() } } fn on_response_readable(&mut self, decoder: &mut Decoder) -> Next { - match io::copy(decoder, &mut self.response) { + match io::copy(decoder, &mut self.resp_body) { Ok(0) => { - debug!("on_response_readable bytes read: {}", self.response.len()); - self.resp_tx.send(Ok(mem::replace(&mut self.response, Vec::new()))); + debug!("on_response_readable body size: {}", self.resp_body.len()); + let resp = ResponseData { + code: self.resp_code, + body: mem::replace(&mut self.resp_body, Vec::new()) + }; + + if resp.code == StatusCode::Unauthorized || resp.code == StatusCode::Forbidden { + self.resp_tx.send(Response::Error(Error::HttpAuth(resp))); + } else if resp.code.is_success() { + self.resp_tx.send(Response::Success(resp)); + } else { + self.resp_tx.send(Response::Failed(resp)); + } + Next::end() } @@ -227,7 +198,7 @@ impl Handler for AuthHandler { Err(err) => { error!("unable to read response body: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::end() } } @@ -235,11 +206,31 @@ impl Handler for AuthHandler { fn on_error(&mut self, err: hyper::Error) -> Next { error!("on_error: {}", err); - self.resp_tx.send(Err(Error::from(err))); + self.resp_tx.send(Response::Error(Error::from(err))); Next::remove() } } +impl AuthHandler { + fn redirect_request(&mut self, resp: HyperResponse) { + match resp.headers().get::() { + Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| { + debug!("redirecting to {}", url); + // drop Authorization Header on redirect + let client = AuthClient::default(); + let resp_rx = client.send_request(Request { + url: url, + method: self.req.method.clone(), + body: mem::replace(&mut self.req.body, None), + }); + self.resp_tx.send(resp_rx.recv().expect("no redirect_request response")) + }).unwrap_or_else(|err| self.resp_tx.send(Response::Error(Error::from(err)))), + + None => self.resp_tx.send(Response::Error((Error::Client("redirect missing Location header".to_string())))) + } + } +} + #[cfg(test)] mod tests { @@ -247,7 +238,7 @@ mod tests { use std::path::Path; use super::*; - use http::{Client, set_ca_certificates}; + use http::{Client, Response, set_ca_certificates}; fn get_client() -> AuthClient { @@ -260,8 +251,13 @@ mod tests { let client = get_client(); let url = "http://eu.httpbin.org/bytes/16?seed=123".parse().unwrap(); let resp_rx = client.get(url, None); - let data = resp_rx.recv().unwrap().unwrap(); - assert_eq!(data, vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22]); + let resp = resp_rx.recv().unwrap(); + let expect = vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22]; + match resp { + Response::Success(data) => assert_eq!(data.body, expect), + Response::Failed(data) => panic!("failed response: {}", data), + Response::Error(err) => panic!("error response: {}", err) + }; } #[test] @@ -269,9 +265,13 @@ mod tests { let client = get_client(); let url = "https://eu.httpbin.org/post".parse().unwrap(); let resp_rx = client.post(url, Some(br#"foo"#.to_vec())); - let body = resp_rx.recv().unwrap().unwrap(); - let resp = String::from_utf8(body).unwrap(); - let json = Json::from_str(&resp).unwrap(); + let resp = resp_rx.recv().unwrap(); + let body = match resp { + Response::Success(data) => String::from_utf8(data.body).unwrap(), + Response::Failed(data) => panic!("failed response: {}", data), + Response::Error(err) => panic!("error response: {}", err) + }; + let json = Json::from_str(&body).unwrap(); let obj = json.as_object().unwrap(); let data = obj.get("data").unwrap().as_string().unwrap(); assert_eq!(data, "foo"); diff --git a/src/http/http_client.rs b/src/http/http_client.rs index 492166c..b911b8d 100644 --- a/src/http/http_client.rs +++ b/src/http/http_client.rs @@ -1,5 +1,8 @@ use chan; use chan::{Sender, Receiver}; +use hyper::status::StatusCode; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::str; use datatype::{Error, Method, Url}; @@ -39,5 +42,42 @@ pub struct Request { pub body: Option> } -/// Return the body of an HTTP response on success, or an `Error` otherwise. -pub type Response = Result, Error>; + +/// A Response enumerates between a successful (e.g. 2xx) HTTP response, a failed +/// (e.g. 4xx/5xx) response, or an Error before receiving any response. +#[derive(Debug)] +pub enum Response { + Success(ResponseData), + Failed(ResponseData), + Error(Error) +} + +impl Display for Response { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match *self { + Response::Success(ref data) => write!(f, "{}", data), + Response::Failed(ref data) => write!(f, "{}", data), + Response::Error(ref err) => write!(f, "{}", err), + } + } +} + + +/// Wraps the HTTP Status Code as well as any returned body. +#[derive(Debug)] +pub struct ResponseData { + pub code: StatusCode, + pub body: Vec +} + +impl Display for ResponseData { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self.body.len() { + 0 => write!(f, "Response Code: {}", self.code), + n => match str::from_utf8(&self.body) { + Ok(text) => write!(f, "Response Code: {}, Body:\n{}", self.code, text), + Err(_) => write!(f, "Response Code: {}, Body: {} bytes", self.code, n), + } + } + } +} diff --git a/src/http/mod.rs b/src/http/mod.rs index 5e990a3..11b1e3a 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -5,7 +5,7 @@ pub mod openssl; pub mod test_client; pub use self::auth_client::{AuthClient, AuthHandler}; -pub use self::http_client::{Client, Request, Response}; +pub use self::http_client::{Client, Request, Response, ResponseData}; pub use self::http_server::{Server, ServerHandler}; pub use self::openssl::{get_openssl, set_ca_certificates}; pub use self::test_client::TestClient; diff --git a/src/http/test_client.rs b/src/http/test_client.rs index 7857e0f..1886fdf 100644 --- a/src/http/test_client.rs +++ b/src/http/test_client.rs @@ -1,8 +1,9 @@ use chan::Sender; +use hyper::status::StatusCode; use std::cell::RefCell; use datatype::Error; -use http::{Client, Request, Response}; +use http::{Client, Request, Response, ResponseData}; /// The `TestClient` will return HTTP responses from an existing list of strings. @@ -26,8 +27,11 @@ impl TestClient { impl Client for TestClient { fn chan_request(&self, req: Request, resp_tx: Sender) { match self.responses.borrow_mut().pop() { - Some(body) => resp_tx.send(Ok(body.as_bytes().to_vec())), - None => resp_tx.send(Err(Error::Client(req.url.to_string()))) + Some(body) => resp_tx.send(Response::Success(ResponseData { + code: StatusCode::Ok, + body: body.as_bytes().to_vec() + })), + None => resp_tx.send(Response::Error(Error::Client(req.url.to_string()))) } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 75e6e8b..00ad37c 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -3,9 +3,8 @@ use chan::{Sender, Receiver}; use std; use std::borrow::Cow; -use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config, - Error, Event, Package, UpdateReport, UpdateRequestStatus as Status, - UpdateResultCode}; +use datatype::{AccessToken, Auth, ClientCredentials, Command, Config, Error, Event, + Package, UpdateReport, UpdateRequestStatus as Status, UpdateResultCode}; use gateway::Interpret; use http::{AuthClient, Client}; use oauth2::authenticate; @@ -38,13 +37,12 @@ impl Interpreter for EventInterpreter { info!("Event received: {}", event); match event { Event::Authenticated => { - ctx.send(Command::SendSystemInfo); - if self.package_manager != PackageManager::Off { self.package_manager.installed_packages().map(|packages| { ctx.send(Command::SendInstalledPackages(packages)); }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err)); } + ctx.send(Command::SendSystemInfo); } Event::NotAuthenticated => { @@ -141,24 +139,22 @@ impl<'t> Interpreter for GlobalInterpreter<'t> { etx.send(ev.clone()); response_ev = Some(ev); } - info!("Interpreter finished."); } - Err(Error::Authorization(_)) => { + Err(Error::HttpAuth(_)) => { let ev = Event::NotAuthenticated; etx.send(ev.clone()); response_ev = Some(ev); - error!("Interpreter authentication failed"); } Err(err) => { let ev = Event::Error(format!("{}", err)); etx.send(ev.clone()); response_ev = Some(ev); - error!("Interpreter failed: {}", err); } } + info!("Interpreter finished."); let ev = response_ev.expect("no response event to send back"); interpret.response_tx.map(|tx| tx.lock().unwrap().send(ev)); } @@ -250,8 +246,10 @@ impl<'t> GlobalInterpreter<'t> { match cmd { Command::Authenticate(_) => { let config = self.config.auth.clone().expect("trying to authenticate without auth config"); - self.set_client(Auth::Credentials(ClientId(config.client_id), - ClientSecret(config.client_secret))); + self.set_client(Auth::Credentials(ClientCredentials { + client_id: config.client_id, + client_secret: config.client_secret, + })); let server = config.server.join("/token").expect("couldn't build authentication url"); let token = try!(authenticate(server, self.http_client.as_ref())); self.set_client(Auth::Token(token.clone())); diff --git a/src/oauth2.rs b/src/oauth2.rs index 0c5f152..e34e4c2 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -1,16 +1,19 @@ use rustc_serialize::json; use datatype::{AccessToken, Error, Url}; -use http::Client; +use http::{Client, Response}; /// Authenticate with the specified OAuth2 server to retrieve a new `AccessToken`. pub fn authenticate(server: Url, client: &Client) -> Result { debug!("authenticating at {}", server); - let resp_rx = client.post(server, None); + let resp_rx = client.post(server, Some(br#"grant_type=client_credentials"#.to_vec())); let resp = resp_rx.recv().expect("no authenticate response received"); - let data = try!(resp); - let body = try!(String::from_utf8(data)); + let body = match resp { + Response::Success(data) => try!(String::from_utf8(data.body)), + Response::Failed(data) => return Err(Error::from(data)), + Response::Error(err) => return Err(err) + }; Ok(try!(json::decode(&body))) } diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 99aacbf..beab7dd 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -23,7 +23,7 @@ pub fn installed_packages() -> Result, Error> { }) } -/// Installs a new RPM package. +/// Installs a new RPM package with `rpm -Uvh --force `. pub fn install_package(path: &str) -> Result { let output = try!(Command::new("rpm").arg("-Uvh").arg("--force").arg(path) .output() diff --git a/src/sota.rs b/src/sota.rs index 6c48424..9ea615e 100644 --- a/src/sota.rs +++ b/src/sota.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use datatype::{Config, DeviceReport, DownloadComplete, Error, Package, UpdateReport, UpdateRequest, UpdateRequestId, Url}; -use http::Client; +use http::{Client, Response}; /// Encapsulate the client configuration and HTTP client used for @@ -38,20 +38,31 @@ impl<'c, 'h> Sota<'c, 'h> { /// Query the Core server for any pending or in-flight package updates. pub fn get_update_requests(&mut self) -> Result, Error> { - let _ = self.client.get(self.endpoint(""), None); // FIXME(PRO-1352): single endpoint let resp_rx = self.client.get(self.endpoint("/queued"), None); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get new updates".to_string()))); - let text = try!(String::from_utf8(try!(resp))); + let data = match resp { + Response::Success(data) => data, + Response::Failed(data) => return Err(Error::from(data)), + Response::Error(err) => return Err(err) + }; + + let text = try!(String::from_utf8(data.body)); Ok(try!(json::decode::>(&text))) } /// Download a specific update from the Core server. pub fn download_update(&mut self, id: UpdateRequestId) -> Result { - let resp_rx = self.client.get(self.endpoint(&format!("/{}/download", id)), None); - let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string()))); + let resp_rx = self.client.get(self.endpoint(&format!("/{}/download", id)), None); + let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string()))); + let data = match resp { + Response::Success(data) => data, + Response::Failed(data) => return Err(Error::from(data)), + Response::Error(err) => return Err(err) + }; + let path = try!(self.package_path(id.clone())); let mut file = try!(File::create(&path)); - let _ = io::copy(&mut &*try!(resp), &mut file); + let _ = io::copy(&mut &*data.body, &mut file); Ok(DownloadComplete { update_id: id, update_image: path.to_string(), @@ -75,8 +86,12 @@ impl<'c, 'h> Sota<'c, 'h> { let body = try!(json::encode(packages)); let resp_rx = self.client.put(self.endpoint("/installed"), Some(body.into_bytes())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send installed packages".to_string()))); - let _ = try!(resp); - Ok(()) + + match resp { + Response::Success(_) => Ok(()), + Response::Failed(data) => Err(Error::from(data)), + Response::Error(err) => Err(err) + } } /// Send the outcome of a package update to the Core server. @@ -86,16 +101,24 @@ impl<'c, 'h> Sota<'c, 'h> { let url = self.endpoint(&format!("/{}", report.device)); let resp_rx = self.client.post(url, Some(body.into_bytes())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send update report".to_string()))); - let _ = try!(resp); - Ok(()) + + match resp { + Response::Success(_) => Ok(()), + Response::Failed(data) => Err(Error::from(data)), + Response::Error(err) => Err(err) + } } /// Send system information from the device to the Core server. pub fn send_system_info(&mut self, body: &str) -> Result<(), Error> { let resp_rx = self.client.put(self.endpoint("/system_info"), Some(body.as_bytes().to_vec())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send system info".to_string()))); - let _ = try!(resp); - Ok(()) + + match resp { + Response::Success(_) => Ok(()), + Response::Failed(data) => Err(Error::from(data)), + Response::Error(err) => Err(err) + } } } @@ -125,7 +148,7 @@ mod tests { let json = format!("[{}]", json::encode(&pending_update).unwrap()); let mut sota = Sota { config: &Config::default(), - client: &mut TestClient::from(vec![json.to_string(), "[]".to_string()]), + client: &mut TestClient::from(vec![json.to_string()]), }; let updates: Vec = sota.get_update_requests().unwrap(); -- cgit v1.2.1 From f15521b7c55eb4a1f9b4c54ec6b9c16d50b7743b Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 30 Sep 2016 10:23:36 +0200 Subject: Print reason for authorization failure --- src/interpreter.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 00ad37c..1d73e1d 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -141,7 +141,8 @@ impl<'t> Interpreter for GlobalInterpreter<'t> { } } - Err(Error::HttpAuth(_)) => { + Err(Error::HttpAuth(resp)) => { + error!("HTTP authorization failed: {}", resp); let ev = Event::NotAuthenticated; etx.send(ev.clone()); response_ev = Some(ev); -- cgit v1.2.1 From 08252de4a101e189af499ae140f62eaaabb9b657 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 30 Sep 2016 15:35:38 +0200 Subject: Fix re-authentication when token expires --- src/datatype/event.rs | 2 ++ src/interpreter.rs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 2d6f895..93ed4ec 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -14,6 +14,8 @@ pub enum Event { Authenticated, /// An operation failed because we are not currently authenticated. NotAuthenticated, + /// Nothing was done as we are already authenticated. + AlreadyAuthenticated, /// A notification from Core of pending or in-flight updates. UpdatesReceived(Vec), diff --git a/src/interpreter.rs b/src/interpreter.rs index 1d73e1d..6908028 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -143,6 +143,7 @@ impl<'t> Interpreter for GlobalInterpreter<'t> { Err(Error::HttpAuth(resp)) => { error!("HTTP authorization failed: {}", resp); + self.token = None; let ev = Event::NotAuthenticated; etx.send(ev.clone()); response_ev = Some(ev); @@ -167,7 +168,7 @@ impl<'t> GlobalInterpreter<'t> { // always send at least one Event response match cmd { - Command::Authenticate(_) => etx.send(Event::Authenticated), + Command::Authenticate(_) => etx.send(Event::AlreadyAuthenticated), Command::GetUpdateRequests => { let mut updates = try!(sota.get_update_requests()); -- cgit v1.2.1 From d2c2e2105078bd3e2487138019dd18a7dca5d78f Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 30 Sep 2016 16:48:48 +0200 Subject: Fix AlreadyAuthenticated test --- src/interpreter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 6908028..adac43a 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -321,7 +321,7 @@ mod tests { let (ctx, erx) = new_interpreter(replies, pkg_mgr); ctx.send(Command::Authenticate(None)); - assert_rx(erx, &[Event::Authenticated]); + assert_rx(erx, &[Event::AlreadyAuthenticated]); } #[test] -- cgit v1.2.1 From f565bb104ed746f81cb5d6d2d2a51a89b3529faa Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 30 Sep 2016 17:45:47 +0200 Subject: Send HTTP Response when there is no ContentLength --- src/http/auth_client.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/http/auth_client.rs b/src/http/auth_client.rs index 2e26464..7d10e5b 100644 --- a/src/http/auth_client.rs +++ b/src/http/auth_client.rs @@ -155,13 +155,14 @@ impl Handler for AuthHandler { let latency = time::precise_time_ns() as f64 - started as f64; debug!("on_response latency: {}ms", (latency / 1e6) as u32); - self.resp_code = *resp.status(); if resp.status().is_redirection() { self.redirect_request(resp); Next::end() } else if let None = resp.headers().get::() { + self.send_response(ResponseData { code: *resp.status(), body: Vec::new() }); Next::end() } else { + self.resp_code = *resp.status(); Next::read() } } @@ -170,19 +171,9 @@ impl Handler for AuthHandler { match io::copy(decoder, &mut self.resp_body) { Ok(0) => { debug!("on_response_readable body size: {}", self.resp_body.len()); - let resp = ResponseData { - code: self.resp_code, - body: mem::replace(&mut self.resp_body, Vec::new()) - }; - - if resp.code == StatusCode::Unauthorized || resp.code == StatusCode::Forbidden { - self.resp_tx.send(Response::Error(Error::HttpAuth(resp))); - } else if resp.code.is_success() { - self.resp_tx.send(Response::Success(resp)); - } else { - self.resp_tx.send(Response::Failed(resp)); - } - + let code = self.resp_code.clone(); + let body = mem::replace(&mut self.resp_body, Vec::new()); + self.send_response(ResponseData { code: code, body: body }); Next::end() } @@ -212,6 +203,16 @@ impl Handler for AuthHandler { } impl AuthHandler { + fn send_response(&mut self, resp: ResponseData) { + if resp.code == StatusCode::Unauthorized || resp.code == StatusCode::Forbidden { + self.resp_tx.send(Response::Error(Error::HttpAuth(resp))); + } else if resp.code.is_success() { + self.resp_tx.send(Response::Success(resp)); + } else { + self.resp_tx.send(Response::Failed(resp)); + } + } + fn redirect_request(&mut self, resp: HyperResponse) { match resp.headers().get::() { Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| { -- cgit v1.2.1 From b27d04354805f54f5772802bd89f3e79f43e5641 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 5 Oct 2016 11:25:25 +0200 Subject: Block polling until all interpreters have finished --- Cargo.lock | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/interpreter.rs | 66 ++++++++++++++++++++++++++++-------------------- src/main.rs | 29 ++++++++++++--------- 3 files changed, 130 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4bcf69c..73fb096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,3 +593,77 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[metadata] +"checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" +"checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da" +"checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" +"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" +"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" +"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" +"checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" +"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" +"checksum chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "82b22acfef7960fd8f829bc50749273be637cbd76b9d4cc20497666cc3a33329" +"checksum chan-signal 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "afbba6202dc1d10ff08c3b04e00e4d2d6cf5effee56cd9fee92928be6692379a" +"checksum cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0e3d6405328b6edb412158b3b7710e2634e23f3614b9bb1c412df7952489a626" +"checksum crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "fb974f835e90390c5f9dfac00f05b06dc117299f5ea4e85fbc7bb443af4911cc" +"checksum dbus 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aefec6d9031bc53358eb822549ca946f50c8618a85bfe8afa52816c3a978eecf" +"checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" +"checksum gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "3da3a2cbaeb01363c8e3704fd9fd0eb2ceb17c6f27abd4c1ef040fb57d20dc79" +"checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" +"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" +"checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" +"checksum hyper 0.9.4 (git+https://github.com/hyperium/hyper)" = "" +"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +"checksum lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" +"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" +"checksum libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "97def9dc7ce1d8e153e693e3a33020bc69972181adb2f871e87e888876feae49" +"checksum libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cbc058951ab6a3ef35ca16462d7642c4867e6403520811f28537a4e2f2db3e71" +"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" +"checksum matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e" +"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum mime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf93a79c700c9df8227ec6a4f0f27a8948373c079312bac24549d944cef85f64" +"checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" +"checksum miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4e93d633d34b8ff65a24566d67d49703e7a5c7ac2844d6139a9fc441a799e89a" +"checksum net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "6a816012ca11cb47009693c1e0c6130e26d39e4d97ee2a13c50e868ec83e3204" +"checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" +"checksum nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d1b06a35295796400a1db7382054f93713bf3924e7c268af94c5357b9fbf4cb6" +"checksum openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "81ff0208f23e726e747375d34e40c93d037a5b504de7305117dfe5ad72516d2d" +"checksum openssl-sys 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "618753feb53784e3ccb131811ed0b02f80640da89fb33b165d69146564b02085" +"checksum openssl-sys-extras 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "01838027da8e31ab4d3530fc5d6752bfd92dcc8e0ae070633e69f2b020bd0f36" +"checksum openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed86cce894f6b0ed4572e21eb34026f1dc8869cb9ee3869029131bc8c3feb2d" +"checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa" +"checksum pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "61c9231d31aea845007443d62fcbb58bb6949ab9c18081ee1e09920e0cf1118b" +"checksum quick-error 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7ac990ab4e038dd8481a5e3fd00641067fcfc674ad663f3222752ed5284e05d4" +"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" +"checksum regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)" = "e58a1b7d2bfecc0746e8587c30a53d01ea7bc0e98fac54e5aaa375b94338a0cc" +"checksum regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "baa04823ba7be7ed0bed3d0704c7b923019d9c4e4931c5af2804c7c7a0e3d00b" +"checksum rotor 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07a6d6ac669b5c7623d7270f657e7fe60bd1d07f37d99fd5b9ea38c273834c14" +"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" +"checksum sha1 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a307a40d5834140e4213a6952483b84e9ad53bdcab918b7335a6e305e505a53c" +"checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e" +"checksum spmc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "93bdab61c1a413e591c4d17388ffa859eaff2df27f1e13a5ec8b716700605adf" +"checksum tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0b62933a3f96cd559700662c34f8bab881d9e3540289fb4f368419c7f13a5aa9" +"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +"checksum thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55dd963dbaeadc08aa7266bf7f91c3154a7805e32bb94b820b769d2ef3b4744d" +"checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" +"checksum toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "0590d72182e50e879c4da3b11c6488dae18fccb1ae0c7a3eda18e16795844796" +"checksum traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "07eaeb7689bb7fca7ce15628319635758eda769fed481ecfe6686ddef2600616" +"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" +"checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" +"checksum unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c1f7ceb96afdfeedee42bade65a0d585a6a0106f681b6749c8ff4daa8df30b3f" +"checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" +"checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" +"checksum url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8ab4ca6f0107350f41a59a51cb0e71a04d905bc6a29181d2cb42fa4f040c65c9" +"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" +"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3969e500d618a5e974917ddefd0ba152e4bcaae5eb5d9b8c1fbc008e9e28c24e" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum ws 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f888214c823b739f072b6d781df41824bd5e162a53be27d0079449d12ab0c9" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/src/interpreter.rs b/src/interpreter.rs index adac43a..4793b32 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,7 +1,9 @@ use chan; -use chan::{Sender, Receiver}; -use std; +use chan::{Sender, Receiver, WaitGroup}; +use std::{process, thread}; use std::borrow::Cow; +use std::time::Duration; +use time; use datatype::{AccessToken, Auth, ClientCredentials, Command, Config, Error, Event, Package, UpdateReport, UpdateRequestStatus as Status, UpdateResultCode}; @@ -18,9 +20,20 @@ use sota::Sota; pub trait Interpreter { fn interpret(&mut self, input: I, otx: &Sender); - fn run(&mut self, irx: Receiver, otx: Sender) { + fn run(&mut self, irx: Receiver, otx: Sender, wg: WaitGroup) { + let cooldown = Duration::from_millis(100); + loop { - self.interpret(irx.recv().expect("interpreter sender closed"), &otx); + let input = irx.recv().expect("interpreter sender closed"); + let started = time::precise_time_ns(); + + wg.add(1); + debug!("interpreter starting: {}", started); + self.interpret(input, &otx); + + thread::sleep(cooldown); // let any further work commence + debug!("interpreter stopping: {}", started); + wg.done(); } } } @@ -29,16 +42,17 @@ 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 package_manager: PackageManager + pub pacman: PackageManager } impl Interpreter for EventInterpreter { fn interpret(&mut self, event: Event, ctx: &Sender) { - info!("Event received: {}", event); + info!("EventInterpreter received: {}", event); + match event { Event::Authenticated => { - if self.package_manager != PackageManager::Off { - self.package_manager.installed_packages().map(|packages| { + if self.pacman != PackageManager::Off { + self.pacman.installed_packages().map(|packages| { ctx.send(Command::SendInstalledPackages(packages)); }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err)); } @@ -52,17 +66,16 @@ impl Interpreter for EventInterpreter { Event::UpdatesReceived(requests) => { for request in requests { - match (request.status, &self.package_manager) { - (Status::Pending, _) => ctx.send(Command::StartDownload(request.requestId.clone())), - - (Status::InFlight, &PackageManager::Off) => (), - - (Status::InFlight, _) => { - if self.package_manager.is_installed(&request.packageId) { - ctx.send(Command::SendUpdateReport(UpdateReport::single( - request.requestId.clone(), UpdateResultCode::OK, "".to_string()))); + let id = request.requestId.clone(); + match request.status { + Status::Pending => ctx.send(Command::StartDownload(id)), + + Status::InFlight if self.pacman != PackageManager::Off => { + if self.pacman.is_installed(&request.packageId) { + let report = UpdateReport::single(id, UpdateResultCode::OK, "".to_string()); + ctx.send(Command::SendUpdateReport(report)); } else { - ctx.send(Command::StartDownload(request.requestId.clone())); + ctx.send(Command::StartDownload(id)); } } @@ -72,7 +85,7 @@ impl Interpreter for EventInterpreter { } Event::DownloadComplete(dl) => { - if self.package_manager != PackageManager::Off { + if self.pacman != PackageManager::Off { ctx.send(Command::StartInstall(dl.update_id.clone())); } } @@ -87,8 +100,8 @@ impl Interpreter for EventInterpreter { } Event::UpdateReportSent => { - if self.package_manager != PackageManager::Off { - self.package_manager.installed_packages().map(|packages| { + if self.pacman != PackageManager::Off { + self.pacman.installed_packages().map(|packages| { ctx.send(Command::SendInstalledPackages(packages)); }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err)); } @@ -106,7 +119,7 @@ pub struct CommandInterpreter; impl Interpreter for CommandInterpreter { fn interpret(&mut self, cmd: Command, itx: &Sender) { - info!("Command received: {}", cmd); + info!("CommandInterpreter received: {}", cmd); itx.send(Interpret { command: cmd, response_tx: None }); } } @@ -124,7 +137,7 @@ pub struct GlobalInterpreter<'t> { impl<'t> Interpreter for GlobalInterpreter<'t> { fn interpret(&mut self, interpret: Interpret, etx: &Sender) { - info!("Interpreter started: {}", interpret.command); + info!("GlobalInterpreter received: {}", interpret.command); let (multi_tx, multi_rx) = chan::async::(); let outcome = match (self.token.as_ref(), self.config.auth.is_none()) { @@ -156,7 +169,6 @@ impl<'t> Interpreter for GlobalInterpreter<'t> { } } - info!("Interpreter finished."); let ev = response_ev.expect("no response event to send back"); interpret.response_tx.map(|tx| tx.lock().unwrap().send(ev)); } @@ -238,7 +250,7 @@ impl<'t> GlobalInterpreter<'t> { .map_err(|report| etx.send(Event::InstallFailed(report))); } - Command::Shutdown => std::process::exit(0), + Command::Shutdown => process::exit(0), } Ok(()) @@ -259,7 +271,7 @@ impl<'t> GlobalInterpreter<'t> { etx.send(Event::Authenticated); } - Command::Shutdown => std::process::exit(0), + Command::Shutdown => process::exit(0), _ => etx.send(Event::NotAuthenticated) } @@ -278,7 +290,7 @@ impl<'t> GlobalInterpreter<'t> { #[cfg(test)] mod tests { use chan; - use chan::{Sender, Receiver}; + use chan::{Sender, Receiver, WaitGroup}; use std::thread; use super::*; diff --git a/src/main.rs b/src/main.rs index 031734c..2caa411 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,12 +9,12 @@ extern crate rustc_serialize; #[macro_use] extern crate sota; extern crate time; -use chan::{Sender, Receiver}; +use chan::{Sender, Receiver, WaitGroup}; use chan_signal::Signal; use env_logger::LogBuilder; use getopts::Options; use log::{LogLevelFilter, LogRecord}; -use std::env; +use std::{env, thread}; use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, Mutex}; @@ -45,16 +45,17 @@ fn start_signal_handler(signals: Receiver) { } } -fn start_update_poller(interval: u64, itx: Sender) { +fn start_update_poller(interval: u64, itx: Sender, wg: WaitGroup) { let (etx, erx) = chan::async::(); - let tick = chan::tick(Duration::from_secs(interval)); + let wait = Duration::from_secs(interval); loop { - let _ = tick.recv(); + 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()))) }); - let _ = erx.recv(); + let _ = erx.recv(); // then wait for the response } } @@ -67,8 +68,9 @@ fn main() { 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| { @@ -78,7 +80,8 @@ fn main() { let poll_tick = config.device.polling_interval; let poll_itx = itx.clone(); - scope.spawn(move || start_update_poller(poll_tick, poll_itx)); + let poll_wg = wg.clone(); + scope.spawn(move || start_update_poller(poll_tick, poll_itx, poll_wg)); if config.gateway.console { let cons_itx = itx.clone(); @@ -133,19 +136,21 @@ fn main() { let event_sub = broadcast.subscribe(); let event_ctx = ctx.clone(); let event_mgr = config.device.package_manager.clone(); + let event_wg = wg.clone(); scope.spawn(move || EventInterpreter { - package_manager: event_mgr - }.run(event_sub, event_ctx)); + pacman: event_mgr + }.run(event_sub, event_ctx, event_wg)); let cmd_itx = itx.clone(); - scope.spawn(move || CommandInterpreter.run(crx, cmd_itx)); + let cmd_wg = wg.clone(); + scope.spawn(move || CommandInterpreter.run(crx, cmd_itx, cmd_wg)); scope.spawn(move || GlobalInterpreter { config: config, token: None, http_client: Box::new(AuthClient::default()), rvi: rvi, - }.run(irx, etx)); + }.run(irx, etx, wg)); scope.spawn(move || broadcast.start()); }); -- cgit v1.2.1 From 0eeef4917d84e6da1fa456225b8750badd11b5df Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 14 Oct 2016 17:20:45 +0200 Subject: Run sync command after installing an RPM package --- src/interpreter.rs | 4 +++- src/package_manager/rpm.rs | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 4793b32..f122fc2 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -70,6 +70,7 @@ impl Interpreter for EventInterpreter { match request.status { Status::Pending => ctx.send(Command::StartDownload(id)), + /* FIXME(PRO-1654): race condition due to prior in-progress operations Status::InFlight if self.pacman != PackageManager::Off => { if self.pacman.is_installed(&request.packageId) { let report = UpdateReport::single(id, UpdateResultCode::OK, "".to_string()); @@ -78,6 +79,7 @@ impl Interpreter for EventInterpreter { ctx.send(Command::StartDownload(id)); } } + */ _ => () } @@ -290,7 +292,7 @@ impl<'t> GlobalInterpreter<'t> { #[cfg(test)] mod tests { use chan; - use chan::{Sender, Receiver, WaitGroup}; + use chan::{Sender, Receiver}; use std::thread; use super::*; diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index beab7dd..bdbfd31 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -27,13 +27,18 @@ pub fn installed_packages() -> Result, Error> { pub fn install_package(path: &str) -> Result { let output = try!(Command::new("rpm").arg("-Uvh").arg("--force").arg(path) .output() - .map_err(|e| (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)))); + .map_err(|err| (UpdateResultCode::GENERAL_ERROR, format!("{:?}", err)))); let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); match output.status.code() { - Some(0) => Ok((UpdateResultCode::OK, stdout)), + Some(0) => { + let _ = Command::new("sync").status() + .map_err(|err| error!("couldn't run 'sync': {}", err)); + Ok((UpdateResultCode::OK, stdout)) + } + _ => { let out = format!("stdout: {}\nstderr: {}", stdout, stderr); if (&stderr).contains("already installed") { -- cgit v1.2.1 From c6d3bb27c6e794f124259b8f7e749df1687f28b0 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 14 Oct 2016 18:05:44 +0200 Subject: Re-enable InFlight retries --- src/interpreter.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index f122fc2..7f401de 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -70,7 +70,6 @@ impl Interpreter for EventInterpreter { match request.status { Status::Pending => ctx.send(Command::StartDownload(id)), - /* FIXME(PRO-1654): race condition due to prior in-progress operations Status::InFlight if self.pacman != PackageManager::Off => { if self.pacman.is_installed(&request.packageId) { let report = UpdateReport::single(id, UpdateResultCode::OK, "".to_string()); @@ -79,7 +78,6 @@ impl Interpreter for EventInterpreter { ctx.send(Command::StartDownload(id)); } } - */ _ => () } -- cgit v1.2.1 From e50ec3ba87a744e3b581311efede49e21e9cb018 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 19 Oct 2016 16:15:22 +0200 Subject: Update dependencies (except Hyper) --- Cargo.lock | 243 +++++++++++++++++++++++++++++++------------------------------ Cargo.toml | 20 ++--- 2 files changed, 133 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73fb096..c6ad549 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,29 +3,29 @@ name = "sota_client" version = "0.1.0" dependencies = [ "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "chan-signal 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", - "dbus 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "chan-signal 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "dbus 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.9.4 (git+https://github.com/hyperium/hyper)", + "hyper 0.9.4 (git+https://github.com/hyperium/hyper?rev=006f66f34a9c3c2a655118aab2186198deeb143b)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ws 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ws 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "aho-corasick" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -51,12 +51,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "0.5.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -79,13 +74,13 @@ dependencies = [ [[package]] name = "chan-signal" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -93,38 +88,38 @@ name = "cookie" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "dbus" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "env_logger" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gcc" -version = "0.3.28" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -132,7 +127,7 @@ name = "gdi32-sys" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -149,14 +144,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hyper" version = "0.9.4" -source = "git+https://github.com/hyperium/hyper#006f66f34a9c3c2a655118aab2186198deeb143b" +source = "git+https://github.com/hyperium/hyper?rev=006f66f34a9c3c2a655118aab2186198deeb143b" dependencies = [ "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rotor 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -165,7 +160,7 @@ dependencies = [ "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -174,7 +169,7 @@ name = "idna" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -184,7 +179,7 @@ name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -205,7 +200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -223,7 +218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "matches" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -231,12 +226,12 @@ name = "memchr" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mime" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -248,36 +243,36 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "miow" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "net2" -version = "0.2.23" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -287,34 +282,45 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "nom" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys-extras 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "openssl-sys" -version = "0.7.13" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -322,12 +328,12 @@ dependencies = [ [[package]] name = "openssl-sys-extras" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -335,7 +341,7 @@ name = "openssl-verify" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -348,7 +354,7 @@ name = "pnacl-build-helper" version = "1.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -361,24 +367,24 @@ name = "rand" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "0.1.71" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.3.3" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -398,8 +404,8 @@ name = "rust-crypto" version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -425,11 +431,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "sha1" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "slab" @@ -443,7 +446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "tempdir" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -455,12 +458,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thread_local" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -472,13 +475,13 @@ version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "toml" -version = "0.1.30" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -507,7 +510,7 @@ name = "unicode-bidi" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -521,16 +524,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "url" -version = "1.1.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -538,7 +541,7 @@ name = "user32-sys" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -552,7 +555,7 @@ name = "vecio" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -563,7 +566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -573,15 +576,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "sha1 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -589,81 +592,81 @@ name = "ws2_32-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] -"checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" +"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" "checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da" "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" -"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" -"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "82b22acfef7960fd8f829bc50749273be637cbd76b9d4cc20497666cc3a33329" -"checksum chan-signal 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "afbba6202dc1d10ff08c3b04e00e4d2d6cf5effee56cd9fee92928be6692379a" +"checksum chan-signal 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "365122ab60a9dc6240b48e39d011b4389c3853093d98bf586edd2b79bfb4fbfa" "checksum cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0e3d6405328b6edb412158b3b7710e2634e23f3614b9bb1c412df7952489a626" -"checksum crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "fb974f835e90390c5f9dfac00f05b06dc117299f5ea4e85fbc7bb443af4911cc" -"checksum dbus 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aefec6d9031bc53358eb822549ca946f50c8618a85bfe8afa52816c3a978eecf" -"checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" -"checksum gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "3da3a2cbaeb01363c8e3704fd9fd0eb2ceb17c6f27abd4c1ef040fb57d20dc79" +"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97" +"checksum dbus 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "58ec7b4cac6f79f36af1cd9cfdb9b935fc5a4e899f494ee03a3a6165f7d10b4b" +"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" +"checksum gcc 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "41337e9dc80ebadf36b4f252bf7440f61bcf34f99caa170e50dcd0f9a0cdb5d8" "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" "checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" -"checksum hyper 0.9.4 (git+https://github.com/hyperium/hyper)" = "" +"checksum hyper 0.9.4 (git+https://github.com/hyperium/hyper?rev=006f66f34a9c3c2a655118aab2186198deeb143b)" = "" "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" "checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" -"checksum libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "97def9dc7ce1d8e153e693e3a33020bc69972181adb2f871e87e888876feae49" +"checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8" "checksum libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cbc058951ab6a3ef35ca16462d7642c4867e6403520811f28537a4e2f2db3e71" "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" -"checksum matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e" +"checksum matches 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc3ad8109fa4b522f9b0cd81440422781f564aaf8c195de6b9d6642177ad0dd" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -"checksum mime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf93a79c700c9df8227ec6a4f0f27a8948373c079312bac24549d944cef85f64" +"checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66" "checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" -"checksum miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4e93d633d34b8ff65a24566d67d49703e7a5c7ac2844d6139a9fc441a799e89a" -"checksum net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "6a816012ca11cb47009693c1e0c6130e26d39e4d97ee2a13c50e868ec83e3204" +"checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a" +"checksum net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "5edf9cb6be97212423aed9413dd4729d62b370b5e1c571750e882cebbbc1e3e2" "checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" -"checksum nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d1b06a35295796400a1db7382054f93713bf3924e7c268af94c5357b9fbf4cb6" -"checksum openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "81ff0208f23e726e747375d34e40c93d037a5b504de7305117dfe5ad72516d2d" -"checksum openssl-sys 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "618753feb53784e3ccb131811ed0b02f80640da89fb33b165d69146564b02085" -"checksum openssl-sys-extras 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "01838027da8e31ab4d3530fc5d6752bfd92dcc8e0ae070633e69f2b020bd0f36" +"checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" +"checksum openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c4117b6244aac42ed0150a6019b4d953d28247c5dd6ae6f46ae469b5f2318733" +"checksum openssl 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b11754cb6c81bb9e62faaf0eb6d94dde2aab0928c04db5078b74242880f35eb1" +"checksum openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "89c47ee94c352eea9ddaf8e364be7f978a3bb6d66d73176572484238dd5a5c3f" +"checksum openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "11c5e1dba7d3d03d80f045bf0d60111dc69213b67651e7c889527a3badabb9fa" "checksum openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed86cce894f6b0ed4572e21eb34026f1dc8869cb9ee3869029131bc8c3feb2d" "checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa" "checksum pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "61c9231d31aea845007443d62fcbb58bb6949ab9c18081ee1e09920e0cf1118b" "checksum quick-error 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7ac990ab4e038dd8481a5e3fd00641067fcfc674ad663f3222752ed5284e05d4" "checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" -"checksum regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)" = "e58a1b7d2bfecc0746e8587c30a53d01ea7bc0e98fac54e5aaa375b94338a0cc" -"checksum regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "baa04823ba7be7ed0bed3d0704c7b923019d9c4e4931c5af2804c7c7a0e3d00b" +"checksum regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)" = "64b03446c466d35b42f2a8b203c8e03ed8b91c0f17b56e1f84f7210a257aa665" +"checksum regex-syntax 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "48f0573bcee95a48da786f8823465b5f2a1fae288a55407aca991e5b3e0eae11" "checksum rotor 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07a6d6ac669b5c7623d7270f657e7fe60bd1d07f37d99fd5b9ea38c273834c14" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" -"checksum sha1 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a307a40d5834140e4213a6952483b84e9ad53bdcab918b7335a6e305e505a53c" +"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" "checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e" "checksum spmc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "93bdab61c1a413e591c4d17388ffa859eaff2df27f1e13a5ec8b716700605adf" -"checksum tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0b62933a3f96cd559700662c34f8bab881d9e3540289fb4f368419c7f13a5aa9" +"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" -"checksum thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55dd963dbaeadc08aa7266bf7f91c3154a7805e32bb94b820b769d2ef3b4744d" +"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" "checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" -"checksum toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "0590d72182e50e879c4da3b11c6488dae18fccb1ae0c7a3eda18e16795844796" +"checksum toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" "checksum traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "07eaeb7689bb7fca7ce15628319635758eda769fed481ecfe6686ddef2600616" "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" "checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" "checksum unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c1f7ceb96afdfeedee42bade65a0d585a6a0106f681b6749c8ff4daa8df30b3f" "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" "checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" -"checksum url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8ab4ca6f0107350f41a59a51cb0e71a04d905bc6a29181d2cb42fa4f040c65c9" +"checksum url 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8527c62d9869a08325c38272b3f85668df22a65890c61a639d233dc0ed0b23a2" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" "checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3969e500d618a5e974917ddefd0ba152e4bcaae5eb5d9b8c1fbc008e9e28c24e" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum ws 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f888214c823b739f072b6d781df41824bd5e162a53be27d0079449d12ab0c9" +"checksum ws 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c47e9ca2f5c47d27f731b1bb9bb50cc05f9886bb84fbd52afa0ff97f4f61b06" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/Cargo.toml b/Cargo.toml index cbc8b2a..8156c48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,21 +16,21 @@ doc = false [dependencies] chan = "0.1.18" -chan-signal = "0.1.6" -crossbeam = "0.2.9" -dbus = "0.3.3" -env_logger = "0.3.3" +chan-signal = "0.1.7" +crossbeam = "0.2.10" +dbus = "0.4.1" +env_logger = "0.3.5" getopts = "0.2.14" -hyper = { git = "https://github.com/hyperium/hyper" } +hyper = { git = "https://github.com/hyperium/hyper", rev = "006f66f34a9c3c2a655118aab2186198deeb143b" } lazy_static = "0.2.1" log = "0.3.6" -nom = "1.2.3" -openssl = "0.7.13" +nom = "1.2.4" +openssl = "0.8.3" rand = "0.3.14" rust-crypto = "0.2.36" rustc-serialize = "0.3.19" time = "0.1.35" -toml = "0.1.30" +toml = "0.2.1" unix_socket = "0.5.0" -url = "1.1.1" -ws = "0.5.0" +url = "1.2.1" +ws = "0.5.3" -- cgit v1.2.1 From 41db1050631cfe0aaca8922ec4a58a0f2109ac5d Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 19 Oct 2016 16:15:31 +0200 Subject: Fix generated SOTA.toml files --- README.md | 3 +-- run/run.sh | 30 ++++++++++++++++-------------- run/sota.toml.env | 6 +++--- src/datatype/config.rs | 14 +++++++------- src/datatype/system_info.rs | 21 ++++++++------------- src/gateway/dbus.rs | 23 ++++++++++++----------- src/gateway/websocket.rs | 38 +++++++++++++++----------------------- src/interpreter.rs | 20 ++++++++++++-------- src/main.rs | 30 ++++++++++++++++-------------- src/sota.rs | 3 ++- tests/sota.toml | 6 +++--- 11 files changed, 95 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 31cb67c..673e199 100644 --- a/README.md +++ b/README.md @@ -94,11 +94,10 @@ See `tests/genivi.sota.toml` for a sample config. See full documentation for det Now you can run the `sota_client`: make client - ./run/sota_client --config tests/genivi.sota.toml + RUST_LOG=debug ./run/sota_client --config tests/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. 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. - diff --git a/run/run.sh b/run/run.sh index 4ac4d05..3050f12 100755 --- a/run/run.sh +++ b/run/run.sh @@ -25,25 +25,27 @@ if [[ -z "${DEVICE_UUID}" ]]; then fi export DEVICE_UUID -# create or use existing device credentials -if [[ -z "${AUTH_CLIENT_ID}" ]]; then - CREDENTIALS=$(http post "${AUTH_SERVER}/clients" \ - client_name="${DEVICE_VIN}" \ - grant_types:='["client_credentials"]' \ - --check-status --print=b) - AUTH_CLIENT_ID=$(echo "${CREDENTIALS}" | jq -r .client_id) - AUTH_CLIENT_SECRET=$(echo "${CREDENTIALS}" | jq -r .client_secret) -fi -export AUTH_CLIENT_ID -export AUTH_CLIENT_SECRET +[[ "${AUTH_SECTION}" = true ]] && { + # create or use existing device credentials + if [[ -z "${AUTH_CLIENT_ID}" ]]; then + CREDENTIALS=$(http post "${AUTH_SERVER}/clients" \ + client_name="${DEVICE_VIN}" \ + grant_types:='["client_credentials"]' \ + --check-status --print=b) + AUTH_CLIENT_ID=$(echo "${CREDENTIALS}" | jq -r .client_id) + AUTH_CLIENT_SECRET=$(echo "${CREDENTIALS}" | jq -r .client_secret) + fi + export AUTH_CLIENT_ID + export AUTH_CLIENT_SECRET +} || { + # remove [auth] section + sed -i '/\[core\]/,$!d' "${TEMPLATE_PATH}" +} # generate sota.toml config echo "---START CONFIG---" envsubst < "${TEMPLATE_PATH}" | tee "${OUTPUT_PATH}" echo "---END CONFIG---" - -# optionally remove auth section and/or quit -[[ "${AUTH_SECTION}" = false ]] && sed -i '/\[core\]/,$!d' "${OUTPUT_PATH}" [[ "${CONFIG_ONLY}" = true ]] && exit 0 # set up dbus diff --git a/run/sota.toml.env b/run/sota.toml.env index ecf15b5..cd7d460 100644 --- a/run/sota.toml.env +++ b/run/sota.toml.env @@ -14,7 +14,7 @@ DEVICE_PACKAGES_DIR=/tmp/ DEVICE_PACKAGE_MANAGER=deb DEVICE_POLLING_INTERVAL=10 DEVICE_CERTIFICATES_PATH=/etc/sota_certificates -DEVICE_SYSTEM_INFO=system_info.sh +DEVICE_SYSTEM_INFO=./system_info.sh GATEWAY_CONSOLE=false GATEWAY_DBUS=false @@ -23,8 +23,8 @@ GATEWAY_RVI=false GATEWAY_SOCKET=false GATEWAY_WEBSOCKET=true -NETWORK_HTTP_SERVER=127.0.0.1:8888 -NETWORK_RVI_EDGE_SERVER=127.0.0.1:9080 +NETWORK_HTTP_SERVER=http://127.0.0.1:8888 +NETWORK_RVI_EDGE_SERVER=http://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/src/datatype/config.rs b/src/datatype/config.rs index c9c7708..c9905f5 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -204,7 +204,7 @@ pub struct DeviceConfig { pub vin: String, pub packages_dir: String, pub package_manager: PackageManager, - pub system_info: SystemInfo, + pub system_info: Option, pub polling_interval: u64, pub certificates_path: String, } @@ -216,7 +216,7 @@ impl Default for DeviceConfig { vin: "V1234567890123456".to_string(), packages_dir: "/tmp/".to_string(), package_manager: PackageManager::Deb, - system_info: SystemInfo::default(), + system_info: Some(SystemInfo::default()), polling_interval: 10, certificates_path: "/tmp/sota_certificates".to_string() } @@ -262,8 +262,8 @@ pub struct NetworkConfig { impl Default for NetworkConfig { fn default() -> NetworkConfig { NetworkConfig { - http_server: "127.0.0.1:8888".to_string(), - rvi_edge_server: "127.0.0.1:9080".to_string(), + http_server: "http://127.0.0.1:8888".to_string(), + rvi_edge_server: "http://127.0.0.1:9080".to_string(), 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() @@ -327,7 +327,7 @@ mod tests { [device] uuid = "123e4567-e89b-12d3-a456-426655440000" vin = "V1234567890123456" - system_info = "system_info.sh" + system_info = "./system_info.sh" polling_interval = 10 packages_dir = "/tmp/" package_manager = "deb" @@ -348,8 +348,8 @@ mod tests { const NETWORK_CONFIG: &'static str = r#" [network] - http_server = "127.0.0.1:8888" - rvi_edge_server = "127.0.0.1:9080" + 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" diff --git a/src/datatype/system_info.rs b/src/datatype/system_info.rs index 2d8fff2..987da3b 100644 --- a/src/datatype/system_info.rs +++ b/src/datatype/system_info.rs @@ -1,6 +1,5 @@ use rustc_serialize::{Decoder, Decodable}; use std::process::Command; -use std::str::FromStr; use datatype::Error; @@ -13,8 +12,12 @@ pub struct SystemInfo { impl SystemInfo { /// Instantiate a new type to report on the system information. - pub fn new(command: String) -> SystemInfo { - SystemInfo { command: command } + pub fn new(command: &str) -> Option { + if command == "" { + None + } else { + Some(SystemInfo { command: command.to_string() }) + } } /// Generate a new report of the system information. @@ -27,20 +30,12 @@ impl SystemInfo { impl Default for SystemInfo { fn default() -> SystemInfo { - SystemInfo::new("system_info.sh".to_string()) - } -} - -impl FromStr for SystemInfo { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(SystemInfo::new(s.to_string())) + 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| Ok(s.parse::().unwrap())) + d.read_str().and_then(|s| SystemInfo::new(&s).ok_or(d.error("bad SystemInfo command path"))) } } diff --git a/src/gateway/dbus.rs b/src/gateway/dbus.rs index 07a3b9c..321e262 100644 --- a/src/gateway/dbus.rs +++ b/src/gateway/dbus.rs @@ -24,7 +24,7 @@ impl Gateway for DBus { thread::spawn(move || { let conn = Connection::get_private(BusType::Session).expect("couldn't get dbus session"); - conn.register_name(&dbus_cfg.name, NameFlag::ReplaceExisting as u32).unwrap(); + conn.register_name(&dbus_cfg.name, NameFlag::ReplaceExisting as u32).expect("couldn't register name"); let mut obj_path = ObjectPath::new(&conn, &dbus_cfg.path, true); obj_path.insert_interface(&dbus_cfg.interface, default_interface(itx)); @@ -33,10 +33,11 @@ impl Gateway for DBus { loop { for item in conn.iter(1000) { if let ConnectionItem::MethodCall(mut msg) = item { - info!("DBus method call: {:?}", msg); - obj_path.handle_message(&mut msg).map(|result| { - let _ = result.map_err(|_| error!("dbus method call failed: {:?}", msg)); - }); + match obj_path.handle_message(&mut msg) { + Some(Ok(())) => info!("DBus message sent: {:?}", msg), + Some(Err(())) => error!("DBus message send failed: {:?}", msg), + None => debug!("unhandled dbus message: {:?}", msg) + } } } } @@ -48,7 +49,7 @@ impl Gateway for DBus { fn pulse(&self, event: Event) { match event { Event::UpdateAvailable(avail) => { - let msg = self.new_message("updateAvailable", &[ + let msg = self.new_swm_message("updateAvailable", &[ MessageItem::from(avail.update_id), MessageItem::from(avail.signature), MessageItem::from(avail.description), @@ -59,7 +60,7 @@ impl Gateway for DBus { } Event::DownloadComplete(comp) => { - let msg = self.new_message("downloadComplete", &[ + let msg = self.new_swm_message("downloadComplete", &[ MessageItem::from(comp.update_image), MessageItem::from(comp.signature) ]); @@ -68,7 +69,7 @@ impl Gateway for DBus { } Event::InstalledSoftwareNeeded => { - let msg = self.new_message("getInstalledPackages", &[ + let msg = self.new_swm_message("getInstalledPackages", &[ MessageItem::from(true), // include packages? MessageItem::from(false) // include firmware? ]); @@ -103,7 +104,7 @@ impl Gateway for DBus { } impl DBus { - fn new_message(&self, method: &str, args: &[MessageItem]) -> Message { + fn new_swm_message(&self, method: &str, args: &[MessageItem]) -> Message { let mgr = self.dbus_cfg.software_manager.clone(); let path = self.dbus_cfg.software_manager_path.clone(); let result = Message::new_method_call(&mgr, &path, &mgr, method); @@ -139,7 +140,7 @@ fn send(itx: &Sender, cmd: Command) { fn handle_initiate_download(itx: &Sender, msg: &mut Message) -> MethodResult { let sender = try!(msg.sender().map(|s| s.to_string()).ok_or(dbus::missing_arg())); - debug!("handle_initiate_download: sender={:?}, msg={:?}", sender, msg); + debug!("dbus handle_initiate_download: sender={:?}, msg={:?}", sender, msg); let mut args = msg.get_items().into_iter(); let arg_id = try!(args.next().ok_or(dbus::missing_arg())); @@ -151,7 +152,7 @@ fn handle_initiate_download(itx: &Sender, msg: &mut Message) -> Metho fn handle_update_report(itx: &Sender, msg: &mut Message) -> MethodResult { let sender = try!(msg.sender().map(|s| s.to_string()).ok_or(dbus::missing_arg())); - debug!("handle_update_report: sender ={:?}, msg ={:?}", sender, msg); + debug!("dbus handle_update_report: sender={:?}, msg={:?}", sender, msg); let mut args = msg.get_items().into_iter(); let id_arg = try!(args.next().ok_or(dbus::missing_arg())); diff --git a/src/gateway/websocket.rs b/src/gateway/websocket.rs index 72e0889..f63a763 100644 --- a/src/gateway/websocket.rs +++ b/src/gateway/websocket.rs @@ -1,12 +1,10 @@ use chan; use chan::Sender; use rustc_serialize::json; -use std::thread; use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use std::time::Duration; use ws; -use ws::{listen, CloseCode, Handler, Handshake, Message, Sender as WsSender}; +use ws::{CloseCode, Handler, Handshake, Message, Sender as WsSender}; use ws::util::Token; use datatype::{Command, Error, Event}; @@ -22,22 +20,15 @@ pub struct Websocket { impl Gateway for Websocket { fn initialize(&mut self, itx: Sender) -> Result<(), String> { - let clients = self.clients.clone(); - let addr = self.server.clone(); - info!("Opening websocket listener at {}", addr); - - thread::spawn(move || { - listen(&addr as &str, |out| { - WebsocketHandler { - out: out, - itx: itx.clone(), - clients: clients.clone() - } - }).expect("couldn't start websocket listener"); - }); + ws::listen(&self.server.clone() as &str, |out| { + WebsocketHandler { + out: out, + itx: itx.clone(), + clients: self.clients.clone() + } + }).expect("couldn't start websocket listener"); - thread::sleep(Duration::from_secs(1)); // FIXME: ugly hack for blocking listen call - Ok(info!("Websocket gateway started.")) + Ok(info!("Websocket gateway started at {}.", self.server)) } fn pulse(&self, event: Event) { @@ -69,7 +60,7 @@ impl Handler for WebsocketHandler { Err(err) } - Err(_) => unreachable!() + Err(err) => panic!("unexpected websocket on_message error: {}", err) }) } @@ -117,7 +108,7 @@ mod tests { use std::collections::HashMap; use std::sync::{Arc, Mutex}; use ws; - use ws::{connect, CloseCode}; + use ws::CloseCode; use datatype::{Command, Event}; use gateway::{Gateway, Interpret}; @@ -125,6 +116,7 @@ mod tests { #[test] + #[ignore] // FIXME: wait for https://github.com/housleyjk/ws-rs/issues/64 fn websocket_connections() { let (etx, erx) = chan::sync::(0); let (itx, irx) = chan::sync::(0); @@ -152,9 +144,9 @@ mod tests { crossbeam::scope(|scope| { for id in 0..10 { scope.spawn(move || { - connect("ws://localhost:3012", |out| { - out.send(format!(r#"{{ "variant": "StartDownload", "fields": [["{}"]] }}"#, id)) - .expect("couldn't write to websocket"); + ws::connect("ws://localhost:3012", |out| { + let msg = format!(r#"{{ "variant": "StartDownload", "fields": [["{}"]] }}"#, id); + out.send(msg).expect("couldn't write to websocket"); move |msg: ws::Message| { let ev: Event = json::decode(&format!("{}", msg)).unwrap(); diff --git a/src/interpreter.rs b/src/interpreter.rs index 7f401de..b15c37e 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -28,11 +28,11 @@ pub trait Interpreter { let started = time::precise_time_ns(); wg.add(1); - debug!("interpreter starting: {}", started); + trace!("interpreter starting: {}", started); self.interpret(input, &otx); thread::sleep(cooldown); // let any further work commence - debug!("interpreter stopping: {}", started); + trace!("interpreter stopping: {}", started); wg.done(); } } @@ -42,7 +42,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 pacman: PackageManager, + pub send_sysinfo: bool, } impl Interpreter for EventInterpreter { @@ -56,7 +57,10 @@ impl Interpreter for EventInterpreter { ctx.send(Command::SendInstalledPackages(packages)); }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err)); } - ctx.send(Command::SendSystemInfo); + + if self.send_sysinfo { + ctx.send(Command::SendSystemInfo); + } } Event::NotAuthenticated => { @@ -201,8 +205,8 @@ impl<'t> GlobalInterpreter<'t> { } Command::ListSystemInfo => { - let info = try!(self.config.device.system_info.report()); - etx.send(Event::FoundSystemInfo(info)); + let sysinfo = self.config.device.system_info.as_ref().expect("SystemInfo command not set"); + etx.send(Event::FoundSystemInfo(try!(sysinfo.report()))); } Command::SendInstalledPackages(packages) => { @@ -218,8 +222,8 @@ impl<'t> GlobalInterpreter<'t> { } Command::SendSystemInfo => { - let info = try!(self.config.device.system_info.report()); - try!(sota.send_system_info(&info)); + let sysinfo = self.config.device.system_info.as_ref().expect("SystemInfo command not set"); + try!(sota.send_system_info(&try!(sysinfo.report()))); etx.send(Event::SystemInfoSent); } diff --git a/src/main.rs b/src/main.rs index 2caa411..fc001b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,6 +46,7 @@ fn start_signal_handler(signals: Receiver) { } 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 { @@ -54,14 +55,13 @@ fn start_update_poller(interval: u64, itx: Sender, wg: WaitGroup) { 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(); set_ca_certificates(Path::new(&config.device.certificates_path)); @@ -78,11 +78,6 @@ fn main() { let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); scope.spawn(move || start_signal_handler(signals)); - 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)); - if config.gateway.console { let cons_itx = itx.clone(); let cons_sub = broadcast.subscribe(); @@ -104,16 +99,21 @@ fn main() { scope.spawn(move || http.start(http_itx, http_sub)); } - let mut rvi = None; - if config.gateway.rvi { + 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 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()); - rvi = Some(services); - } + 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 + }; if config.gateway.socket { let socket_itx = itx.clone(); @@ -136,9 +136,11 @@ fn main() { 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_wg = wg.clone(); scope.spawn(move || EventInterpreter { - pacman: event_mgr + pacman: event_mgr, + send_sysinfo: event_sys, }.run(event_sub, event_ctx, event_wg)); let cmd_itx = itx.clone(); @@ -149,7 +151,7 @@ fn main() { config: config, token: None, http_client: Box::new(AuthClient::default()), - rvi: rvi, + rvi: rvi_services, }.run(irx, etx, wg)); scope.spawn(move || broadcast.start()); @@ -259,7 +261,7 @@ fn build_config() -> Config { config.device.polling_interval = interval.parse().unwrap_or_else(|err| exit!("Invalid device polling interval: {}", 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 = SystemInfo::new(&cmd)); matches.opt_str("gateway-console").map(|console| { config.gateway.console = console.parse().unwrap_or_else(|err| exit!("Invalid console gateway boolean: {}", err)); diff --git a/src/sota.rs b/src/sota.rs index 9ea615e..57f9308 100644 --- a/src/sota.rs +++ b/src/sota.rs @@ -1,6 +1,6 @@ use rustc_serialize::json; +use std::{fs, io}; use std::fs::File; -use std::io; use std::path::PathBuf; use datatype::{Config, DeviceReport, DownloadComplete, Error, Package, @@ -75,6 +75,7 @@ impl<'c, 'h> Sota<'c, 'h> { let ref pacman = self.config.device.package_manager; let path = self.package_path(id.clone()).expect("install_update expects a valid path"); pacman.install_package(&path).and_then(|(code, output)| { + let _ = fs::remove_file(&path).unwrap_or_else(|err| error!("couldn't remove installed package: {}", err)); Ok(UpdateReport::single(id.clone(), code, output)) }).or_else(|(code, output)| { Err(UpdateReport::single(id.clone(), code, output)) diff --git a/tests/sota.toml b/tests/sota.toml index 0444a71..1c58c16 100644 --- a/tests/sota.toml +++ b/tests/sota.toml @@ -20,7 +20,7 @@ uuid = "123e4567-e89b-12d3-a456-426655440000" vin = "V1234567890123456" packages_dir = "/tmp/" package_manager = "deb" -system_info = "system_info.sh" +system_info = "./system_info.sh" polling_interval = 10 certificates_path = "/tmp/sota_certificates" @@ -33,8 +33,8 @@ socket = false websocket = true [network] -http_server = "127.0.0.1:8888" -rvi_edge_server = "127.0.0.1:9080" +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" -- cgit v1.2.1 From fe4b7df778f78342b68af90f24176563e3a04951 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 24 Oct 2016 14:14:12 +0200 Subject: Fix blocking Websocket gateway listen call --- src/gateway/websocket.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/gateway/websocket.rs b/src/gateway/websocket.rs index f63a763..8c597b9 100644 --- a/src/gateway/websocket.rs +++ b/src/gateway/websocket.rs @@ -3,6 +3,7 @@ use chan::Sender; use rustc_serialize::json; use std::collections::HashMap; use std::sync::{Arc, Mutex}; +use std::thread; use ws; use ws::{CloseCode, Handler, Handshake, Message, Sender as WsSender}; use ws::util::Token; @@ -20,13 +21,18 @@ pub struct Websocket { impl Gateway for Websocket { fn initialize(&mut self, itx: Sender) -> Result<(), String> { - ws::listen(&self.server.clone() as &str, |out| { - WebsocketHandler { - out: out, - itx: itx.clone(), - clients: self.clients.clone() - } - }).expect("couldn't start websocket listener"); + let clients = self.clients.clone(); + let addr = self.server.clone(); + + thread::spawn(move || { + ws::listen(&addr as &str, |out| { + WebsocketHandler { + out: out, + itx: itx.clone(), + clients: clients.clone() + } + }).expect("couldn't start websocket listener"); + }); Ok(info!("Websocket gateway started at {}.", self.server)) } -- cgit v1.2.1 From bf1ba9b17a7d58c7d0fdda6062b4be69ba7af488 Mon Sep 17 00:00:00 2001 From: Jon Oster Date: Wed, 26 Oct 2016 16:02:27 +0200 Subject: PRO-1630 remove client docs that have been moved to main docsite --- docs/README.adoc | 7 ++ docs/client-guide.adoc | 240 ------------------------------------------------- 2 files changed, 7 insertions(+), 240 deletions(-) create mode 100644 docs/README.adoc delete mode 100644 docs/client-guide.adoc diff --git a/docs/README.adoc b/docs/README.adoc new file mode 100644 index 0000000..9a48874 --- /dev/null +++ b/docs/README.adoc @@ -0,0 +1,7 @@ +== 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/docs/client-guide.adoc b/docs/client-guide.adoc deleted file mode 100644 index 5968573..0000000 --- a/docs/client-guide.adoc +++ /dev/null @@ -1,240 +0,0 @@ -= ATS Garage SOTA Client: Manual installation and integration guide -:icons: font -:toc: left -:toclevels: 3 - -== Introduction - -The SOTA client is fully open source. If you wish, you can download and build the absolute latest version directly from https://github.com/advancedtelematic/rvi_sota_client[its GitHub repo]. However, ATS recommends using the provided binaries or building from the `stable` branch, as the latest master may not always be fully compatible with ATS Garage. - -This document describes link:#_starting_the_sota_client[how to manually install and set up the SOTA client], provides link:#_sota_client_config_file_guide[a line-by-line guide to the config file], and then describes link:#_api_documentation[the UNIX Domain Sockets API] for the SOTA client to communicate with the software loading manager. - -== Starting the SOTA client - -The SOTA client should be run at startup, as a service. We provide a systemd service file below as an example, but systemd is not a requirement. You can also, of course, run it from the command line to test. Setting the `RUST_LOG` environment variable will let you configure how much debug info you receive. - -===== Example systemd service file ----- -[Unit] -Description=SOTA Client -Wants=network-online.target -After=network.target network-online.target -Requires=network-online.target - -[Service] -RestartSec=5 -Restart=on-failure -Environment="RUST_LOG=info" -DefaultTimeoutStopSec=5 -ExecStart=/usr/bin/sota_client --config /etc/sota.toml - -[Install] -WantedBy=multi-user.target ----- - -== SOTA Client config file guide - -The config file for the SOTA client is in TOML format, and can be invoked at startup with `sota_client --config /path/to/config_file.toml`. A config file unique to each device can be downloaded from its device page on ATS Garage. - -=== [auth] - -This section is required for connection to an SOTA server that implements authentication. It needs to be the first section of the config file. - ----- -[auth] -server = "https://auth-plus.gw.prod01.advancedtelematic.com" <1> -client_id = "bf66425f-d4d6-422b-b510-7c7f178af9fe" <2> -client_secret = "hr8nEWzQc9" <2> -credentials_file = "/opt/sota/credentials.toml" <3> ----- -<1> The URL of the auth server. Should not change. -<2> A unique client ID and secret for this device. With ATS Garage, this is assigned upon device creation and should not change. -<3> If this value is defined, SOTA client will check this file for auth credentials first, and use the credentials it finds there. If the file does not exist, it will be created with the `client_id` and `client_secret` above. - -=== [core] - -This is simply the URL of the core SOTA update server. It should not be changed. - ----- -[core] -server = "https://sota-core.gw.prod01.advancedtelematic.com" ----- - -=== [device] - -This section contains device-specific configuration. - ----- -[device] -uuid = "123e4567-e89b-12d3-a456-426655440000" <1> -vin = "" <2> -system_info = "system_info.sh" <3> -polling_interval = 10 <4> -packages_dir = "/tmp/" <5> -package_manager = "deb" <6> -certificates_path = "/tmp/sota_certificates" <7> ----- -<1> The UUID of the device. This is assigned by ATS Garage upon device creation, and should not be changed. -<2> The device's VIN, if it has one. Deprecated feature; this value is not used by the current version of ATS Garage. -<3> The script to use to gather system information. -<4> The frequency, in seconds, with which the SOTA client should poll the server for updates. -<5> The location SOTA Client should use for temporary package storage until they are processed by the software loading manager. -<6> The software loading manager backend to use. Possible values are `deb`, `rpm`, and `off`. If an external software loading manager is in use, this should be set to `off`. -<7> The certificate authorities SOTA Client trusts. The recommended defaults are taken from Mozilla Servo, and the default certificates file can be downloaded from https://raw.githubusercontent.com/advancedtelematic/rvi_sota_client/master/run/sota_certificates[the SOTA client repo]. You may also use your own CA file or the operating system's default trusted certificates file, but ATS recommends using the provided certificate file. - -=== [network] - -This section contains network configuration information for various gateways. Note that if the corresponding gateway is not enabled, these settings will have no effect. - ----- -[network] -http_server = "127.0.0.1:8080" <1> -socket_commands_path = "/tmp/sota-commands.socket" <2> -socket_events_path = "/tmp/sota-events.socket" <3> -websocket_server = "https://sota-core.gw.prod01.advancedtelematic.com" <4> -rvi_edge_server = "127.0.0.1:9080" <5> ----- -<1> The path to the http-only core server, if the http gateway is enabled in the [gateway] section. -<2> The name of the unix domain socket to be used for sending commands, if the socket gateway is enabled in the [gateway] section. -<3> The name of the unix domain socket to be used for sending events, if the socket gateway is enabled in the [gateway] section. -<4> The location of the websocket server, for communication with the ATS Garage Core server. -<5> The location of the RVI edge node, if the rvi gateway is enabled in the [gateway] section. - -=== [gateway] - -OTA Client communicates externally with the SOTA Core server, and internally with the device's software loading manager. This section defines which gateways/protocols it should use to do so. - ----- -[gateway] -console = false <1> -dbus = false -http = false -rvi = false -socket = false <2> -websocket = true ----- -<1> REPL mode, for debug use only. -<2> Unix domain sockets for local communication. - -==== Optional gateway: [rvi] - -Remote Vehicle Interaction (RVI) is an open source infrastructure developed by GENIVI and Jaguar Land Rover to power the next generation of connected vehicle services. This section contains values for configuration of RVI nodes. Note that having this section defined does not imply that RVI will be used; if the RVI gateway is turned off in the `[gateway]` section, this is ignored. - ----- -[rvi] -client = "http://127.0.0.1:8901" -storage_dir = "/var/sota" -timeout = 20 ----- - -==== Optional gateway: [dbus] - -This section contains values for dbus configuration, using the GENIVI software loading manager's names as the default. Note that having this section defined does not imply that dbus will be used; if the dbus gateway is turned off in the `[gateway]` section, this is ignored. - ----- -[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 ----- - -== API Documentation - -The SOTA client is capable of integrating with various types of software loading manager (SWLM). This enables, with a relatively small amount of integration work, the installation of unlimited varieties of software or firmware packages. A complete API is available via D-Bus, but there are other options in development, including a simple unix domain socket API. - -=== D-Bus API - -For more information on the D-Bus API, please check out the https://github.com/advancedtelematic/rvi_sota_client[rvi_sota_client] repo from GitHub, and use `make doc` to build the Rustdoc library for this or better yet link:mailto:support@atsgarage.com[contact us] and let us know what you'd like to do and how we can help. - -=== Unix domain socket API - -Currently, only the core functionality of making software updates available and accepting reports on their installation is present in the unix domain socket API. A software loading manager must support the following: - -- It must listen for `DownloadComplete` events on the events socket. At the moment, the only events published on the events socket are DownloadComplete events, but in future other types of events may be published. The SWLM should be capable of filtering for only the type of events it is interested in. -- It must send a `SendUpdateReport` command on the command socket, with a status code, when the update finishes. -- It must send a `SendInstalledPackages` command on the command socket, listing the names and versions of installed packages, after a successful package install. - -==== Configuration - -To communicate with the SOTA Client over unix domain sockets, `socket = true` must be defined in the `[gateway]` section of the config file. Additionally, the names of the two sockets are configurable in the link:#__network[[network]] section. - -The SOTA Client will create the commands socket, but the software loading manager must create the events socket. Note that it must be readable and writable by root, and be at the location configured in `sota.toml`. - -==== DownloadComplete - -Once the SOTA client has successfully downloaded an update ordered by an ATS Garage user, it will send a DownloadComplete event on the events socket with the following body: - -[source,json] ----- -{ - "version": "0.1", <1> - "event": "DownloadComplete", <2> - "data": { - "update_id": "string", <3> - "update_image": "string", <4> - "signature": "string" <5> - } -} ----- -<1> The API version of the response. -<2> The Event type of the message. -<3> A unique ID for the update. The SWLM will need to reference this ID when reporting on the status of the install. -<4> The location of the delivered update file. -<5> A cryptographic signature; may be blank if the package uploader chose not to supply one. The SWLM *may* implement signature verification, but is not required to do so. - -==== SendUpdateReport - -The Software Loading Manager should send this command on the command socket upon termination of the install, whether it was successful or failed. The form of the command over unix domain sockets is `SendUpdateReport update_id result_code`. - -* The update ID is the one received from the `DownloadComplete` message. -* The result of the update attempt, either in numberical or verbal form. The possible update codes are enumerated below. - -TIP: Over D-Bus, it is also possible to sent a longer textual description of the install status/error. This feature is not yet available via unix domain sockets, but will be in the future. It is recommended that SWLM integration developers maintain the capability to send more verbose status messages. - -===== Possible update result codes - -[cols="1,2,5",options="header"] -|=== -| Numerical | Verbal | Description -|0 | OK | Operation executed successfully -|1 | ALREADY_PROCESSED | Operation has already been processed -|2 | DEPENDENCY_FAILURE | Dependency failure during package install, upgrade, or removal -|3 | VALIDATION_FAILED | Update image integrity has been compromised -|4 | INSTALL_FAILED | Package installation failed -|5 | UPGRADE_FAILED | Package upgrade failed -|6 | REMOVAL_FAILED | Package removal failed -|7 | FLASH_FAILED | The module loader could not flash its managed module -|8 | CREATE_PARTITION_FAILED | Partition creation failed -|9 | DELETE_PARTITION_FAILED | Partition deletion failed -|10 | RESIZE_PARTITION_FAILED | Partition resize failed -|11 | WRITE_PARTITION_FAILED | Partition write failed -|12 | PATCH_PARTITION_FAILED | Partition patching failed -|13 | USER_DECLINED | User declined the update -|14 | SOFTWARE_BLACKLISTED | Software was blacklisted -|15 | DISK_FULL | Ran out of disk space -|16 | NOT_FOUND | Software package not found -|17 | OLD_VERSION | Tried to downgrade to older version -|18 | INTERNAL_ERROR | SWM Internal integrity error -|19 | GENERAL_ERROR | Other error -|=== - -==== SendInstalledPackages - -This command is used to notify the OTA client of what packages are installed on the system. It _must_ be sent (to the command socket) after each `SendUpdateReport`, and also _may_ be sent at any other time. ATS recommends sending it on system startup, at a minimum. - -The command syntax is simply this: - ----- -SendInstalledPackages package1_name package1_version package2_name package2_version [...] packageN_name packageN_version ----- - -Package names and versions can't contain spaces, but there are no other character restrictions. For example, all of the package/versions listed here are valid: - ----- -SendInstalledPackages gcc 7.63 Movie&MusicPlayer rc2-beta3 ECU9274927BF82-firmware gitID-2fab572 ----- - -Note, however, that all packages must have a version. -- cgit v1.2.1 From 07380a9a8974890c5b484dc3524198daaa766d60 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Wed, 26 Oct 2016 14:55:53 +0200 Subject: Use new 'mydevice' endpoint on sota-core --- src/sota.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/sota.rs b/src/sota.rs index 57f9308..64abacd 100644 --- a/src/sota.rs +++ b/src/sota.rs @@ -3,7 +3,7 @@ use std::{fs, io}; use std::fs::File; use std::path::PathBuf; -use datatype::{Config, DeviceReport, DownloadComplete, Error, Package, +use datatype::{Config, DownloadComplete, Error, Package, UpdateReport, UpdateRequest, UpdateRequestId, Url}; use http::{Client, Response}; @@ -22,9 +22,9 @@ impl<'c, 'h> Sota<'c, 'h> { } /// Takes a path and returns a new endpoint of the format - /// `/api/v1/device_updates/$path`. + /// `/api/v1/mydevice/$path`. fn endpoint(&self, path: &str) -> Url { - let endpoint = format!("/api/v1/device_updates/{}{}", self.config.device.uuid, path); + let endpoint = format!("/api/v1/mydevice/{}{}", self.config.device.uuid, path); self.config.core.server.join(&endpoint).expect("couldn't build endpoint url") } @@ -38,7 +38,7 @@ impl<'c, 'h> Sota<'c, 'h> { /// Query the Core server for any pending or in-flight package updates. pub fn get_update_requests(&mut self) -> Result, Error> { - let resp_rx = self.client.get(self.endpoint("/queued"), None); + let resp_rx = self.client.get(self.endpoint("/updates"), None); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get new updates".to_string()))); let data = match resp { Response::Success(data) => data, @@ -52,7 +52,7 @@ impl<'c, 'h> Sota<'c, 'h> { /// Download a specific update from the Core server. pub fn download_update(&mut self, id: UpdateRequestId) -> Result { - let resp_rx = self.client.get(self.endpoint(&format!("/{}/download", id)), None); + let resp_rx = self.client.get(self.endpoint(&format!("/updates/{}/download", id)), None); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string()))); let data = match resp { Response::Success(data) => data, @@ -97,9 +97,8 @@ impl<'c, 'h> Sota<'c, 'h> { /// Send the outcome of a package update to the Core server. pub fn send_update_report(&mut self, update_report: &UpdateReport) -> Result<(), Error> { - let report = DeviceReport::new(&self.config.device.uuid, update_report); - let body = try!(json::encode(&report)); - let url = self.endpoint(&format!("/{}", report.device)); + let body = try!(json::encode(&update_report.operation_results)); + let url = self.endpoint(&format!("/updates/{}", update_report.update_id)); let resp_rx = self.client.post(url, Some(body.into_bytes())); let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send update report".to_string()))); -- cgit v1.2.1 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 From e6a3cec143ef7164544531b727389f10fab60ae1 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 28 Oct 2016 17:12:24 +0200 Subject: Move documentation to the rvi_sota_server repository. --- README.md | 89 ++++------------------------------------------ src/package_manager/rpm.rs | 2 +- 2 files changed, 7 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 5f3c089..41db865 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,10 @@ -# SOTA client +# SOTA Client 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. +## Documentation -## 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). - -## Configuration - -See [here](http://advancedtelematic.github.io/rvi_sota_server/cli/client-startup-and-configuration.html) for full details on configuring the client on startup. - -### Makefile configuration - -Run `make help` to see the full list of targets. - -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 --------------------: | :------------------------ | :------------------ -`AUTH_SECTION` | `false` | Set to true to authenticate on startup. -`CONFIG_ONLY` | `false` | Set to true to generate a config file then quit. -`AUTH_SERVER` | http://127.0.0.1:9001 | The Auth server for client authentication. -`CORE_SERVER` | http://127.0.0.1:9000 | The Core server for client communication. -`REGISTRY_SERVER` | http://127.0.0.1:8083 | The server used for registering new devices. -`OUTPUT_PATH` | `/etc/sota.toml` | Path to write the newly generated config. -`TEMPLATE_PATH` | `/etc/sota.toml.template` | Path to the template for new config files. -`DEVICE_VIN` | (generated) | Use this VIN rather than generating a new one. -`DEVICE_UUID` | (generated) | Use this UUID rather than generating a new one. -`AUTH_CLIENT_ID` | (from registry server) | Use this client ID for authentication. -`AUTH_CLIENT_SECRET` | (from registry server) | Use this client secret for authentication. - -For example, running `CONFIG_ONLY=true make run` will output a newly generated `sota.toml` to stdout then quit. - -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. - -## GENIVI Development Platform over RVI - -See the full documentation at [rvi_sota_server](http://advancedtelematic.github.io/rvi_sota_server/). - -### Starting the SOTA Server - -To get started: - - 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 - -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"). - -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 - -See `tests/toml/genivi.toml` for a sample config. - -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. - -Now you can start the client: - - make client - 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) 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. +* [Building the SOTA Client.](http://advancedtelematic.github.io/rvi_sota_server/cli/building-the-sota-client.html) +* [Configuring the SOTA Client.](http://advancedtelematic.github.io/rvi_sota_server/cli/client-startup-and-configuration.html) +* [Deploying the SOTA Server.](http://advancedtelematic.github.io/rvi_sota_server/doc/deployment-with-dockercompose.html) +* [The `Command` and `Event` API reference.](http://advancedtelematic.github.io/rvi_sota_server/cli/client-commands-and-events-reference.html) diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index bdbfd31..f2c8f2a 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -5,7 +5,7 @@ use package_manager::package_manager::{InstallOutcome, parse_package}; /// Returns a list of installed RPM packages with -/// `rpm -qa ==queryformat ${NAME} ${VERSION}\n`. +/// `rpm -qa --queryformat ${NAME} ${VERSION}\n`. pub fn installed_packages() -> Result, Error> { Command::new("rpm").arg("-qa").arg("--queryformat").arg("%{NAME} %{VERSION}\n") .output() -- cgit v1.2.1