-
Notifications
You must be signed in to change notification settings - Fork 124
Add PowerVS provider #592
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add PowerVS provider #592
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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" | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.