Skip to content

Commit

Permalink
provider: add boot check-in on azure and packet
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lucab committed Jan 2, 2019
1 parent 6599a53 commit 2b9c7b9
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

use reqwest::header;
use serde_json;

error_chain!{
links {
Expand All @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern crate reqwest;
#[macro_use]
extern crate serde_derive;
extern crate serde;
#[macro_use]
extern crate serde_json;
extern crate serde_xml_rs;
#[macro_use]
Expand Down Expand Up @@ -63,6 +64,7 @@ const CMDLINE_OEM_FLAG: &str = "coreos.oem.id";
struct Config {
provider: String,
attributes_file: Option<String>,
check_in: bool,
ssh_keys_user: Option<String>,
hostname_file: Option<String>,
network_units_dir: Option<String>,
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -142,6 +150,9 @@ fn init() -> Result<Config> {
.long("attributes")
.help("The file into which the metadata attributes are written")
.takes_value(true))
.arg(Arg::with_name("check-in")
.long("cmdline")
.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"))
Expand Down Expand Up @@ -174,6 +185,7 @@ fn init() -> Result<Config> {
}
},
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),
Expand Down
82 changes: 82 additions & 0 deletions src/providers/azure/mock_tests.rs
Original file line number Diff line number Diff line change
@@ -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#"<?xml version="1.0" encoding="utf-8"?>
<Versions>
<Preferred>
<Version>2015-04-05</Version>
</Preferred>
<Supported>
<Version>2015-04-05</Version>
<Version>2012-11-30</Version>
<Version>2012-09-15</Version>
<Version>2012-05-15</Version>
<Version>2011-12-31</Version>
<Version>2011-10-15</Version>
<Version>2011-08-31</Version>
<Version>2011-04-07</Version>
<Version>2010-12-15</Version>
<Version>2010-28-10</Version>
</Supported>
</Versions>"#;
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#"<?xml version="1.0" encoding="utf-8"?>
<GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
<Version>2012-11-30</Version>
<Incarnation>1</Incarnation>
<Machine>
<ExpectedState>Started</ExpectedState>
<StopRolesDeadlineHint>300000</StopRolesDeadlineHint>
<LBProbePorts>
<Port>16001</Port>
</LBProbePorts>
<ExpectHealthReport>FALSE</ExpectHealthReport>
</Machine>
<Container>
<ContainerId>a511aa6d-29e7-4f53-8788-55655dfe848f</ContainerId>
<RoleInstanceList>
<RoleInstance>
<InstanceId>f6cd1d7ef1644557b9059345e5ba890c.lars-test-1</InstanceId>
<State>Started</State>
<Configuration>
<HostingEnvironmentConfig>http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=config&amp;type=hostingEnvironmentConfig&amp;incarnation=1</HostingEnvironmentConfig>
<SharedConfig>http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=config&amp;type=sharedConfig&amp;incarnation=1</SharedConfig>
<ExtensionsConfig>http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=config&amp;type=extensionsConfig&amp;incarnation=1</ExtensionsConfig>
<FullConfig>http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=config&amp;type=fullConfig&amp;incarnation=1</FullConfig>
<Certificates>http://100.115.176.3:80/machine/a511aa6d-29e7-4f53-8788-55655dfe848f/f6cd1d7ef1644557b9059345e5ba890c.lars%2Dtest%2D1?comp=certificates&amp;incarnation=1</Certificates>
<ConfigName>f6cd1d7ef1644557b9059345e5ba890c.0.f6cd1d7ef1644557b9059345e5ba890c.0.lars-test-1.1.xml</ConfigName>
</Configuration>
</RoleInstance>
</RoleInstanceList>
</Container>
</GoalState>
"#;
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("<State>Ready</State>".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();
}
81 changes: 73 additions & 8 deletions src/providers/azure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
mod crypto;

use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use std::net::{self, IpAddr, SocketAddr};

use openssh_keys::PublicKey;
use reqwest::header::{HeaderName, HeaderValue};
Expand All @@ -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 = "\
Expand All @@ -46,6 +47,28 @@ Content-Transfer-Encoding: base64
";

macro_rules! ready_state {
($container:expr, $instance:expr) => {
format!(r#"<?xml version="1.0" encoding="utf-8"?>
<Health xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GoalStateIncarnation>1</GoalStateIncarnation>
<Container>
<ContainerId>{}</ContainerId>
<RoleInstanceList>
<Role>
<InstanceId>{}</InstanceId>
<Health>
<State>Ready</State>
</Health>
</Role>
</RoleInstanceList>
</Container>
</Health>
"#,
$container, $instance)
}
}

#[derive(Debug, Deserialize, Clone, Default)]
struct GoalState {
#[serde(rename = "Container")]
Expand All @@ -54,8 +77,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)]
Expand All @@ -67,7 +92,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)]
Expand Down Expand Up @@ -174,13 +201,14 @@ impl Azure {
}

