Skip to content

Commit

Permalink
Merge pull request #147 from jlebon/pr/checkin
Browse files Browse the repository at this point in the history
provider: add boot check-in on azure and packet
  • Loading branch information
Luca Bruno committed Mar 5, 2019
2 parents aa2cdd6 + 72e48d8 commit 0ccef66
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 10 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
11 changes: 11 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,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 +111,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 +149,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("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"))
Expand Down Expand Up @@ -174,6 +184,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();
}
82 changes: 75 additions & 7 deletions src/providers/azure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -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#"<?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 @@ -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)]
Expand All @@ -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)]
Expand Down Expand Up @@ -177,11 +205,12 @@ 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() -> 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| {
Expand All @@ -191,8 +220,9 @@ impl Azure {
})
}

#[cfg(not(test))]
fn get_fabric_address_from_dhcp() -> 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 @@ -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")?;

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -328,4 +388,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, Some(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(())
}
}
33 changes: 33 additions & 0 deletions src/providers/packet/mock_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use mockito::{self, Matcher};
use providers::{packet, MetadataProvider};

#[test]
fn test_boot_checkin() {
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 mock = mockito::mock("POST", "/")
.match_header("content-type", Matcher::Regex("application/json".to_string()))
.match_body("")
.with_status(200)
.create();

let r = provider.boot_checkin();
mock.assert();
r.unwrap();
}
Loading

0 comments on commit 0ccef66

Please sign in to comment.