Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,23 @@
#![allow(deprecated)]

use error_chain::error_chain;
use reqwest::header;
use serde_json;

error_chain! {
links {
PublicKey(::openssh_keys::errors::Error, ::openssh_keys::errors::ErrorKind);
AuthorizedKeys(::update_ssh_keys::errors::Error, ::update_ssh_keys::errors::ErrorKind) #[cfg(feature = "cl-legacy")];
PublicKey(::openssh_keys::errors::Error, ::openssh_keys::errors::ErrorKind);
}
foreign_links {
Log(::slog::Error);
XmlDeserialize(::serde_xml_rs::Error);
Base64Decode(::base64::DecodeError);
HeaderValue(reqwest::header::InvalidHeaderValue);
Io(::std::io::Error);
IpNetwork(ipnetwork::IpNetworkError);
Json(serde_json::Error);
Reqwest(::reqwest::Error);
Log(::slog::Error);
MacAddr(pnet_base::ParseMacAddrErr);
OpensslStack(::openssl::error::ErrorStack);
HeaderValue(header::InvalidHeaderValue);
Reqwest(::reqwest::Error);
XmlDeserialize(::serde_xml_rs::Error);
}
errors {
UnknownProvider(p: String) {
Expand Down
6 changes: 6 additions & 0 deletions src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ pub fn bonding_mode_to_string(mode: u32) -> Result<String> {
Err(format!("no such bonding mode: {}", mode).into())
}

/// Try to parse an IP+netmask pair into a CIDR network.
pub fn try_parse_cidr(address: IpAddr, netmask: IpAddr) -> Result<IpNetwork> {
let prefix = ipnetwork::ip_mask_to_prefix(netmask)?;
IpNetwork::new(address, prefix).chain_err(|| "failed to parse network")
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NetworkRoute {
pub destination: IpNetwork,
Expand Down
166 changes: 159 additions & 7 deletions src/providers/ibmcloud/classic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
//!
//! configdrive: https://cloudinit.readthedocs.io/en/latest/topics/datasources/configdrive.html

use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};

use error_chain::bail;
use openssh_keys::PublicKey;
use pnet_base::MacAddr;
use serde::Deserialize;
use slog_scope::warn;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, Read};
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use tempdir::TempDir;

use crate::errors::*;
Expand Down Expand Up @@ -57,6 +58,60 @@ pub struct MetaDataJSON {
pub public_keys: HashMap<String, String>,
}

/// Partial object for `network_data.json`
#[derive(Debug, Deserialize)]
pub struct NetworkDataJSON {
pub links: Vec<NetLinkJSON>,
pub networks: Vec<NetNetworkJSON>,
pub services: Vec<NetServiceJSON>,
}

/// JSON entry in `links` array.
#[derive(Debug, Deserialize)]
pub struct NetLinkJSON {
pub name: String,
pub id: String,
#[serde(rename = "ethernet_mac_address")]
pub mac_addr: String,
}

/// JSON entry in `networks` array.
#[derive(Debug, Deserialize)]
pub struct NetNetworkJSON {
/// Unique network ID.
pub id: String,
/// Network type (e.g. `ipv4`)
#[serde(rename = "type")]
pub kind: String,
/// Reference to the underlying interface (see `NetLinkJSON.id`)
pub link: String,
/// IP network address.
pub ip_address: IpAddr,
/// IP network mask.
pub netmask: IpAddr,
/// Routable networks.
pub routes: Vec<NetRouteJSON>,
}

/// JSON entry in `networks.routes` array.
#[derive(Debug, Deserialize)]
pub struct NetRouteJSON {
/// Route network address.
pub network: IpAddr,
/// Route netmask.
pub netmask: IpAddr,
/// Route gateway.
pub gateway: IpAddr,
}

/// JSON entry in `services` array.
#[derive(Debug, Deserialize)]
pub struct NetServiceJSON {
#[serde(rename = "type")]
pub kind: String,
pub address: IpAddr,
}

impl ClassicProvider {
/// Try to build a new provider client.
///
Expand Down Expand Up @@ -120,6 +175,86 @@ impl ClassicProvider {
};
Ok(attrs)
}

/// Read and parse network configuration.
fn read_network_data(&self) -> Result<NetworkDataJSON> {
let filename = self.metadata_dir().join("network_data.json");
let file =
File::open(&filename).chain_err(|| format!("failed to open file '{:?}'", filename))?;
let bufrd = BufReader::new(file);
Self::parse_network_data(bufrd)
}

/// Parse network configuration.
///
/// Network configuration file contains a JSON object, corresponding to `NetworkDataJSON`.
fn parse_network_data<T: Read>(input: BufReader<T>) -> Result<NetworkDataJSON> {
serde_json::from_reader(input).chain_err(|| "failed parse JSON network data")
}

/// Transform network JSON data into a set of interface configurations.
fn network_interfaces(input: NetworkDataJSON) -> Result<Vec<network::Interface>> {
use std::str::FromStr;

// Validate links and parse them into a map, keyed by id.
let mut devices: HashMap<String, (String, MacAddr)> =
HashMap::with_capacity(input.links.len());
for dev in input.links {
let mac = MacAddr::from_str(&dev.mac_addr)?;
devices.insert(dev.id, (dev.name, mac));
}

// Parse resolvers.
let nameservers: Vec<IpAddr> = input
.services
.into_iter()
.filter_map(|svc| {
if svc.kind == "dns" {
Some(svc.address)
} else {
None
}
})
.collect();

let mut output = Vec::with_capacity(input.networks.len());
for net in input.networks {
// Ensure that the referenced link exists.
let (name, mac_addr) = match devices.get(&net.link) {
Some(dev) => (dev.0.clone(), dev.1),
None => continue,
};

// Assemble network CIDR.
let ip_net = network::try_parse_cidr(net.ip_address, net.netmask)?;

// Parse network routes.
let mut routes = Vec::with_capacity(net.routes.len());
for entry in net.routes {
let destination = network::try_parse_cidr(entry.network, entry.netmask)?;
let route = network::NetworkRoute {
destination,
gateway: entry.gateway,
};
routes.push(route);
}

let iface = network::Interface {
name: Some(name),
mac_address: Some(mac_addr),
priority: 10,
nameservers: nameservers.clone(),
ip_addresses: vec![ip_net],
routes,
bond: None,
unmanaged: false,
};
output.push(iface);
}

output.shrink_to_fit();
Ok(output)
}
}

impl MetadataProvider for ClassicProvider {
Expand All @@ -144,8 +279,9 @@ impl MetadataProvider for ClassicProvider {
}

fn networks(&self) -> Result<Vec<network::Interface>> {
warn!("network metadata requested, but not supported on this platform");
Ok(vec![])
let data = self.read_network_data()?;
let interfaces = Self::network_interfaces(data)?;
Ok(interfaces)
}

fn virtual_network_devices(&self) -> Result<Vec<network::VirtualNetDev>> {
Expand Down Expand Up @@ -217,4 +353,20 @@ mod tests {
assert!(!parsed.local_hostname.is_empty());
assert!(!parsed.public_keys.is_empty());
}

#[test]
fn test_parse_network_data_json() {
let fixture = File::open("./tests/fixtures/ibmcloud/classic/network_data.json").unwrap();
let bufrd = BufReader::new(fixture);
let parsed = ClassicProvider::parse_network_data(bufrd).unwrap();

let interfaces = ClassicProvider::network_interfaces(parsed).unwrap();
assert_eq!(interfaces.len(), 2);
assert_eq!(interfaces[0].routes.len(), 3);
assert_eq!(interfaces[1].routes.len(), 1);

for entry in interfaces {
assert_eq!(entry.nameservers.len(), 2);
}
}
}
68 changes: 68 additions & 0 deletions tests/fixtures/ibmcloud/classic/network_data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"links": [
{
"id": "interface_58965010",
"name": "eth0",
"mtu": null,
"type": "phy",
"ethernet_mac_address": "06:52:db:01:ff:d9"
},
{
"id": "interface_58965014",
"name": "eth1",
"mtu": null,
"type": "phy",
"ethernet_mac_address": "06:f6:71:3b:64:01"
}
],
"networks": [
{
"id": "network_142213448",
"link": "interface_58965010",
"type": "ipv4",
"ip_address": "10.135.202.146",
"netmask": "255.255.255.192",
"routes": [
{
"network": "10.0.0.0",
"netmask": "255.0.0.0",
"gateway": "10.135.202.129"
},
{
"network": "161.26.0.0",
"netmask": "255.255.0.0",
"gateway": "10.135.202.129"
},
{
"network": "166.8.0.0",
"netmask": "255.252.0.0",
"gateway": "10.135.202.129"
}
]
},
{
"id": "network_142211974",
"link": "interface_58965014",
"type": "ipv4",
"ip_address": "133.133.209.229",
"netmask": "255.255.255.240",
"routes": [
{
"network": "0.0.0.0",
"netmask": "0.0.0.0",
"gateway": "133.133.209.225"
}
]
}
],
"services": [
{
"type": "dns",
"address": "10.0.80.11"
},
{
"type": "dns",
"address": "10.0.80.12"
}
]
}