Skip to content

Commit

Permalink
openstack: use config-drive by default
Browse files Browse the repository at this point in the history
This changes afterburn to use config-drive as default for scraping
metadata and then try metadata API if config-drive is not available.
Also changes the name of the platform from `openstack-metadata` to
`openstack`.

Closes: coreos/fedora-coreos-tracker#422
Signed-off-by: Allen Bai <abai@redhat.com>
  • Loading branch information
Allen Bai committed Jul 22, 2020
1 parent ff48266 commit 4f52a9e
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 11 deletions.
8 changes: 6 additions & 2 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ use crate::providers::exoscale::ExoscaleProvider;
use crate::providers::gcp::GcpProvider;
use crate::providers::ibmcloud::IBMGen2Provider;
use crate::providers::ibmcloud_classic::IBMClassicProvider;
use crate::providers::openstack::network::OpenstackProvider;
use crate::providers::openstack::configdrive::OpenstackConfigDrive;
use crate::providers::openstack::network::OpenstackProviderNetwork;
use crate::providers::packet::PacketProvider;
#[cfg(feature = "cl-legacy")]
use crate::providers::vagrant_virtualbox::VagrantVirtualboxProvider;
Expand Down Expand Up @@ -62,7 +63,10 @@ pub fn fetch_metadata(provider: &str) -> errors::Result<Box<dyn providers::Metad
"ibmcloud" => box_result!(IBMGen2Provider::try_new()?),
// IBM Cloud - Classic infrastructure.
"ibmcloud-classic" => box_result!(IBMClassicProvider::try_new()?),
"openstack-metadata" => box_result!(OpenstackProvider::try_new()?),
"openstack" => match OpenstackConfigDrive::try_new() {
Ok(result) => box_result!(result),
Err(_) => box_result!(OpenstackProviderNetwork::try_new()?),
},
"packet" => box_result!(PacketProvider::try_new()?),
#[cfg(feature = "cl-legacy")]
"vagrant-virtualbox" => box_result!(VagrantVirtualboxProvider::new()),
Expand Down
167 changes: 167 additions & 0 deletions src/providers/openstack/configdrive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
//! configdrive metadata fetcher for OpenStack
//! reference: https://docs.openstack.org/nova/latest/user/metadata.html

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

use openssh_keys::PublicKey;
use slog_scope::{error, warn};
use tempfile::TempDir;

use crate::errors::*;
use crate::network;
use crate::providers::MetadataProvider;

const CONFIG_DRIVE_LABEL: &str = "config-2";

/// OpenStack config-drive.
#[derive(Debug)]
pub struct OpenstackConfigDrive {
/// Path to the top directory of the mounted config-drive.
drive_path: PathBuf,
/// Temporary directory for own mountpoint (if any).
temp_dir: Option<TempDir>,
}

impl OpenstackConfigDrive {
/// Try to build a new provider client.
///
/// This internally tries to mount (and own) the config-drive.
pub fn try_new() -> Result<Self> {
// Short-circuit if the config-drive is already mounted.
let path = Path::new("/media/ConfigDrive/openstack/latest/");
if path.exists() {
return Ok(OpenstackConfigDrive {
temp_dir: None,
drive_path: PathBuf::from("/media/ConfigDrive/"),
});
}

// Otherwise, try and mount with the label.
let target = tempfile::Builder::new()
.prefix("afterburn-")
.tempdir()
.chain_err(|| "failed to create temporary directory")?;
crate::util::mount_ro(
&Path::new("/dev/disk/by-label/").join(CONFIG_DRIVE_LABEL),
target.path(),
"iso9660",
3,
)?;

let cd = OpenstackConfigDrive {
drive_path: target.path().to_owned(),
temp_dir: Some(target),
};
Ok(cd)
}

/// Return the path to the metadata directory.
fn metadata_dir(&self, platform: &str) -> PathBuf {
self.drive_path.clone().join(platform).join("latest")
}

// The metadata is stored as key:value pair in ec2/meta-data.json file
fn fetch_value(&self, key: &str) -> Result<Option<String>> {
let filename = self.metadata_dir("ec2").join("meta-data.json");

if !filename.exists() {
return Ok(None);
}

let mut file =
File::open(&filename).chain_err(|| format!("failed to open file '{:?}'", filename))?;

let mut contents = String::new();
file.read_to_string(&mut contents)
.chain_err(|| format!("failed to read from file '{:?}'", filename))?;
let contents_json: serde_json::Value = serde_json::from_str(&contents)?;

if let Some(value) = contents_json[key].as_str() {
Ok(Some(value.to_owned()))
} else {
Ok(None)
}
}

fn fetch_publickeys(&self) -> Result<Vec<PublicKey>> {
let filename = self.metadata_dir("openstack").join("meta_data.json");
let mut file =
File::open(&filename).chain_err(|| format!("failed to open file '{:?}'", filename))?;

let mut contents = String::new();
file.read_to_string(&mut contents)
.chain_err(|| format!("failed to read from file '{:?}'", filename))?;
let contents_json: serde_json::Value = serde_json::from_str(&contents)?;

if let Some(public_keys_map) = contents_json["public_keys"].as_object() {
let public_keys_vec: Vec<&std::string::String> =
public_keys_map.keys().collect();
let mut out = vec![];
for key in public_keys_vec {
let key = PublicKey::parse(&key)?;
out.push(key);
}
Ok(out)
} else {
Ok(vec![])
}
}
}

