-
Notifications
You must be signed in to change notification settings - Fork 104
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
As part of supporting Power Virtual server as a platform (coreos/fedora-coreos-tracker#817), Afterburn will be used to set the hostname attributes and possibly the ssh-keys. This provider is very similar to the IBMCloud classic provider with the exception of the filesystem for the config drive which is `iso9660` similar to Openstack. The contents of the config drive are usually the meta_data.json and the network_data.json. This provider includes support for: - hostname - ssh keys
- Loading branch information
1 parent
a5d98ae
commit 542ee1b
Showing
6 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
//! Metadata fetcher for PowerVS instances. | ||
//! | ||
//! This provider supports the Power Virtual Server infrastructure type on IBMCloud. | ||
//! It provides a config-drive as the only metadata source, whose layout | ||
//! follows the `cloud-init ConfigDrive v2` [datasource][configdrive], with | ||
//! the following details: | ||
//! - disk filesystem label is `config-2` (lowercase) | ||
//! - filesystem is `iso9660` | ||
//! - drive contains a single directory at `/openstack/latest/` | ||
//! - content is exposed as JSON files called `meta_data.json`. | ||
//! | ||
//! configdrive: https://cloudinit.readthedocs.io/en/latest/topics/datasources/configdrive.html | ||
|
||
use anyhow::{bail, Context, Result}; | ||
use openssh_keys::PublicKey; | ||
use serde::Deserialize; | ||
use slog_scope::warn; | ||
use std::collections::HashMap; | ||
use std::fs::File; | ||
use std::io::{BufReader, Read}; | ||
use std::path::{Path, PathBuf}; | ||
use tempfile::TempDir; | ||
|
||
use crate::network; | ||
use crate::providers::MetadataProvider; | ||
|
||
// Filesystem label for the Config Drive. | ||
static CONFIG_DRIVE_FS_LABEL: &str = "config-2"; | ||
|
||
// Filesystem type for the Config Drive. | ||
static CONFIG_DRIVE_FS_TYPE: &str = "iso9660"; | ||
|
||
///PowerVS provider. | ||
#[derive(Debug)] | ||
pub struct PowerVSProvider { | ||
/// Path to the top directory of the mounted config-drive. | ||
drive_path: PathBuf, | ||
/// Temporary directory for own mountpoint. | ||
temp_dir: TempDir, | ||
} | ||
|
||
/// Partial object for `meta_data.json` | ||
#[derive(Debug, Deserialize)] | ||
pub struct MetaDataJSON { | ||
/// Fully-Qualified Domain Name (FQDN). | ||
#[serde(rename = "hostname")] | ||
pub fqdn: String, | ||
/// Local hostname. | ||
#[serde(rename = "name")] | ||
pub local_hostname: String, | ||
/// Instance ID (UUID). | ||
#[serde(rename = "uuid")] | ||
pub instance_id: String, | ||
/// SSH public keys. | ||
pub public_keys: Option<HashMap<String, String>>, | ||
} | ||
|
||
impl PowerVSProvider { | ||
/// Try to build a new provider client. | ||
/// | ||
/// This internally tries to mount (and own) the config-drive. | ||
pub fn try_new() -> Result<Self> { | ||
let target = tempfile::Builder::new() | ||
.prefix("afterburn-") | ||
.tempdir() | ||
.context("failed to create temporary directory")?; | ||
crate::util::mount_ro( | ||
&Path::new("/dev/disk/by-label/").join(CONFIG_DRIVE_FS_LABEL), | ||
target.path(), | ||
CONFIG_DRIVE_FS_TYPE, | ||
3, // maximum retries | ||
)?; | ||
|
||
let provider = Self { | ||
drive_path: target.path().to_owned(), | ||
temp_dir: target, | ||
}; | ||
Ok(provider) | ||
} | ||
|
||
/// Return the path to the metadata directory. | ||
fn metadata_dir(&self) -> PathBuf { | ||
let drive = self.drive_path.clone(); | ||
drive.join("openstack").join("latest") | ||
} | ||
|
||
/// Read and parse metadata file. | ||
fn read_metadata(&self) -> Result<MetaDataJSON> { | ||
let filename = self.metadata_dir().join("meta_data.json"); | ||
let file = File::open(&filename) | ||
.with_context(|| format!("failed to open file '{:?}'", filename))?; | ||
let bufrd = BufReader::new(file); | ||
Self::parse_metadata(bufrd) | ||
} | ||
|
||
/// Parse metadata attributes. | ||
/// | ||
/// Metadata file contains a JSON object, corresponding to `MetaDataJSON`. | ||
fn parse_metadata<T: Read>(input: BufReader<T>) -> Result<MetaDataJSON> { | ||
serde_json::from_reader(input).context("failed to parse JSON metadata") | ||
} | ||
|
||
/// Extract supported metadata values and convert to Afterburn attributes. | ||
/// | ||
/// The `AFTERBURN_` prefix is added later on, so it is not part of the | ||
/// key-labels here. | ||
fn known_attributes(metadata: MetaDataJSON) -> Result<HashMap<String, String>> { | ||
if metadata.instance_id.is_empty() { | ||
bail!("empty instance ID"); | ||
} | ||
|
||
if metadata.local_hostname.is_empty() { | ||
bail!("empty local hostname"); | ||
} | ||
|
||
let attrs = maplit::hashmap! { | ||
"POWERVS_INSTANCE_ID".to_string() => metadata.instance_id, | ||
"POWERVS_LOCAL_HOSTNAME".to_string() => metadata.local_hostname, | ||
|
||
}; | ||
Ok(attrs) | ||
} | ||
|
||
/// The public key is stored as key:value pair in openstack/latest/meta_data.json file | ||
fn public_keys(metadata: MetaDataJSON) -> Result<Vec<PublicKey>> { | ||
let public_keys_map = metadata.public_keys.unwrap_or_default(); | ||
let public_keys_vec: Vec<&std::string::String> = public_keys_map.values().collect(); | ||
let mut out = vec![]; | ||
for key in public_keys_vec { | ||
let key = PublicKey::parse(key)?; | ||
out.push(key); | ||
} | ||
Ok(out) | ||
} | ||
} | ||
|
||
impl MetadataProvider for PowerVSProvider { | ||
fn attributes(&self) -> Result<HashMap<String, String>> { | ||
let metadata = self.read_metadata()?; | ||
Self::known_attributes(metadata) | ||
} | ||
|
||
fn hostname(&self) -> Result<Option<String>> { | ||
let metadata = self.read_metadata()?; | ||
let hostname = if metadata.local_hostname.is_empty() { | ||
None | ||
} else { | ||
Some(metadata.local_hostname) | ||
}; | ||
Ok(hostname) | ||
} | ||
|
||
fn ssh_keys(&self) -> Result<Vec<PublicKey>> { | ||
let metadata = self.read_metadata()?; | ||
Self::public_keys(metadata) | ||
} | ||
|
||
fn networks(&self) -> Result<Vec<network::Interface>> { | ||
warn!("network interfaces metadata requested, but not supported on this platform"); | ||
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 PowerVSProvider { | ||
fn drop(&mut self) { | ||
if let Err(e) = crate::util::unmount( | ||
self.temp_dir.path(), | ||
3, // maximum retries | ||
) { | ||
slog_scope::error!("failed to unmount powervs config-drive: {}", e); | ||
}; | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use std::io::Cursor; | ||
|
||
#[test] | ||
fn test_powervs_basic_attributes() { | ||
let metadata = r#" | ||
{ | ||
"hostname": "test_instance-powervs.foo.cloud", | ||
"name": "test_instance-powervs", | ||
"uuid": "41b4fb82-ca29-11eb-b8bc-0242ac130003" | ||
} | ||
"#; | ||
|
||
let bufrd = BufReader::new(Cursor::new(metadata)); | ||
let parsed = PowerVSProvider::parse_metadata(bufrd).unwrap(); | ||
assert_eq!(parsed.instance_id, "41b4fb82-ca29-11eb-b8bc-0242ac130003",); | ||
assert_eq!(parsed.local_hostname, "test_instance-powervs",); | ||
|
||
let attrs = PowerVSProvider::known_attributes(parsed).unwrap(); | ||
assert_eq!(attrs.len(), 2); | ||
assert_eq!( | ||
attrs.get("POWERVS_INSTANCE_ID"), | ||
Some(&"41b4fb82-ca29-11eb-b8bc-0242ac130003".to_string()) | ||
); | ||
assert_eq!( | ||
attrs.get("POWERVS_LOCAL_HOSTNAME"), | ||
Some(&"test_instance-powervs".to_string()) | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_powervs_parse_metadata_json() { | ||
let fixture = File::open("./tests/fixtures/powervs/meta_data.json").unwrap(); | ||
let bufrd = BufReader::new(fixture); | ||
let parsed = PowerVSProvider::parse_metadata(bufrd).unwrap(); | ||
|
||
assert!(!parsed.instance_id.is_empty()); | ||
assert!(!parsed.local_hostname.is_empty()); | ||
assert!(!parsed.public_keys.is_none()); | ||
} | ||
|
||
#[test] | ||
fn test_powervs_ssh_keys() { | ||
let fixture = File::open("./tests/fixtures/powervs/meta_data.json").unwrap(); | ||
let bufrd = BufReader::new(fixture); | ||
let parsed = PowerVSProvider::parse_metadata(bufrd).unwrap(); | ||
let keys = PowerVSProvider::public_keys(parsed).unwrap(); | ||
let expect = PublicKey::parse("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmMuiypdqqftqhrQeBTjOhcgyARvylZMLiH+6nCvi5Lv5M7evAnvvG3Hz4rbjbbqoVgSCIdAEb4PuttiCdwE6UyAl0TYAydOVPx7l87BlaucTEqDFbXkQB+yyUmzodllCpWAMUmxwvJB/ntFrC6rP0K0kKxx4SESvozutwM2X5oH3LNHcYI1xgKIMF9VMJLkkM0rLo8Fmj6mWF5KtbU7vS7JJPvLTCRhW5TYrqvhHKuIS6KBtj3GJqvRt+it8AsIb6/RUaji68Mt7W41UrmFSPt8bxJMdE/xKGMcFQjamURPSCHx7z8/pr2/pv2QbQF76FO7lRdPH3f542uAkOOpO1 user1@user1-MacBook-Pro-2.local").unwrap(); | ||
|
||
assert_eq!(keys.len(), 1); | ||
assert_eq!(keys[0], expect); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"admin_pass": "oSfw8JEKHvBo", | ||
"random_seed": "YUQzy5FVwllOEHrRfvB9rims2ZtotnhJz/f7EVXIknrhh2htbSMxYt1DKxnTzLzWm+tfRjcvWyjie4aU4lIVFrwFgsFmDoAcBIjBID20QVObzKaN0rfrGtdwojyiu+7uGWuQlojoBR/m5pzO1HFcdexEdOSIE+EpU7WIHnS6K948rpH8kERslIz7W6kJixjTpsNanidsvV4DRiSoYdI3wtxyVxBt7ZSzSyuzUIPkbRQ5D/XE/DLs+B8ChRGe9apGCcPvpDRPvot3UUvmwBvtZtaNfH99poKp+j7GOjINT5tc+Wypvgls2EoI/klekzrIOqLmRx/q6UpvvmpoP2PhKT687WMoWNg4uS4d0VbxS2UkydSZoUVCSQmO9O95RdD/jYqXY6q5o7qdr3TZZFf4s1gdoXltb70oQzi7wo4q5Z4QsOsdZdNz5BZ5vtQ06+7neS8ppP5cD6OkQXm+d9bBBWElG5yTeN/zyURpgGdsQlocZzkVlhdVRW9wyeGeN6TGiIn3EkzOJdb6ypZ50iNAnnTN5+zQpt0Y3f1up2/Ppy+nBNzjGdGAHDl4cc+2ri1IiNAIiG2Ta/kcNghNx3+XqUuYsK97e9grW+qXDa3gkeHqclOu9753GJKXeBR5gcH6O9PpPWRQ6T6slltsOPUnW3K3E54Il87fbxd73WSPN/8=", | ||
"uuid": "1c0c0bb6-8dcd-4966-ad80-946b871814f1", | ||
"availability_zone": "s922", | ||
"keys": [ | ||
{ | ||
"data": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmMuiypdqqftqhrQeBTjOhcgyARvylZMLiH+6nCvi5Lv5M7evAnvvG3Hz4rbjbbqoVgSCIdAEb4PuttiCdwE6UyAl0TYAydOVPx7l87BlaucTEqDFbXkQB+yyUmzodllCpWAMUmxwvJB/ntFrC6rP0K0kKxx4SESvozutwM2X5oH3LNHcYI1xgKIMF9VMJLkkM0rLo8Fmj6mWF5KtbU7vS7JJPvLTCRhW5TYrqvhHKuIS6KBtj3GJqvRt+it8AsIb6/RUaji68Mt7W41UrmFSPt8bxJMdE/xKGMcFQjamURPSCHx7z8/pr2/pv2QbQF76FO7lRdPH3f542uAkOOpO1 user1@user1-MacBook-Pro-2.local", | ||
"type": "ssh", | ||
"name": "65b64c1f1c29460e8c2e4bbfbd893c2c_7c6cd5d4-a7aa-4d07-bb89-94f274bfeed0_user1-pub-key" | ||
} | ||
], | ||
"hostname": "test-tf-pvm.power-iaas.cloud.ibm.com", | ||
"launch_index": 0, | ||
"devices": [], | ||
"meta": { | ||
"ibmiDBQ": "false", | ||
"ibmiCSS": "false", | ||
"storage_pool": "Tier1-Flash-2", | ||
"ibmiRDS": "false", | ||
"ibmiPHA": "false" | ||
}, | ||
"public_keys": { | ||
"65b64c1f1c29460e8c2e4bbfbd893c2c_7c6cd5d4-a7aa-4d07-bb89-94f274bfeed0_user1-pub-key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmMuiypdqqftqhrQeBTjOhcgyARvylZMLiH+6nCvi5Lv5M7evAnvvG3Hz4rbjbbqoVgSCIdAEb4PuttiCdwE6UyAl0TYAydOVPx7l87BlaucTEqDFbXkQB+yyUmzodllCpWAMUmxwvJB/ntFrC6rP0K0kKxx4SESvozutwM2X5oH3LNHcYI1xgKIMF9VMJLkkM0rLo8Fmj6mWF5KtbU7vS7JJPvLTCRhW5TYrqvhHKuIS6KBtj3GJqvRt+it8AsIb6/RUaji68Mt7W41UrmFSPt8bxJMdE/xKGMcFQjamURPSCHx7z8/pr2/pv2QbQF76FO7lRdPH3f542uAkOOpO1 user1@user1-MacBook-Pro-2.local" | ||
}, | ||
"project_id": "15ea5428420d4d47be42e19332645a18", | ||
"network_config": { | ||
"content_path": "/content/0000", | ||
"name": "network_config" | ||
}, | ||
"name": "test-tf-pVM" | ||
} |