From 0852d79ccbb18fbdd054a7254978d738f1d11751 Mon Sep 17 00:00:00 2001 From: Luca Bruno Date: Fri, 21 Dec 2018 10:49:48 +0000 Subject: [PATCH 1/5] provider: add boot check-in on azure and packet This adds a phone-home feature via the `--check-in` flag, in order to support reporting back readiness state to the hosting infrastructure. Initial implementation supports azure and packet. --- src/errors.rs | 2 + src/main.rs | 12 ++++ src/providers/azure/mock_tests.rs | 82 +++++++++++++++++++++++++ src/providers/azure/mod.rs | 82 ++++++++++++++++++++++--- src/providers/cloudstack/configdrive.rs | 5 ++ src/providers/cloudstack/network.rs | 5 ++ src/providers/digitalocean/mod.rs | 5 ++ src/providers/ec2/mod.rs | 5 ++ src/providers/gce/mod.rs | 5 ++ src/providers/mod.rs | 1 + src/providers/openstack/network.rs | 5 ++ src/providers/packet/mock_tests.rs | 39 ++++++++++++ src/providers/packet/mod.rs | 27 ++++++++ src/providers/vagrant_virtualbox/mod.rs | 5 ++ src/retry/client.rs | 48 ++++++++++++++- 15 files changed, 320 insertions(+), 8 deletions(-) create mode 100644 src/providers/azure/mock_tests.rs create mode 100644 src/providers/packet/mock_tests.rs diff --git a/src/errors.rs b/src/errors.rs index 40916d15..35ded191 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -13,6 +13,7 @@ // limitations under the License. use reqwest::header; +use serde_json; error_chain!{ links { @@ -24,6 +25,7 @@ error_chain!{ XmlDeserialize(::serde_xml_rs::Error); Base64Decode(::base64::DecodeError); Io(::std::io::Error); + Json(serde_json::Error); Reqwest(::reqwest::Error); OpensslStack(::openssl::error::ErrorStack); HeaderValue(header::InvalidHeaderValue); diff --git a/src/main.rs b/src/main.rs index aacab831..f81faf06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ extern crate reqwest; #[macro_use] extern crate serde_derive; extern crate serde; +#[cfg_attr(test, macro_use)] extern crate serde_json; extern crate serde_xml_rs; #[macro_use] @@ -63,6 +64,7 @@ const CMDLINE_OEM_FLAG: &str = "coreos.oem.id"; struct Config { provider: String, attributes_file: Option, + check_in: bool, ssh_keys_user: Option, hostname_file: Option, network_units_dir: Option, @@ -110,6 +112,12 @@ fn run() -> Result<()> { .map_or(Ok(()), |x| metadata.write_network_units(x)) .chain_err(|| "writing network units")?; + // perform boot check-in. + if config.check_in { + metadata.boot_checkin() + .chain_err(|| "checking-in instance boot to cloud provider")?; + } + debug!("Done!"); Ok(()) @@ -142,6 +150,9 @@ fn init() -> Result { .long("attributes") .help("The file into which the metadata attributes are written") .takes_value(true)) + .arg(Arg::with_name("check-in") + .long("check-in") + .help("Check-in this instance boot with the cloud provider")) .arg(Arg::with_name("cmdline") .long("cmdline") .help("Read the cloud provider from the kernel cmdline")) @@ -174,6 +185,7 @@ fn init() -> Result { } }, attributes_file: matches.value_of("attributes").map(String::from), + check_in: matches.is_present("check-in"), ssh_keys_user: matches.value_of("ssh-keys").map(String::from), hostname_file: matches.value_of("hostname").map(String::from), network_units_dir: matches.value_of("network-units").map(String::from), diff --git a/src/providers/azure/mock_tests.rs b/src/providers/azure/mock_tests.rs new file mode 100644 index 00000000..4d0e2743 --- /dev/null +++ b/src/providers/azure/mock_tests.rs @@ -0,0 +1,82 @@ +use mockito::{self, Matcher}; +use providers::{azure, MetadataProvider}; + +#[test] +fn test_boot_checkin() { + let fab_version = "/?comp=versions"; + let ver_body = r#" + + + 2015-04-05 + + + 2015-04-05 + 2012-11-30 + 2012-09-15 + 2012-05-15 + 2011-12-31 + 2011-10-15 + 2011-08-31 + 2011-04-07 + 2010-12-15 + 2010-28-10 + +"#; + let m_version = mockito::mock("GET", fab_version) + .with_body(ver_body) + .with_status(200) + .create(); + + let fab_goalstate = "/machine/?comp=goalstate"; + let gs_body = r#" + + 2012-11-30 + 1 + + Started + 300000 + + 16001 + + FALSE + + + a511aa6d-29e7-4f53-8788-55655dfe848f + + + f6cd1d7ef1644557b9059345e5ba890c.lars-test-1 + Started + + http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=config&type=hostingEnvironmentConfig&incarnation=1 + http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=config&type=sharedConfig&incarnation=1 + http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=config&type=extensionsConfig&incarnation=1 + http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=config&type=fullConfig&incarnation=1 + http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=certificates&incarnation=1 + f6cd1d7ef1644557b9059345e5ba890c.0.f6cd1d7ef1644557b9059345e5ba890c.0.lars-test-1.1.xml + + + + + +"#; + let m_goalstate = mockito::mock("GET", fab_goalstate) + .with_body(gs_body) + .with_status(200) + .create(); + + let fab_health = "/machine/?comp=health"; + let m_health = mockito::mock("POST", fab_health) + .match_header("content-type", Matcher::Regex("text/xml".to_string())) + .match_header("x-ms-version", Matcher::Regex("2012-11-30".to_string())) + .match_body(Matcher::Regex("Ready".to_string())) + .with_status(200) + .create(); + + let provider = azure::Azure::try_new(); + let r = provider.unwrap().boot_checkin(); + + m_version.assert(); + m_goalstate.assert(); + m_health.assert(); + r.unwrap(); +} diff --git a/src/providers/azure/mod.rs b/src/providers/azure/mod.rs index 628332c1..ec1a3f4c 100644 --- a/src/providers/azure/mod.rs +++ b/src/providers/azure/mod.rs @@ -28,14 +28,15 @@ use errors::*; use network; use providers::MetadataProvider; use retry; -use util; + +#[cfg(test)] +mod mock_tests; static HDR_AGENT_NAME: &str = "x-ms-agent-name"; static HDR_VERSION: &str = "x-ms-version"; static HDR_CIPHER_NAME: &str = "x-ms-cipher-name"; static HDR_CERT: &str = "x-ms-guest-agent-public-x509-cert"; -const OPTION_245: &str = "OPTION_245"; const MS_AGENT_NAME: &str = "com.coreos.metadata"; const MS_VERSION: &str = "2012-11-30"; const SMIME_HEADER: &str = "\ @@ -48,8 +49,31 @@ Content-Transfer-Encoding: base64 /// This is a known working wireserver endpoint within Azure. /// See: https://blogs.msdn.microsoft.com/mast/2015/05/18/what-is-the-ip-address-168-63-129-16/ +#[cfg(not(test))] const FALLBACK_WIRESERVER_ADDR: [u8; 4] = [168, 63, 129, 16]; // for grep: 168.63.129.16 +macro_rules! ready_state { + ($container:expr, $instance:expr) => { + format!(r#" + + 1 + + {} + + + {} + + Ready + + + + + +"#, + $container, $instance) + } +} + #[derive(Debug, Deserialize, Clone, Default)] struct GoalState { #[serde(rename = "Container")] @@ -58,8 +82,10 @@ struct GoalState { #[derive(Debug, Deserialize, Clone, Default)] struct Container { + #[serde(rename = "ContainerId")] + pub container_id: String, #[serde(rename = "RoleInstanceList")] - pub role_instance_list: RoleInstanceList + pub role_instance_list: RoleInstanceList, } #[derive(Debug, Deserialize, Clone, Default)] @@ -71,7 +97,9 @@ struct RoleInstanceList { #[derive(Debug, Deserialize, Clone)] struct RoleInstance { #[serde(rename = "Configuration")] - pub configuration: Configuration + pub configuration: Configuration, + #[serde(rename = "InstanceId")] + pub instance_id: String, } #[derive(Debug, Deserialize, Clone)] @@ -177,11 +205,12 @@ impl Azure { } fn get_goal_state(&self) -> Result { - self.client.get(retry::Xml, format!("http://{}/machine/?comp=goalstate", self.endpoint)).send() + self.client.get(retry::Xml, format!("{}/machine/?comp=goalstate", self.fabric_base_url())).send() .chain_err(|| "failed to get goal state")? .ok_or_else(|| "failed to get goal state: not found response".into()) } + #[cfg(not(test))] fn get_fabric_address() -> IpAddr { // try to fetch from dhcp, else use fallback; this is similar to what WALinuxAgent does Azure::get_fabric_address_from_dhcp().unwrap_or_else(|e| { @@ -191,8 +220,9 @@ impl Azure { }) } + #[cfg(not(test))] fn get_fabric_address_from_dhcp() -> Result { - let v = util::dns_lease_key_lookup(OPTION_245)?; + let v = ::util::dns_lease_key_lookup("OPTION_245")?; // value is an 8 digit hex value. convert it to u32 and // then parse that into an ip. Ipv4Addr::from(u32) // performs conversion from big-endian @@ -202,8 +232,24 @@ impl Azure { Ok(IpAddr::V4(dec.into())) } + #[cfg(not(test))] + fn fabric_base_url(&self) -> String { + format!("http://{}", self.endpoint) + } + + #[cfg(test)] + fn get_fabric_address() -> IpAddr { + use std::net::Ipv4Addr; + IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)) + } + + #[cfg(test)] + fn fabric_base_url(&self) -> String { + ::mockito::server_url().to_string() + } + fn is_fabric_compatible(&self, version: &str) -> Result<()> { - let versions: Versions = self.client.get(retry::Xml, format!("http://{}/?comp=versions", self.endpoint)).send() + let versions: Versions = self.client.get(retry::Xml, format!("{}/?comp=versions", self.fabric_base_url())).send() .chain_err(|| "failed to get versions")? .ok_or_else(|| "failed to get versions: not found")?; @@ -294,6 +340,20 @@ impl Azure { Ok(attributes) } + + /// Return this instance `ContainerId`. + pub(crate) fn container_id(&self) -> &str { + &self.goal_state.container.container_id + } + + /// Return this instance `InstanceId`. + pub(crate) fn instance_id(&self) -> Result<&str> { + Ok(&self.goal_state.container + .role_instance_list + .role_instances.get(0) + .ok_or_else(|| "empty RoleInstanceList".to_string())? + .instance_id) + } } impl MetadataProvider for Azure { @@ -328,4 +388,12 @@ impl MetadataProvider for Azure { fn network_devices(&self) -> Result> { Ok(vec![]) } + + fn boot_checkin(&self) -> Result<()> { + let body = ready_state!(self.container_id(), self.instance_id()?); + let url = self.fabric_base_url() + "/machine/?comp=health"; + self.client.post(retry::Xml, url, body.into()) + .dispatch_post()?; + Ok(()) + } } diff --git a/src/providers/cloudstack/configdrive.rs b/src/providers/cloudstack/configdrive.rs index 8d6674bf..83219b09 100644 --- a/src/providers/cloudstack/configdrive.rs +++ b/src/providers/cloudstack/configdrive.rs @@ -137,6 +137,11 @@ impl MetadataProvider for ConfigDrive { fn network_devices(&self) -> Result> { Ok(vec![]) } + + fn boot_checkin(&self) -> Result<()> { + warn!("boot check-in requested, but not supported on this platform"); + Ok(()) + } } impl ::std::ops::Drop for ConfigDrive { diff --git a/src/providers/cloudstack/network.rs b/src/providers/cloudstack/network.rs index c7a79f2c..de6db75c 100644 --- a/src/providers/cloudstack/network.rs +++ b/src/providers/cloudstack/network.rs @@ -101,4 +101,9 @@ impl MetadataProvider for CloudstackNetwork { fn network_devices(&self) -> Result> { Ok(vec![]) } + + fn boot_checkin(&self) -> Result<()> { + warn!("boot check-in requested, but not supported on this platform"); + Ok(()) + } } diff --git a/src/providers/digitalocean/mod.rs b/src/providers/digitalocean/mod.rs index 2234d52f..0e17beb6 100644 --- a/src/providers/digitalocean/mod.rs +++ b/src/providers/digitalocean/mod.rs @@ -265,4 +265,9 @@ impl MetadataProvider for DigitalOceanProvider { fn network_devices(&self) -> Result> { Ok(vec![]) } + + fn boot_checkin(&self) -> Result<()> { + warn!("boot check-in requested, but not supported on this platform"); + Ok(()) + } } diff --git a/src/providers/ec2/mod.rs b/src/providers/ec2/mod.rs index a5e5dc03..08569837 100644 --- a/src/providers/ec2/mod.rs +++ b/src/providers/ec2/mod.rs @@ -140,4 +140,9 @@ impl MetadataProvider for Ec2Provider { fn network_devices(&self) -> Result> { Ok(vec![]) } + + fn boot_checkin(&self) -> Result<()> { + warn!("boot check-in requested, but not supported on this platform"); + Ok(()) + } } diff --git a/src/providers/gce/mod.rs b/src/providers/gce/mod.rs index 0ebda15e..180fff88 100644 --- a/src/providers/gce/mod.rs +++ b/src/providers/gce/mod.rs @@ -146,4 +146,9 @@ impl MetadataProvider for GceProvider { fn network_devices(&self) -> Result> { Ok(vec![]) } + + fn boot_checkin(&self) -> Result<()> { + warn!("boot check-in requested, but not supported on this platform"); + Ok(()) + } } diff --git a/src/providers/mod.rs b/src/providers/mod.rs index ff50e14f..c570db01 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -61,6 +61,7 @@ pub trait MetadataProvider { fn ssh_keys(&self) -> Result>; fn networks(&self) -> Result>; fn network_devices(&self) -> Result>; + fn boot_checkin(&self) -> Result<()>; fn write_attributes(&self, attributes_file_path: String) -> Result<()> { let mut attributes_file = create_file(&attributes_file_path)?; diff --git a/src/providers/openstack/network.rs b/src/providers/openstack/network.rs index 11d93a03..ee288d2e 100644 --- a/src/providers/openstack/network.rs +++ b/src/providers/openstack/network.rs @@ -91,4 +91,9 @@ impl MetadataProvider for OpenstackProvider { fn network_devices(&self) -> Result> { Ok(vec![]) } + + fn boot_checkin(&self) -> Result<()> { + warn!("boot check-in requested, but not supported on this platform"); + Ok(()) + } } diff --git a/src/providers/packet/mock_tests.rs b/src/providers/packet/mock_tests.rs new file mode 100644 index 00000000..134be6b0 --- /dev/null +++ b/src/providers/packet/mock_tests.rs @@ -0,0 +1,39 @@ +use mockito::{self, Matcher}; +use providers::{packet, MetadataProvider}; + +#[test] +fn test_boot_checkin() { + let ep = "/events"; + let data = packet::PacketData { + id: String::new(), + hostname: String::new(), + iqn: String::new(), + plan: String::new(), + facility: String::new(), + tags: vec![], + ssh_keys: vec![], + network: packet::PacketNetworkInfo { + interfaces: vec![], + addresses: vec![], + bonding: packet::PacketBondingMode { mode: 0 }, + }, + error: None, + phone_home_url: mockito::server_url(), + }; + let provider = packet::PacketProvider { data }; + + let json_body = json!({ + "state": "succeeded", + "code": 1042, + "message": "coreos-metadata: boot check-in", + }); + let mock = mockito::mock("POST", ep) + .match_header("content-type", Matcher::Regex("text/json.*".to_string())) + .match_body(Matcher::Json(json_body)) + .with_status(200) + .create(); + + let r = provider.boot_checkin(); + mock.assert(); + r.unwrap(); +} diff --git a/src/providers/packet/mod.rs b/src/providers/packet/mod.rs index 096be00f..0464cc1f 100644 --- a/src/providers/packet/mod.rs +++ b/src/providers/packet/mod.rs @@ -24,6 +24,7 @@ use std::str::FromStr; use openssh_keys::PublicKey; use pnet::util::MacAddr; +use serde_json; use update_ssh_keys::AuthorizedKeyEntry; use errors::*; @@ -34,6 +35,9 @@ use util; use ipnetwork::{self, IpNetwork, Ipv4Network, Ipv6Network}; +#[cfg(test)] +mod mock_tests; + #[derive(Clone, Debug, Deserialize)] struct PacketData { id: String, @@ -79,6 +83,15 @@ struct PacketAddressInfo { gateway: IpAddr, } +/// Custom user-state that can be posted on instance +/// boot to Packet phone-home events endpoint. +#[derive(Clone, Debug, Serialize)] +pub(crate) struct PacketUserState { + state: String, + code: u16, + message: String, +} + #[derive(Clone, Debug)] pub struct PacketProvider { data: PacketData, @@ -279,4 +292,18 @@ impl MetadataProvider for PacketProvider { Ok(devices) } + + fn boot_checkin(&self) -> Result<()> { + let user_state = PacketUserState { + state: "succeeded".into(), + code: 1042, + message: "coreos-metadata: boot check-in".into(), + }; + let client = retry::Client::try_new()?; + let url = self.data.phone_home_url.clone() + "/events"; + let body = serde_json::to_string(&user_state)?; + client.post(retry::Json, url, body.into()) + .dispatch_post()?; + Ok(()) + } } diff --git a/src/providers/vagrant_virtualbox/mod.rs b/src/providers/vagrant_virtualbox/mod.rs index 5c5e0f2f..17f595d9 100644 --- a/src/providers/vagrant_virtualbox/mod.rs +++ b/src/providers/vagrant_virtualbox/mod.rs @@ -91,4 +91,9 @@ impl MetadataProvider for VagrantVirtualboxProvider { fn network_devices(&self) -> Result> { Ok(vec![]) } + + fn boot_checkin(&self) -> Result<()> { + warn!("boot check-in requested, but not supported on this platform"); + Ok(()) + } } diff --git a/src/retry/client.rs b/src/retry/client.rs index e312a804..484020a2 100644 --- a/src/retry/client.rs +++ b/src/retry/client.rs @@ -19,6 +19,7 @@ //! of attempts and a backoff strategy. It also takes care of automatically //! deserializing responses and handles headers in a sane way. +use std::borrow::Cow; use std::io::Read; use std::time::Duration; @@ -138,6 +139,21 @@ impl Client { { RequestBuilder{ url, + body: None, + d, + client: self.client.clone(), + headers: self.headers.clone(), + retry: self.retry.clone(), + return_on_404: self.return_on_404, + } + } + + pub fn post(&self, d: D, url: String, body: Cow) -> RequestBuilder + where D: Deserializer + { + RequestBuilder{ + url, + body: Some(body.into_owned()), d, client: self.client.clone(), headers: self.headers.clone(), @@ -151,6 +167,7 @@ pub struct RequestBuilder where D: Deserializer { url: String, + body: Option, d: D, client: reqwest::Client, headers: header::HeaderMap, @@ -183,6 +200,34 @@ impl RequestBuilder }) } + pub fn dispatch_post(self) -> Result + { + let url = reqwest::Url::parse(self.url.as_str()) + .chain_err(|| "failed to parse uri")?; + + self.retry.clone().retry(|attempt| { + let mut builder = reqwest::Client::new() + .post(url.clone()) + .headers(self.headers.clone()) + .header(header::CONTENT_TYPE, self.d.content_type()); + if let Some(ref content) = self.body { + builder = builder.body(content.clone()); + }; + let req = builder.build() + .chain_err(|| "failed to build POST request")?; + + info!("Posting {}: Attempt #{}", req.url(), attempt + 1); + let status = self.client.execute(req) + .chain_err(|| "failed to POST request")? + .status(); + if status.is_success() { + Ok(status) + } else { + Err(format!("POST failed: {}", status).into()) + } + }) + } + fn dispatch_request(&self, req: &Request) -> Result> where T: for<'de> serde::Deserialize<'de> { @@ -213,7 +258,8 @@ impl RequestBuilder } } -/// Reqwests Request struct doesn't implement copy, so we have to do it here +/// Reqwests Request struct doesn't implement `Clone`, +/// so we have to do it here. fn clone_request(req: &Request) -> Request { let mut newreq = Request::new(req.method().clone(), req.url().clone()); newreq.headers_mut().extend(req.headers().clone().into_iter()); From 6ec202f71917d45adc527cb4d5f2e0229cbac960 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 21 Feb 2019 16:17:26 -0500 Subject: [PATCH 2/5] retry/client: don't set Content-Type header in GETs There's no need for GET requests to have a Content-Type header since it's only useful as a way to interpret the message body, which by definition GET requests do not have. --- src/retry/client.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/retry/client.rs b/src/retry/client.rs index 484020a2..68c549c5 100644 --- a/src/retry/client.rs +++ b/src/retry/client.rs @@ -192,7 +192,6 @@ impl RequestBuilder .chain_err(|| "failed to parse uri")?; let mut req = Request::new(Method::GET, url); req.headers_mut().extend(self.headers.clone().into_iter()); - req.headers_mut().append(header::CONTENT_TYPE, self.d.content_type()); self.retry.clone().retry(|attempt| { info!("Fetching {}: Attempt #{}", req.url(), attempt + 1); From ed8a126c0c114f44b419c97b3e98fb34647221a1 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 21 Feb 2019 16:21:24 -0500 Subject: [PATCH 3/5] retry/client: support POSTs without bodies A POST request doesn't have to have a body. Make the `body` argument an `Option` to reflect that. --- src/providers/azure/mod.rs | 2 +- src/providers/packet/mod.rs | 2 +- src/retry/client.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/providers/azure/mod.rs b/src/providers/azure/mod.rs index ec1a3f4c..02f57923 100644 --- a/src/providers/azure/mod.rs +++ b/src/providers/azure/mod.rs @@ -392,7 +392,7 @@ impl MetadataProvider for Azure { fn boot_checkin(&self) -> Result<()> { let body = ready_state!(self.container_id(), self.instance_id()?); let url = self.fabric_base_url() + "/machine/?comp=health"; - self.client.post(retry::Xml, url, body.into()) + self.client.post(retry::Xml, url, Some(body.into())) .dispatch_post()?; Ok(()) } diff --git a/src/providers/packet/mod.rs b/src/providers/packet/mod.rs index 0464cc1f..8a8865f1 100644 --- a/src/providers/packet/mod.rs +++ b/src/providers/packet/mod.rs @@ -302,7 +302,7 @@ impl MetadataProvider for PacketProvider { let client = retry::Client::try_new()?; let url = self.data.phone_home_url.clone() + "/events"; let body = serde_json::to_string(&user_state)?; - client.post(retry::Json, url, body.into()) + client.post(retry::Json, url, Some(body.into())) .dispatch_post()?; Ok(()) } diff --git a/src/retry/client.rs b/src/retry/client.rs index 68c549c5..929fcd6d 100644 --- a/src/retry/client.rs +++ b/src/retry/client.rs @@ -148,12 +148,12 @@ impl Client { } } - pub fn post(&self, d: D, url: String, body: Cow) -> RequestBuilder + pub fn post(&self, d: D, url: String, body: Option>) -> RequestBuilder where D: Deserializer { RequestBuilder{ url, - body: Some(body.into_owned()), + body: body.map(|b| b.into_owned()), d, client: self.client.clone(), headers: self.headers.clone(), From a71315006a4aa539b3047201af241f2de23e07b8 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 21 Feb 2019 16:22:20 -0500 Subject: [PATCH 4/5] packet: use correct phone home URL First, we were incorrectly building the `/events` URL. But second, POSTing a "succeeded" message is not a substitute for phoning home; we still need to do that. In which case, there's no need to POST to `/events` also. So let's just simplify things and do a POST only to `/phone-home`, just like `packet-phone-home.service`. --- src/main.rs | 1 - src/providers/packet/mock_tests.rs | 12 +++--------- src/providers/packet/mod.rs | 21 ++------------------- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/main.rs b/src/main.rs index f81faf06..70d5d081 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,6 @@ extern crate reqwest; #[macro_use] extern crate serde_derive; extern crate serde; -#[cfg_attr(test, macro_use)] extern crate serde_json; extern crate serde_xml_rs; #[macro_use] diff --git a/src/providers/packet/mock_tests.rs b/src/providers/packet/mock_tests.rs index 134be6b0..0bddd9d5 100644 --- a/src/providers/packet/mock_tests.rs +++ b/src/providers/packet/mock_tests.rs @@ -3,7 +3,6 @@ use providers::{packet, MetadataProvider}; #[test] fn test_boot_checkin() { - let ep = "/events"; let data = packet::PacketData { id: String::new(), hostname: String::new(), @@ -22,14 +21,9 @@ fn test_boot_checkin() { }; let provider = packet::PacketProvider { data }; - let json_body = json!({ - "state": "succeeded", - "code": 1042, - "message": "coreos-metadata: boot check-in", - }); - let mock = mockito::mock("POST", ep) - .match_header("content-type", Matcher::Regex("text/json.*".to_string())) - .match_body(Matcher::Json(json_body)) + let mock = mockito::mock("POST", "/") + .match_header("content-type", Matcher::Regex("application/json".to_string())) + .match_body("") .with_status(200) .create(); diff --git a/src/providers/packet/mod.rs b/src/providers/packet/mod.rs index 8a8865f1..3b355e77 100644 --- a/src/providers/packet/mod.rs +++ b/src/providers/packet/mod.rs @@ -24,7 +24,6 @@ use std::str::FromStr; use openssh_keys::PublicKey; use pnet::util::MacAddr; -use serde_json; use update_ssh_keys::AuthorizedKeyEntry; use errors::*; @@ -83,15 +82,6 @@ struct PacketAddressInfo { gateway: IpAddr, } -/// Custom user-state that can be posted on instance -/// boot to Packet phone-home events endpoint. -#[derive(Clone, Debug, Serialize)] -pub(crate) struct PacketUserState { - state: String, - code: u16, - message: String, -} - #[derive(Clone, Debug)] pub struct PacketProvider { data: PacketData, @@ -294,16 +284,9 @@ impl MetadataProvider for PacketProvider { } fn boot_checkin(&self) -> Result<()> { - let user_state = PacketUserState { - state: "succeeded".into(), - code: 1042, - message: "coreos-metadata: boot check-in".into(), - }; let client = retry::Client::try_new()?; - let url = self.data.phone_home_url.clone() + "/events"; - let body = serde_json::to_string(&user_state)?; - client.post(retry::Json, url, Some(body.into())) - .dispatch_post()?; + let url = self.data.phone_home_url.clone(); + client.post(retry::Json, url, None).dispatch_post()?; Ok(()) } } From 72e48d866b057cede20da936788d7a04f5ffb014 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 21 Feb 2019 16:31:36 -0500 Subject: [PATCH 5/5] retry/client: fix JSON MIME type The MIME type for JSON is `application/json`, not `text/json`. Packet was choking on this as a result. --- src/retry/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/retry/client.rs b/src/retry/client.rs index 929fcd6d..3f4aa74b 100644 --- a/src/retry/client.rs +++ b/src/retry/client.rs @@ -67,7 +67,7 @@ impl Deserializer for Json { .chain_err(|| "failed json deserialization") } fn content_type(&self) -> header::HeaderValue { - header::HeaderValue::from_static("text/json; charset=utf-8") + header::HeaderValue::from_static("application/json") } }