impl MetadataProvider for OpenstackConfigDrive {
fn attributes(&self) -> Result<HashMap<String, String>> {
let mut out = HashMap::with_capacity(5);
let add_value = |map: &mut HashMap<_, _>, key: &str, name| -> Result<()> {
let value = self.fetch_value(name)?;

if let Some(value) = value {
map.insert(key.to_string(), value);
}

Ok(())
};

add_value(&mut out, "OPENSTACK_HOSTNAME", "hostname")?;
add_value(&mut out, "OPENSTACK_INSTANCE_ID", "instance-id")?;
add_value(&mut out, "OPENSTACK_INSTANCE_TYPE", "instance-type")?;
add_value(&mut out, "OPENSTACK_IPV4_LOCAL", "local-ipv4")?;
add_value(&mut out, "OPENSTACK_IPV4_PUBLIC", "public-ipv4")?;

Ok(out)
}

fn hostname(&self) -> Result<Option<String>> {
Ok(None)
}

fn ssh_keys(&self) -> Result<Vec<PublicKey>> {
self.fetch_publickeys()
}

fn networks(&self) -> Result<Vec<network::Interface>> {
Ok(vec![])
}

fn virtual_network_devices(&self) -> Result<Vec<network::VirtualNetDev>> {
warn!("virtual network devices metadata requested, but not supported on this platform");
Ok(vec![])
}

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

impl Drop for OpenstackConfigDrive {
fn drop(&mut self) {
if self.temp_dir.is_some() {
if let Err(e) = crate::util::unmount(&self.drive_path, 3) {
error!("failed to cleanup OpenStack config-drive: {}", e);
};
}
}
}
1 change: 1 addition & 0 deletions src/providers/openstack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@

//! openstack metadata fetcher

pub mod configdrive;
pub mod network;
24 changes: 15 additions & 9 deletions src/providers/openstack/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ use crate::retry;
const URL: &str = "http://169.254.169.254/latest/meta-data";

#[derive(Clone, Debug)]
pub struct OpenstackProvider {
pub struct OpenstackProviderNetwork {
client: retry::Client,
}

impl OpenstackProvider {
pub fn try_new() -> Result<OpenstackProvider> {
impl OpenstackProviderNetwork {
pub fn try_new() -> Result<OpenstackProviderNetwork> {
let client = retry::Client::try_new()?;
Ok(OpenstackProvider { client })
Ok(OpenstackProviderNetwork { client })
}

fn endpoint_for(key: &str) -> String {
Expand All @@ -30,7 +30,10 @@ impl OpenstackProvider {
fn fetch_keys(&self) -> Result<Vec<String>> {
let keys_list: Option<String> = self
.client
.get(retry::Raw, OpenstackProvider::endpoint_for("public-keys"))
.get(
retry::Raw,
OpenstackProviderNetwork::endpoint_for("public-keys"),
)
.send()?;
let mut keys = Vec::new();
if let Some(keys_list) = keys_list {
Expand All @@ -43,7 +46,7 @@ impl OpenstackProvider {
.client
.get(
retry::Raw,
OpenstackProvider::endpoint_for(&format!(
OpenstackProviderNetwork::endpoint_for(&format!(
"public-keys/{}/openssh-key",
tokens[0]
)),
Expand All @@ -57,14 +60,14 @@ impl OpenstackProvider {
}
}

impl MetadataProvider for OpenstackProvider {
impl MetadataProvider for OpenstackProviderNetwork {
fn attributes(&self) -> Result<HashMap<String, String>> {
let mut out = HashMap::with_capacity(4);

let add_value = |map: &mut HashMap<_, _>, key: &str, name| -> Result<()> {
let value = self
.client
.get(retry::Raw, OpenstackProvider::endpoint_for(name))
.get(retry::Raw, OpenstackProviderNetwork::endpoint_for(name))
.send()?;
if let Some(value) = value {
map.insert(key.to_string(), value);
Expand All @@ -83,7 +86,10 @@ impl MetadataProvider for OpenstackProvider {

fn hostname(&self) -> Result<Option<String>> {
self.client
.get(retry::Raw, OpenstackProvider::endpoint_for("hostname"))
.get(
retry::Raw,
OpenstackProviderNetwork::endpoint_for("hostname"),
)
.send()
}

Expand Down

0 comments on commit 4f52a9e

Please sign in to comment.