fn get_goal_state(&self) -> Result<GoalState> {
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() -> Result<IpAddr> {
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
Expand All @@ -190,8 +218,23 @@ 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() -> Result<IpAddr> {
Ok(IpAddr::from(net::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")?;

Expand Down Expand Up @@ -282,6 +325,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 {
Expand Down Expand Up @@ -316,4 +373,12 @@ impl MetadataProvider for Azure {
fn network_devices(&self) -> Result<Vec<network::Device>> {
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(())
}
}
5 changes: 5 additions & 0 deletions src/providers/cloudstack/configdrive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ impl MetadataProvider for ConfigDrive {
fn network_devices(&self) -> Result<Vec<network::Device>> {
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 {
Expand Down
5 changes: 5 additions & 0 deletions src/providers/cloudstack/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,9 @@ impl MetadataProvider for CloudstackNetwork {
fn network_devices(&self) -> Result<Vec<network::Device>> {
Ok(vec![])
}

fn boot_checkin(&self) -> Result<()> {
warn!("boot check-in requested, but not supported on this platform");
Ok(())
}
}
5 changes: 5 additions & 0 deletions src/providers/digitalocean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,9 @@ impl MetadataProvider for DigitalOceanProvider {
fn network_devices(&self) -> Result<Vec<network::Device>> {
Ok(vec![])
}

fn boot_checkin(&self) -> Result<()> {
warn!("boot check-in requested, but not supported on this platform");
Ok(())
}
}
5 changes: 5 additions & 0 deletions src/providers/ec2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,9 @@ impl MetadataProvider for Ec2Provider {
fn network_devices(&self) -> Result<Vec<network::Device>> {
Ok(vec![])
}

fn boot_checkin(&self) -> Result<()> {
warn!("boot check-in requested, but not supported on this platform");
Ok(())
}
}
5 changes: 5 additions & 0 deletions src/providers/gce/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,9 @@ impl MetadataProvider for GceProvider {
fn network_devices(&self) -> Result<Vec<network::Device>> {
Ok(vec![])
}

fn boot_checkin(&self) -> Result<()> {
warn!("boot check-in requested, but not supported on this platform");
Ok(())
}
}
1 change: 1 addition & 0 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub trait MetadataProvider {
fn ssh_keys(&self) -> Result<Vec<AuthorizedKeyEntry>>;
fn networks(&self) -> Result<Vec<network::Interface>>;
fn network_devices(&self) -> Result<Vec<network::Device>>;
fn boot_checkin(&self) -> Result<()>;

fn write_attributes(&self, attributes_file_path: String) -> Result<()> {
let mut attributes_file = create_file(&attributes_file_path)?;
Expand Down
5 changes: 5 additions & 0 deletions src/providers/openstack/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,9 @@ impl MetadataProvider for OpenstackProvider {
fn network_devices(&self) -> Result<Vec<network::Device>> {
Ok(vec![])
}

fn boot_checkin(&self) -> Result<()> {
warn!("boot check-in requested, but not supported on this platform");
Ok(())
}
}
Loading

0 comments on commit 2b9c7b9

Please sign in to comment.