From 755076a5d59e81094af9d4c71d1211790d0e9cef Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 09:24:08 +0100 Subject: [PATCH 01/58] feat(cargo): add block_storage feature Signed-off-by: Sandro-Alessio Gierens --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b020d30c51..cc165badd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ edition = "2021" rust-version = "1.65" [features] -default = ["compute", "image", "network", "native-tls", "object-storage"] +default = ["block-storage", "compute", "image", "network", "native-tls", "object-storage"] +block-storage = [] compute = [] image = [] network = [] From a17e11527e8c8f6b422fe974ae064a6c3a44ba9f Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 09:24:24 +0100 Subject: [PATCH 02/58] feat(lib): register block_storage module with library Signed-off-by: Sandro-Alessio Gierens --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 3dd66fd6c3..f040948941 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -652,6 +652,8 @@ pub mod image; pub mod network; #[cfg(feature = "object-storage")] pub mod object_storage; +#[cfg(feature = "block-storage")] +pub mod block_storage; /// Synchronous sessions based on one from [osauth](https://docs.rs/osauth/). pub mod session { pub use osauth::services::ServiceType; From 7ac91a99163fa7fbd575041512d67303e7f3251e Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 09:23:38 +0100 Subject: [PATCH 03/58] feat(block_storage): add empty block_storage module Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/api.rs | 15 +++++++++++++++ src/block_storage/mod.rs | 18 ++++++++++++++++++ src/block_storage/protocol.rs | 15 +++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 src/block_storage/api.rs create mode 100644 src/block_storage/mod.rs create mode 100644 src/block_storage/protocol.rs diff --git a/src/block_storage/api.rs b/src/block_storage/api.rs new file mode 100644 index 0000000000..4cb937695d --- /dev/null +++ b/src/block_storage/api.rs @@ -0,0 +1,15 @@ +// Copyright 2024 Sandro-Alessio Gierens +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Foundation bits exposing the Block Storage API. diff --git a/src/block_storage/mod.rs b/src/block_storage/mod.rs new file mode 100644 index 0000000000..67a7594057 --- /dev/null +++ b/src/block_storage/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2024 Sandro-Alessio Gierens +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Block Storage API implementation bits. + +mod api; +mod protocol; diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs new file mode 100644 index 0000000000..37c3d868ec --- /dev/null +++ b/src/block_storage/protocol.rs @@ -0,0 +1,15 @@ +// Copyright 2024 Sandro-Alessio Gierens +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! JSON structures and protocol bits for the Block Storage API. From ef50c1962cf676daa6aecfdfe86f6a175838b16e Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 11:52:57 +0100 Subject: [PATCH 04/58] feat(block_storage): add VolumeStatus and VolumeSortKey to protocol Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 37c3d868ec..1d34cbde3a 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -13,3 +13,52 @@ // limitations under the License. //! JSON structures and protocol bits for the Block Storage API. + +#![allow(non_snake_case)] +#![allow(missing_docs)] + +use serde::Deserialize; + +// use super::super::common; + +protocol_enum! { + #[doc = "Possible volume statuses."] + enum VolumeStatus { + Creating = "creating", + Available = "available", + Reserved = "reserved", + Attaching = "attaching", + Detaching = "detaching", + InUse = "in-use", + Maintenance = "maintenance", + Deleting = "deleting", + AwaitingTransfer = "awaiting-transfer", + Error = "error", + ErrorDeleting = "error_deleting", + BackingUp = "backing-up", + RestoringBackup = "restoring-backup", + ErrorBackingUp = "error_backing-up", + ErrorRestoring = "error_restoring", + ErrorExtending = "error_extending", + Downloading = "downloading", + Uploading = "uploading", + Retyping = "retyping", + Extending = "extending" + } +} + +protocol_enum! { + #[doc = "Available sort keys."] + enum VolumeSortKey { + CreatedAt = "created_at", + Id = "id", + Name = "name", + UpdatedAt = "updated_at" + } +} + +impl Default for VolumeSortKey { + fn default() -> VolumeSortKey { + VolumeSortKey::CreatedAt + } +} From a269a43eeb881f6a7a81384a4b516fcf0aa6aadb Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 11:53:33 +0100 Subject: [PATCH 05/58] feat(block_storage): add minimal Volume, and Volume(s)Root to protocol Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 1d34cbde3a..2b0eb7670d 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -62,3 +62,23 @@ impl Default for VolumeSortKey { VolumeSortKey::CreatedAt } } + +/// A volume. +#[derive(Debug, Clone, Deserialize)] +pub struct Volume { + pub id: String, + pub name: String, + pub status: VolumeStatus, +} + +/// A volume root. +#[derive(Clone, Debug, Deserialize)] +pub struct VolumeRoot { + pub volume: Volume, +} + +/// A list of volumes. +#[derive(Debug, Clone, Deserialize)] +pub struct VolumesRoot { + pub volumes: Vec, +} From c3f0192fc80e96032b5477352bcd40bd7d5db3f8 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 11:56:39 +0100 Subject: [PATCH 06/58] feat(block_storage): add get_volume(_by_id/name) to api Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/api.rs | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/block_storage/api.rs b/src/block_storage/api.rs index 4cb937695d..d21e7067ab 100644 --- a/src/block_storage/api.rs +++ b/src/block_storage/api.rs @@ -13,3 +13,51 @@ // limitations under the License. //! Foundation bits exposing the Block Storage API. + +use std::fmt::Debug; + +use osauth::services::BLOCK_STORAGE; +use osauth::ErrorKind; +use serde::Serialize; + +use super::super::session::Session; +use super::super::utils; +use super::protocol::*; +use super::super::Result; + +/// Get an volume. +pub async fn get_volume>(session: &Session, id_or_name: S) -> Result { + let s = id_or_name.as_ref(); + match get_volume_by_id(session, s).await { + Ok(value) => Ok(value), + Err(err) if err.kind() == ErrorKind::ResourceNotFound => { + get_volume_by_name(session, s).await + } + Err(err) => Err(err), + } +} + +/// Get an volume by its ID. +pub async fn get_volume_by_id>(session: &Session, id: S) -> Result { + trace!("Fetching volume {}", id.as_ref()); + let root: VolumeRoot = session.get(BLOCK_STORAGE, &["volumes", id.as_ref()]).fetch().await?; + trace!("Received {:?}", root.volume); + Ok(root.volume) +} + +/// Get an volume by its name. +pub async fn get_volume_by_name>(session: &Session, name: S) -> Result { + trace!("Get volume by name {}", name.as_ref()); + let root: VolumesRoot = session + .get(BLOCK_STORAGE, &["volumes"]) + .query(&[("name", name.as_ref())]) + .fetch() + .await?; + let result = utils::one( + root.volumes, + "Volume with given name or ID not found", + "Too many volumes found with given name", + )?; + trace!("Received {:?}", result); + Ok(result) +} From 6e678f42bd15a206a8266756e96a56dbba69c579 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 11:57:07 +0100 Subject: [PATCH 07/58] feat(block_storage): add list_volumes to api Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/api.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/block_storage/api.rs b/src/block_storage/api.rs index d21e7067ab..1e8c5f1be0 100644 --- a/src/block_storage/api.rs +++ b/src/block_storage/api.rs @@ -61,3 +61,14 @@ pub async fn get_volume_by_name>(session: &Session, name: S) -> Re trace!("Received {:?}", result); Ok(result) } + +/// List volumes. +pub async fn list_volumes( + session: &Session, + query: &Q, +) -> Result> { + trace!("Listing volumes with {:?}", query); + let root: VolumesRoot = session.get(BLOCK_STORAGE, &["volumes", "detail"]).query(query).fetch().await?; + trace!("Received volumes: {:?}", root.volumes); + Ok(root.volumes) +} From 41d5fcf06d33ce4d7a9b5e8ba009cfd17a1ea6e2 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 11:58:51 +0100 Subject: [PATCH 08/58] feat(block_storage): add volumes submodule with Volume Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/block_storage/volumes.rs diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs new file mode 100644 index 0000000000..33f48e9f55 --- /dev/null +++ b/src/block_storage/volumes.rs @@ -0,0 +1,49 @@ +// Copyright 2024 Sandro-Alessio Gierens +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Volume management via Block Storage API. + +use async_trait::async_trait; +use futures::stream::{Stream, TryStreamExt}; + +use super::super::common::{Refresh, ResourceIterator, ResourceQuery}; +use super::super::session::Session; +use super::super::utils::Query; +use super::super::{Result, Sort}; +use super::{api, protocol}; + +/// Structure representing a summary of a single volume. +#[derive(Clone, Debug)] +pub struct Volume { + session: Session, + inner: protocol::Volume, +} + +impl Volume { + /// Create an Volume object. + pub(crate) async fn new>(session: Session, id: Id) -> Result { + let inner = api::get_volume(&session, id).await?; + Ok(Volume { session, inner }) + } + + transparent_property! { + #[doc = "Unique ID."] + id: ref String + } + + transparent_property! { + #[doc = "Volume name."] + name: ref String + } +} From 1d408bb66adad2eb2b3ac80a4f047f356b2922d8 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 11:59:25 +0100 Subject: [PATCH 09/58] feat(block_storage): implement Refresh for volumes::Volume Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 33f48e9f55..ebc505d523 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -47,3 +47,12 @@ impl Volume { name: ref String } } + +#[async_trait] +impl Refresh for Volume { + /// Refresh the volume. + async fn refresh(&mut self) -> Result<()> { + self.inner = api::get_volume_by_id(&self.session, &self.inner.id).await?; + Ok(()) + } +} From 56740f4df920872574436736bd66acbe8d83e2f6 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 12:01:06 +0100 Subject: [PATCH 10/58] feat(block_storage): add VolumeQuery to volumes Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 93 ++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index ebc505d523..c67b338153 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -23,6 +23,15 @@ use super::super::utils::Query; use super::super::{Result, Sort}; use super::{api, protocol}; +/// A query to volume list. +#[derive(Clone, Debug)] +pub struct VolumeQuery { + session: Session, + query: Query, + can_paginate: bool, + sort: Vec, +} + /// Structure representing a summary of a single volume. #[derive(Clone, Debug)] pub struct Volume { @@ -56,3 +65,87 @@ impl Refresh for Volume { Ok(()) } } + +impl VolumeQuery { + pub(crate) fn new(session: Session) -> VolumeQuery { + VolumeQuery { + session, + query: Query::new(), + can_paginate: true, + sort: Vec::new(), + } + } + + /// Add sorting to the request. + pub fn sort_by(mut self, sort: Sort) -> Self { + let (field, direction) = sort.into(); + self.sort.push(format!("{field}:{direction}")); + self + } + + /// Add marker to the request. + /// + /// Using this disables automatic pagination. + pub fn with_marker>(mut self, marker: T) -> Self { + self.can_paginate = false; + self.query.push_str("marker", marker); + self + } + + /// Add limit to the request. + /// + /// Using this disables automatic pagination. + pub fn with_limit(mut self, limit: usize) -> Self { + self.can_paginate = false; + self.query.push("limit", limit); + self + } + + query_filter! { + #[doc = "Filter by volume name."] + with_name -> name + } + + query_filter! { + #[doc = "Filter by volume status."] + with_status -> status: protocol::VolumeStatus + } + + /// Convert this query into a stream executing the request. + /// + /// Returns a `TryStream`, which is a stream with each `next` + /// call returning a `Result`. + /// + /// Note that no requests are done until you start iterating. + pub fn into_stream( + mut self, + ) -> impl Stream::Item>> { + if !self.sort.is_empty() { + self.query.push_str("sort", self.sort.join(",")); + } + debug!("Fetching volumes with {:?}", self.query); + ResourceIterator::new(self).into_stream() + } + + /// Execute this request and return all results. + /// + /// A convenience shortcut for `self.into_stream().try_collect().await`. + pub async fn all(self) -> Result> { + self.into_stream().try_collect().await + } + + /// Return one and exactly one result. + /// + /// Fails with `ResourceNotFound` if the query produces no results and + /// with `TooManyItems` if the query produces more than one result. + pub async fn one(mut self) -> Result { + debug!("Fetching one volume with {:?}", self.query); + if self.can_paginate { + // We need only one result. We fetch maximum two to be able + // to check if the query yields more than one result. + self.query.push("limit", 2); + } + + ResourceIterator::new(self).one().await + } +} From 8ffa8a09c8061d9916b4ee84a43bfb7dbd01173a Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 12:01:24 +0100 Subject: [PATCH 11/58] feat(block_storage): implement ResourceQuery for volumes::VolumeQuery Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index c67b338153..990a7288d4 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -149,3 +149,34 @@ impl VolumeQuery { ResourceIterator::new(self).one().await } } + +#[async_trait] +impl ResourceQuery for VolumeQuery { + type Item = Volume; + + const DEFAULT_LIMIT: usize = 50; + + async fn can_paginate(&self) -> Result { + Ok(self.can_paginate) + } + + fn extract_marker(&self, resource: &Self::Item) -> String { + resource.id().clone() + } + + async fn fetch_chunk( + &self, + limit: Option, + marker: Option, + ) -> Result> { + let query = self.query.with_marker_and_limit(limit, marker); + Ok(api::list_volumes(&self.session, &query) + .await? + .into_iter() + .map(|item| Volume { + session: self.session.clone(), + inner: item, + }) + .collect()) + } +} From 390e01567ab618d45b273c9c3c579cc010350101 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 12:02:02 +0100 Subject: [PATCH 12/58] feat(block_storage): update mod to publish volumes and protocol stuff Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/block_storage/mod.rs b/src/block_storage/mod.rs index 67a7594057..1333f8961d 100644 --- a/src/block_storage/mod.rs +++ b/src/block_storage/mod.rs @@ -15,4 +15,10 @@ //! Block Storage API implementation bits. mod api; +mod volumes; mod protocol; + +pub use self::volumes::{Volume, VolumeQuery}; +pub use self::protocol::{ + VolumeSortKey, VolumeStatus, +}; From fd315aa01c9c5b362a0c1c7906df481853343dd6 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 12:03:02 +0100 Subject: [PATCH 13/58] style(lib): reorder modules alphabetically Signed-off-by: Sandro-Alessio Gierens --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f040948941..40fae40649 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -644,6 +644,8 @@ pub mod auth { } mod cloud; pub mod common; +#[cfg(feature = "block-storage")] +pub mod block_storage; #[cfg(feature = "compute")] pub mod compute; #[cfg(feature = "image")] @@ -652,8 +654,6 @@ pub mod image; pub mod network; #[cfg(feature = "object-storage")] pub mod object_storage; -#[cfg(feature = "block-storage")] -pub mod block_storage; /// Synchronous sessions based on one from [osauth](https://docs.rs/osauth/). pub mod session { pub use osauth::services::ServiceType; From 9ffbd2bede2d9f6acf450a05c726d6907fd8216a Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 12:03:59 +0100 Subject: [PATCH 14/58] feat(cloud): add find_volumes to Cloud implementation Signed-off-by: Sandro-Alessio Gierens --- src/cloud.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cloud.rs b/src/cloud.rs index 1ccfbe6b8f..87a7550b4a 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -22,6 +22,8 @@ use std::io; use super::auth::AuthType; #[allow(unused_imports)] use super::common::{ContainerRef, FlavorRef, NetworkRef}; +#[cfg(feature = "block-storage")] +use super::block_storage::{Volume, VolumeQuery}; #[cfg(feature = "compute")] use super::compute::{ Flavor, FlavorQuery, FlavorSummary, KeyPair, KeyPairQuery, NewKeyPair, NewServer, Server, @@ -310,6 +312,15 @@ impl Cloud { SubnetQuery::new(self.session.clone()) } + /// Build a query against volume list. + /// + /// The returned object is a builder that should be used to construct + /// the query. + #[cfg(feature = "block-storage")] + pub fn find_volumes(&self) -> VolumeQuery { + VolumeQuery::new(self.session.clone()) + } + /// Get object container metadata by its name. /// /// # Example From 1bc77cc9dd8248a3c304e698694da08b2435622b Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 12:04:29 +0100 Subject: [PATCH 15/58] feat(cloud): add list_volumes to Cloud implementation Signed-off-by: Sandro-Alessio Gierens --- src/cloud.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cloud.rs b/src/cloud.rs index 87a7550b4a..74feb628e8 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -750,6 +750,11 @@ impl Cloud { self.find_subnets().all().await } + /// List all volumes. + pub async fn list_volumes(&self) -> Result> { + self.find_volumes().all().await + } + /// Prepare a new object for creation. /// /// This call returns a `NewObject` object, which is a builder From 2818deb4aa95b2f88f4b99c25cb97879edb48818 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 12:04:44 +0100 Subject: [PATCH 16/58] feat(cloud): add get_volume to Cloud implementation Signed-off-by: Sandro-Alessio Gierens --- src/cloud.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/cloud.rs b/src/cloud.rs index 74feb628e8..21b37e327c 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -520,6 +520,23 @@ impl Cloud { Subnet::load(self.session.clone(), id_or_name).await } + /// Find an volume by its name or ID. + /// + /// # Example + /// + /// ```rust,no_run + /// use openstack; + /// + /// # async fn async_wrapper() { + /// let os = openstack::Cloud::from_env().await.expect("Unable to authenticate"); + /// let volume = os.get_volume("my-first-volume").await.expect("Unable to get a volume"); + /// # } + /// ``` + #[cfg(feature = "block-storage")] + pub async fn get_volume>(&self, id_or_name: Id) -> Result { + Volume::new(self.session.clone(), id_or_name).await + } + /// List all containers. /// /// This call can yield a lot of results, use the From 3ccfbbd0bfddf806a591abb7d4ba680452ac1996 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Thu, 25 Jan 2024 12:05:03 +0100 Subject: [PATCH 17/58] test(tests): add test_list_volumes to integration-list-resources Signed-off-by: Sandro-Alessio Gierens --- tests/integration-list-resources.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration-list-resources.rs b/tests/integration-list-resources.rs index 924917bb86..a04240d630 100644 --- a/tests/integration-list-resources.rs +++ b/tests/integration-list-resources.rs @@ -82,3 +82,9 @@ async fn test_list_routers() { let os = set_up().await; let _ = os.list_routers().await.expect("Cannot list routers"); } + +#[tokio::test] +async fn test_list_volumes() { + let os = set_up().await; + let _ = os.list_volumes().await.expect("Cannot list volumes"); +} From 8a97bb969a0c3c0aa41efb33539c61c25c8b6d64 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Wed, 31 Jan 2024 22:42:11 +0100 Subject: [PATCH 18/58] feat(block_storage): implement Display for Volume to show just Volume.inner This allows to hide session details and just focus on the wrapped inner protocol::Volume. Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 990a7288d4..0baf8b910c 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -16,6 +16,7 @@ use async_trait::async_trait; use futures::stream::{Stream, TryStreamExt}; +use std::fmt::{self, Display, Formatter}; use super::super::common::{Refresh, ResourceIterator, ResourceQuery}; use super::super::session::Session; @@ -39,6 +40,12 @@ pub struct Volume { inner: protocol::Volume, } +impl Display for Volume { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{:#?}", self.inner) + } +} + impl Volume { /// Create an Volume object. pub(crate) async fn new>(session: Session, id: Id) -> Result { From f7750a898214561003c4cd6c2b691f3aecf1296c Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 10 Mar 2024 00:08:58 +0100 Subject: [PATCH 19/58] feat(block_storage): add delete_volume to api Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/api.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/block_storage/api.rs b/src/block_storage/api.rs index 1e8c5f1be0..5deaff1b6a 100644 --- a/src/block_storage/api.rs +++ b/src/block_storage/api.rs @@ -25,6 +25,17 @@ use super::super::utils; use super::protocol::*; use super::super::Result; +/// Delete a volume. +pub async fn delete_volume>(session: &Session, id: S) -> Result<()> { + trace!("Deleting volume {}", id.as_ref()); + let _ = session + .delete(BLOCK_STORAGE, &["volumes", id.as_ref()]) + .send() + .await?; + debug!("Successfully requested deletion of volume {}", id.as_ref()); + Ok(()) +} + /// Get an volume. pub async fn get_volume>(session: &Session, id_or_name: S) -> Result { let s = id_or_name.as_ref(); From 914d37bd9c69cdaa30ab9893f65a04819258de24 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 10 Mar 2024 00:09:39 +0100 Subject: [PATCH 20/58] feat(block_storage): add delete to Volume implementation Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 0baf8b910c..1fd622cd13 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -17,8 +17,10 @@ use async_trait::async_trait; use futures::stream::{Stream, TryStreamExt}; use std::fmt::{self, Display, Formatter}; +use std::time::Duration; use super::super::common::{Refresh, ResourceIterator, ResourceQuery}; +use super::super::waiter::DeletionWaiter; use super::super::session::Session; use super::super::utils::Query; use super::super::{Result, Sort}; @@ -62,6 +64,16 @@ impl Volume { #[doc = "Volume name."] name: ref String } + + /// Delete the volume. + pub async fn delete(self) -> Result> { + api::delete_volume(&self.session, &self.inner.id).await?; + Ok(DeletionWaiter::new( + self, + Duration::new(120, 0), + Duration::new(1, 0), + )) + } } #[async_trait] From 3f4fb35f61674b749d5aca6ba2f42c719507f165 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 10 Mar 2024 00:12:07 +0100 Subject: [PATCH 21/58] style(block_storage): removed commented out import in protocol Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 2b0eb7670d..73e9b8828c 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -19,8 +19,6 @@ use serde::Deserialize; -// use super::super::common; - protocol_enum! { #[doc = "Possible volume statuses."] enum VolumeStatus { From cd961df7e7a99b412ce173729739ddf7a527b07a Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 11 Mar 2024 11:36:33 +0100 Subject: [PATCH 22/58] feat(block_storage): add VolumeAttachment and Link structs to protocol Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 73e9b8828c..827d17b397 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -61,6 +61,26 @@ impl Default for VolumeSortKey { } } +/// A volume attachment. +#[derive(Debug, Clone, Deserialize)] +#[allow(dead_code)] +pub struct VolumeAttachment { + pub server_id: String, // this should be a reference to a server + pub attachment_id: String, + pub attached_at: String, + pub host_name: String, + pub volume_id: String, // this should be a reference to a volume + pub device: String, + pub id: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[allow(dead_code)] +pub struct Link { + pub rel: String, + pub href: String, +} + /// A volume. #[derive(Debug, Clone, Deserialize)] pub struct Volume { From 940326ad95855ac9c101540a368e0b63ddd90cfc Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 11 Mar 2024 11:37:32 +0100 Subject: [PATCH 23/58] feat(block_storage): add all remaining fields to protocol.Volume Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 48 ++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 827d17b397..fb224fbfa7 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -17,6 +17,7 @@ #![allow(non_snake_case)] #![allow(missing_docs)] +use std::collections::HashMap; use serde::Deserialize; protocol_enum! { @@ -83,10 +84,55 @@ pub struct Link { /// A volume. #[derive(Debug, Clone, Deserialize)] +#[allow(dead_code)] pub struct Volume { + // TODO: not all fields fully match the API spec: + // https://docs.openstack.org/api-ref/block-storage/v3/#list-accessible-volumes-with-details + // Some fields are not actually optional, but don't work without Option<>. + // Others should maybe be enums, but the possible values are not documented. + // There are comments for these cases. + pub migration_status: Option, // consider enum + pub attachments: Vec, + pub links: Vec, + pub availability_zone: Option, + #[serde(rename = "os-vol-host-attr:host")] + pub host: Option, + pub encrypted: bool, + pub encryption_key_id: Option, + pub updated_at: String, + pub replication_status: Option, // not optional in spec, also consider enum + pub snapshot_id: Option, pub id: String, - pub name: String, + pub size: u64, + pub user_id: String, + #[serde(rename = "os-vol-tenant-attr:tenant_id")] + pub tenant_id: String, + #[serde(rename = "os-vol-mig-status-attr:migstat")] + pub migstat: Option, // consider enum + pub metadata: HashMap, pub status: VolumeStatus, + #[serde(rename = "volume_image_metadata")] + pub image_metadata: Option>, + pub description: String, + pub multiattach: bool, + pub source_volid: Option, + pub consistencygroup_id: Option, // not optional in spec + #[serde(rename = "os-vol-mig-status-attr:name_id")] + pub name_id: Option, + pub name: String, + pub bootable: String, + pub created_at: String, + pub volumes: Option>, // not optional in spec + pub volume_type: String, // consider enum + pub volume_type_id: Option>, // not optional in spec + pub group_id: Option, + pub volumes_links: Option>, + pub provider_id: Option, + pub service_uuid: Option, // not optional in spec + pub shared_targets: Option, // not optional in spec + pub cluster_name: Option, + pub consumes_quota: Option, + pub count: Option, } /// A volume root. From 4618270eba516a2a7c97b6b7dabf876b896fbf2f Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 11 Mar 2024 13:31:00 +0100 Subject: [PATCH 24/58] style(src): fix formatting issues mostly in block_storage module Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/api.rs | 13 ++++++++++--- src/block_storage/mod.rs | 6 ++---- src/block_storage/protocol.rs | 4 ++-- src/block_storage/volumes.rs | 2 +- src/cloud.rs | 4 ++-- src/lib.rs | 4 ++-- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/block_storage/api.rs b/src/block_storage/api.rs index 5deaff1b6a..2397af208a 100644 --- a/src/block_storage/api.rs +++ b/src/block_storage/api.rs @@ -22,8 +22,8 @@ use serde::Serialize; use super::super::session::Session; use super::super::utils; -use super::protocol::*; use super::super::Result; +use super::protocol::*; /// Delete a volume. pub async fn delete_volume>(session: &Session, id: S) -> Result<()> { @@ -51,7 +51,10 @@ pub async fn get_volume>(session: &Session, id_or_name: S) -> Resu /// Get an volume by its ID. pub async fn get_volume_by_id>(session: &Session, id: S) -> Result { trace!("Fetching volume {}", id.as_ref()); - let root: VolumeRoot = session.get(BLOCK_STORAGE, &["volumes", id.as_ref()]).fetch().await?; + let root: VolumeRoot = session + .get(BLOCK_STORAGE, &["volumes", id.as_ref()]) + .fetch() + .await?; trace!("Received {:?}", root.volume); Ok(root.volume) } @@ -79,7 +82,11 @@ pub async fn list_volumes( query: &Q, ) -> Result> { trace!("Listing volumes with {:?}", query); - let root: VolumesRoot = session.get(BLOCK_STORAGE, &["volumes", "detail"]).query(query).fetch().await?; + let root: VolumesRoot = session + .get(BLOCK_STORAGE, &["volumes", "detail"]) + .query(query) + .fetch() + .await?; trace!("Received volumes: {:?}", root.volumes); Ok(root.volumes) } diff --git a/src/block_storage/mod.rs b/src/block_storage/mod.rs index 1333f8961d..b90b2ea408 100644 --- a/src/block_storage/mod.rs +++ b/src/block_storage/mod.rs @@ -15,10 +15,8 @@ //! Block Storage API implementation bits. mod api; -mod volumes; mod protocol; +mod volumes; +pub use self::protocol::{VolumeSortKey, VolumeStatus}; pub use self::volumes::{Volume, VolumeQuery}; -pub use self::protocol::{ - VolumeSortKey, VolumeStatus, -}; diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index fb224fbfa7..25f743f2b4 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -17,8 +17,8 @@ #![allow(non_snake_case)] #![allow(missing_docs)] -use std::collections::HashMap; use serde::Deserialize; +use std::collections::HashMap; protocol_enum! { #[doc = "Possible volume statuses."] @@ -123,7 +123,7 @@ pub struct Volume { pub bootable: String, pub created_at: String, pub volumes: Option>, // not optional in spec - pub volume_type: String, // consider enum + pub volume_type: String, // consider enum pub volume_type_id: Option>, // not optional in spec pub group_id: Option, pub volumes_links: Option>, diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 1fd622cd13..e67cb472e2 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -20,9 +20,9 @@ use std::fmt::{self, Display, Formatter}; use std::time::Duration; use super::super::common::{Refresh, ResourceIterator, ResourceQuery}; -use super::super::waiter::DeletionWaiter; use super::super::session::Session; use super::super::utils::Query; +use super::super::waiter::DeletionWaiter; use super::super::{Result, Sort}; use super::{api, protocol}; diff --git a/src/cloud.rs b/src/cloud.rs index 21b37e327c..a3050a93eb 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -20,10 +20,10 @@ use futures::io::AsyncRead; use std::io; use super::auth::AuthType; -#[allow(unused_imports)] -use super::common::{ContainerRef, FlavorRef, NetworkRef}; #[cfg(feature = "block-storage")] use super::block_storage::{Volume, VolumeQuery}; +#[allow(unused_imports)] +use super::common::{ContainerRef, FlavorRef, NetworkRef}; #[cfg(feature = "compute")] use super::compute::{ Flavor, FlavorQuery, FlavorSummary, KeyPair, KeyPairQuery, NewKeyPair, NewServer, Server, diff --git a/src/lib.rs b/src/lib.rs index 40fae40649..dc2f0f8879 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -642,10 +642,10 @@ pub mod auth { pub use osauth::identity::{Password, Scope, Token}; pub use osauth::{AuthType, NoAuth}; } -mod cloud; -pub mod common; #[cfg(feature = "block-storage")] pub mod block_storage; +mod cloud; +pub mod common; #[cfg(feature = "compute")] pub mod compute; #[cfg(feature = "image")] From 8dfa775a7625db3d4bec2a5f0325dfd531b01b5f Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 11 Mar 2024 13:35:24 +0100 Subject: [PATCH 25/58] fix(cloud): add missing cfg for block_storage feature to list_volumes Signed-off-by: Sandro-Alessio Gierens --- src/cloud.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cloud.rs b/src/cloud.rs index a3050a93eb..739af59879 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -768,6 +768,7 @@ impl Cloud { } /// List all volumes. + #[cfg(feature = "block-storage")] pub async fn list_volumes(&self) -> Result> { self.find_volumes().all().await } From 8ba033293a4fc54fc17fac37bef62ad777dde162 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 15:04:48 +0100 Subject: [PATCH 26/58] style(block_storage): remove not needed allow(dead_code) in protocol module Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 25f743f2b4..9b2fe6d39e 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -64,7 +64,6 @@ impl Default for VolumeSortKey { /// A volume attachment. #[derive(Debug, Clone, Deserialize)] -#[allow(dead_code)] pub struct VolumeAttachment { pub server_id: String, // this should be a reference to a server pub attachment_id: String, @@ -76,7 +75,6 @@ pub struct VolumeAttachment { } #[derive(Debug, Clone, Deserialize)] -#[allow(dead_code)] pub struct Link { pub rel: String, pub href: String, @@ -84,7 +82,6 @@ pub struct Link { /// A volume. #[derive(Debug, Clone, Deserialize)] -#[allow(dead_code)] pub struct Volume { // TODO: not all fields fully match the API spec: // https://docs.openstack.org/api-ref/block-storage/v3/#list-accessible-volumes-with-details From 58966b18fb6d597527dabe9d38fdb82cb8ada7ee Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 15:07:29 +0100 Subject: [PATCH 27/58] fix(block_storage): revise protocol::Volume fields for create_volume Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 9b2fe6d39e..6a748303ec 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -96,24 +96,26 @@ pub struct Volume { pub host: Option, pub encrypted: bool, pub encryption_key_id: Option, - pub updated_at: String, + pub updated_at: Option, pub replication_status: Option, // not optional in spec, also consider enum pub snapshot_id: Option, pub id: String, pub size: u64, pub user_id: String, #[serde(rename = "os-vol-tenant-attr:tenant_id")] - pub tenant_id: String, + pub tenant_id: Option, #[serde(rename = "os-vol-mig-status-attr:migstat")] pub migstat: Option, // consider enum pub metadata: HashMap, pub status: VolumeStatus, #[serde(rename = "volume_image_metadata")] pub image_metadata: Option>, - pub description: String, + pub description: Option, pub multiattach: bool, - pub source_volid: Option, - pub consistencygroup_id: Option, // not optional in spec + #[serde(rename = "source_volid")] + pub source_volume_id: Option, + #[serde(rename = "consistencygroup_id")] + pub consistency_group_id: Option, // not optional in spec #[serde(rename = "os-vol-mig-status-attr:name_id")] pub name_id: Option, pub name: String, From a1e3b9941dfc362471722abb072c8bb3071956d2 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 15:08:10 +0100 Subject: [PATCH 28/58] feat(block_storage): add VolumeCreate(Root) struct to protocol module Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 53 ++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 6a748303ec..fce9772956 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -17,7 +17,7 @@ #![allow(non_snake_case)] #![allow(missing_docs)] -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; protocol_enum! { @@ -145,3 +145,54 @@ pub struct VolumeRoot { pub struct VolumesRoot { pub volumes: Vec, } + +/// Volume arguments for a create request. +#[derive(Debug, Clone, Serialize)] +pub struct VolumeCreate { + pub size: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub availability_zone: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "source_volid")] + pub source_volume_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub backup_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "imageRef")] + pub image_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub volume_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + #[serde(skip_serializing_if = "Option::is_none", rename = "consistency_group_id")] + pub consistency_group_id: Option, +} + +/// A volume create request. +#[derive(Clone, Debug, Serialize)] +pub struct VolumeCreateRoot { + pub volume: VolumeCreate, + // NOTE: this can also contain a scheduler_hints field +} + +impl VolumeCreate { + pub fn new(size: u64) -> VolumeCreate { + VolumeCreate { + size, + availability_zone: None, + source_volume_id: None, + description: None, + snapshot_id: None, + backup_id: None, + name: None, + image_id: None, + volume_type: None, + metadata: None, + consistency_group_id: None, + } + } +} From 11d58720f709aa302e5a69a4df0550ca9450e8d7 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 15:09:01 +0100 Subject: [PATCH 29/58] feat(block_storage): add create_volume call to api module Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/api.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/block_storage/api.rs b/src/block_storage/api.rs index 2397af208a..4b4904e0e1 100644 --- a/src/block_storage/api.rs +++ b/src/block_storage/api.rs @@ -90,3 +90,16 @@ pub async fn list_volumes( trace!("Received volumes: {:?}", root.volumes); Ok(root.volumes) } + +/// Create a volume. +pub async fn create_volume(session: &Session, request: VolumeCreate) -> Result { + debug!("Creating a volume with {:?}", request); + let body = VolumeCreateRoot { volume: request }; + let root: VolumeRoot = session + .post(BLOCK_STORAGE, &["volumes"]) + .json(&body) + .fetch() + .await?; + trace!("Requested creation of volume {:?}", root.volume); + Ok(root.volume) +} From b44a35db5465cae24055328704b0cd10a5eb1516 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 15:09:35 +0100 Subject: [PATCH 30/58] feat(block_storage): add NewVolume and impl to volumes module Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index e67cb472e2..747b9029e3 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -18,6 +18,7 @@ use async_trait::async_trait; use futures::stream::{Stream, TryStreamExt}; use std::fmt::{self, Display, Formatter}; use std::time::Duration; +use std::collections::HashMap; use super::super::common::{Refresh, ResourceIterator, ResourceQuery}; use super::super::session::Session; @@ -42,6 +43,13 @@ pub struct Volume { inner: protocol::Volume, } +/// A request to create a volume. +#[derive(Clone, Debug)] +pub struct NewVolume { + session: Session, + inner: protocol::VolumeCreate, +} + impl Display for Volume { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:#?}", self.inner) @@ -199,3 +207,72 @@ impl ResourceQuery for VolumeQuery { .collect()) } } + +impl NewVolume { + /// Start creating a volume. + pub(crate) fn new(session: Session, size: u64) -> NewVolume { + NewVolume { + session, + inner: protocol::VolumeCreate::new(size), + } + } + + /// Request creation of the volume. + pub async fn create(self) -> Result { + let inner = api::create_volume(&self.session, self.inner).await?; + Ok(Volume { + session: self.session, + inner, + }) + } + + creation_inner_field! { + #[doc = "Set the availability zone."] + set_availability_zone, with_availability_zone -> availability_zone: optional String + } + + creation_inner_field! { + #[doc = "Set the source volume ID."] + set_source_volume_id, with_source_volume_id -> source_volume_id: optional String + } + + creation_inner_field! { + #[doc = "Set the description."] + set_description, with_description -> description: optional String + } + + creation_inner_field! { + #[doc = "Set the snapshot ID."] + set_snapshot_id, with_snapshot_id -> snapshot_id: optional String + } + + creation_inner_field! { + #[doc = "Set the backup ID."] + set_backup_id, with_backup_id -> backup_id: optional String + } + + creation_inner_field! { + #[doc = "Set the name."] + set_name, with_name -> name: optional String + } + + creation_inner_field! { + #[doc = "Set the image ID."] + set_image_id, with_image_id -> image_id: optional String + } + + creation_inner_field! { + #[doc = "Set the volume type."] + set_volume_type, with_volume_type -> volume_type: optional String + } + + creation_inner_field! { + #[doc = "Set the metadata."] + set_metadata, with_metadata -> metadata: optional HashMap + } + + creation_inner_field! { + #[doc = "Set the consistency group ID."] + set_consistency_group_id, with_consistency_group_id -> consistency_group_id: optional String + } +} From d672b51c49e6f9f03bd059cd481d3f52fb6885c3 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 15:10:05 +0100 Subject: [PATCH 31/58] feat(block_storage): expose volumes::NewVolume in mod Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block_storage/mod.rs b/src/block_storage/mod.rs index b90b2ea408..9383c21d33 100644 --- a/src/block_storage/mod.rs +++ b/src/block_storage/mod.rs @@ -19,4 +19,4 @@ mod protocol; mod volumes; pub use self::protocol::{VolumeSortKey, VolumeStatus}; -pub use self::volumes::{Volume, VolumeQuery}; +pub use self::volumes::{Volume, VolumeQuery, NewVolume}; From 40aa943abfc90e468bd04674614a7eba045c45d0 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 15:10:43 +0100 Subject: [PATCH 32/58] feat(cloud): add new_volume to Cloud impl Signed-off-by: Sandro-Alessio Gierens --- src/cloud.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/cloud.rs b/src/cloud.rs index 739af59879..a9360ed9ae 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -21,7 +21,7 @@ use std::io; use super::auth::AuthType; #[cfg(feature = "block-storage")] -use super::block_storage::{Volume, VolumeQuery}; +use super::block_storage::{Volume, VolumeQuery, NewVolume}; #[allow(unused_imports)] use super::common::{ContainerRef, FlavorRef, NetworkRef}; #[cfg(feature = "compute")] @@ -854,6 +854,18 @@ impl Cloud { NewServer::new(self.session.clone(), name.into(), flavor.into()) } + /// Prepare a new volume for creation. + /// + /// This call returns a `NewVolume` object, which is a builder to populate + /// volume fields. + #[cfg(feature = "block-storage")] + pub fn new_volume(&self, size: U) -> NewVolume + where + U: Into, + { + NewVolume::new(self.session.clone(), size.into()) + } + /// Prepare a new subnet for creation. /// /// This call returns a `NewSubnet` object, which is a builder to populate From ffda1a4d6e137b77bf9e8e4bf151e57c13c061f1 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 15:12:57 +0100 Subject: [PATCH 33/58] style(src): apply rustfmt Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/mod.rs | 2 +- src/block_storage/protocol.rs | 5 ++++- src/block_storage/volumes.rs | 2 +- src/cloud.rs | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/block_storage/mod.rs b/src/block_storage/mod.rs index 9383c21d33..0e486af7c3 100644 --- a/src/block_storage/mod.rs +++ b/src/block_storage/mod.rs @@ -19,4 +19,4 @@ mod protocol; mod volumes; pub use self::protocol::{VolumeSortKey, VolumeStatus}; -pub use self::volumes::{Volume, VolumeQuery, NewVolume}; +pub use self::volumes::{NewVolume, Volume, VolumeQuery}; diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index fce9772956..213540ed6e 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -168,7 +168,10 @@ pub struct VolumeCreate { pub volume_type: Option, #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option>, - #[serde(skip_serializing_if = "Option::is_none", rename = "consistency_group_id")] + #[serde( + skip_serializing_if = "Option::is_none", + rename = "consistency_group_id" + )] pub consistency_group_id: Option, } diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 747b9029e3..90bd2abeb6 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -16,9 +16,9 @@ use async_trait::async_trait; use futures::stream::{Stream, TryStreamExt}; +use std::collections::HashMap; use std::fmt::{self, Display, Formatter}; use std::time::Duration; -use std::collections::HashMap; use super::super::common::{Refresh, ResourceIterator, ResourceQuery}; use super::super::session::Session; diff --git a/src/cloud.rs b/src/cloud.rs index a9360ed9ae..6f6ecca5c8 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -21,7 +21,7 @@ use std::io; use super::auth::AuthType; #[cfg(feature = "block-storage")] -use super::block_storage::{Volume, VolumeQuery, NewVolume}; +use super::block_storage::{NewVolume, Volume, VolumeQuery}; #[allow(unused_imports)] use super::common::{ContainerRef, FlavorRef, NetworkRef}; #[cfg(feature = "compute")] From d3a8e0aedae511903c6890e37a07b574d45588db Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 22:43:05 +0100 Subject: [PATCH 34/58] refactor(block_storage): rename field protocol::Volume.multi_attachable Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 213540ed6e..2e6bb12572 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -111,7 +111,8 @@ pub struct Volume { #[serde(rename = "volume_image_metadata")] pub image_metadata: Option>, pub description: Option, - pub multiattach: bool, + #[serde(rename = "multiattach")] + pub multi_attachable: bool, #[serde(rename = "source_volid")] pub source_volume_id: Option, #[serde(rename = "consistencygroup_id")] From fcf7eee1ef75181c0860c5815f87c18000f68180 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 22:44:28 +0100 Subject: [PATCH 35/58] feat(block_storage): add transparent_property for all volumes::Volume fields Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 179 ++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 2 deletions(-) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 90bd2abeb6..78cad2c707 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -64,15 +64,190 @@ impl Volume { } transparent_property! { - #[doc = "Unique ID."] + #[doc = "Migration status."] + migration_status: ref Option + } + + transparent_property! { + #[doc = "Volume attachments."] + attachments: ref Vec + } + + transparent_property! { + #[doc = "Volume links."] + links: ref Vec + } + + transparent_property! { + #[doc = "Name of the availability zone."] + availability_zone: ref Option + } + + transparent_property! { + #[doc = "Current backend of the volume."] + host: ref Option + } + + transparent_property! { + #[doc = "Whether the volume is encrypted."] + encrypted: ref bool + } + + transparent_property! { + #[doc = "UUID of the encryption key."] + encryption_key_id: ref Option + } + + transparent_property! { + #[doc = "When the volume was last updated."] + updated_at: ref Option + } + + transparent_property! { + #[doc = "Volume replication status."] + replication_status: ref Option + } + + transparent_property! { + #[doc = "UUID of the snapshot the volume originated from."] + snapshot_id: ref Option + } + + transparent_property! { + #[doc = "UUID of the volume."] id: ref String } transparent_property! { - #[doc = "Volume name."] + #[doc = "Size of the volume in GiB."] + size: ref u64 + } + + transparent_property! { + #[doc = "UUID of the user."] + user_id: ref String + } + + transparent_property! { + #[doc = "UUID of the project."] + tenant_id: ref Option + } + + transparent_property! { + #[doc = "Migration status."] + migstat: ref Option + } + + transparent_property! { + #[doc = "Metadata of the volume."] + metadata: ref HashMap + } + + transparent_property! { + #[doc = "Status of the volume."] + status: ref protocol::VolumeStatus + } + + transparent_property! { + #[doc = "Metadata of the image used to create the volume."] + image_metadata: ref Option> + } + + transparent_property! { + #[doc = "Description of the volume."] + description: ref Option + } + + transparent_property! { + #[doc = "Whether the volume is multi-attachable."] + multi_attachable: ref bool + } + + transparent_property! { + #[doc = "UUID of the volume this one originated from."] + source_volume_id: ref Option + } + + transparent_property! { + #[doc = "UUID of the consistency group."] + consistency_group_id: ref Option + } + + transparent_property! { + #[doc = "UUID of the volume that this volume name on the backend is based on."] + name_id: ref Option + } + + transparent_property! { + #[doc = "Name of the volume."] name: ref String } + transparent_property! { + #[doc = "Whether the volume is bootable."] + bootable: ref String + } + + transparent_property! { + #[doc = "When the volume was created."] + created_at: ref String + } + + transparent_property! { + #[doc = "A list of volume objects."] + volumes: ref Option> + } + + transparent_property! { + #[doc = "Name of the volume type."] + volume_type: ref String + } + + transparent_property! { + #[doc = "UUID of the volume type."] + volume_type_id: ref Option> + } + + transparent_property! { + #[doc = "UUID of the group."] + group_id: ref Option + } + + transparent_property! { + #[doc = "A list of volume links."] + volumes_links: ref Option> + } + + transparent_property! { + #[doc = "UUID of the provider for the volume."] + provider_id: ref Option + } + + transparent_property! { + #[doc = "UUID of the service the volume is served on."] + service_uuid: ref Option + } + + transparent_property! { + #[doc = "Whether the volume has shared targets."] + shared_targets: ref Option + } + + transparent_property! { + #[doc = "Cluster name of the volume backend."] + cluster_name: ref Option + } + + transparent_property! { + #[doc = "Whether the volume consumes quota."] + consumes_quota: ref Option + } + + transparent_property! { + #[doc = "Total count of volumes requested before pagination."] + count: ref Option + } + /// Delete the volume. pub async fn delete(self) -> Result> { api::delete_volume(&self.session, &self.inner.id).await?; From 15da6458fcc813c0fd7858dc6c093aeaf4550470 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 22:51:15 +0100 Subject: [PATCH 36/58] test(block_storage): add integration test_volume_create_get_delete_simple Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 49 ++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/integration-block-storage.rs diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs new file mode 100644 index 0000000000..3f229823ea --- /dev/null +++ b/tests/integration-block-storage.rs @@ -0,0 +1,49 @@ +// Copyright 2024 Sandro-Alessio Gierens +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Once; + +static INIT: Once = Once::new(); + +async fn set_up() -> openstack::Cloud { + INIT.call_once(|| { + env_logger::init(); + }); + + openstack::Cloud::from_env() + .await + .expect("Failed to create an identity provider from the environment") +} + +#[tokio::test] +async fn test_volume_create_get_delete_simple() { + let os = set_up().await; + + let volume = os + .new_volume(1 as u64) + .create() + .await + .expect("Could not create volume"); + let id = volume.id().clone(); + assert_eq!(volume.name(), ""); + assert_eq!(*volume.size(), 1 as u64); + + let volume2 = os.get_volume(&id).await.expect("Could not get volume"); + assert_eq!(volume2.id(), volume.id()); + + volume.delete().await.expect("Could not delete volume"); + + let volume3 = os.get_volume(id).await; + assert!(volume3.is_err()); +} From 2a4595ad4064a7d8588c22b258e7b6aa1462a4b6 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 23:08:24 +0100 Subject: [PATCH 37/58] test(block_storage): add more asserts to test_volume_create_get_delete_simple Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index 3f229823ea..6740809c1a 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::sync::Once; +use openstack::block_storage::VolumeStatus; static INIT: Once = Once::new(); @@ -36,8 +37,10 @@ async fn test_volume_create_get_delete_simple() { .await .expect("Could not create volume"); let id = volume.id().clone(); - assert_eq!(volume.name(), ""); + assert!(volume.name().is_empty()); + assert!(volume.description().is_none()); assert_eq!(*volume.size(), 1 as u64); + assert_eq!(*volume.status(), VolumeStatus::Available); let volume2 = os.get_volume(&id).await.expect("Could not get volume"); assert_eq!(volume2.id(), volume.id()); From 9b7b123a6273bfa78fd91397c97ca889c4a96e97 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 23:11:02 +0100 Subject: [PATCH 38/58] test(block_storage): add test_volume_create_with_fields Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index 6740809c1a..fd3a988af2 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Once; use openstack::block_storage::VolumeStatus; +use std::sync::Once; static INIT: Once = Once::new(); @@ -50,3 +50,22 @@ async fn test_volume_create_get_delete_simple() { let volume3 = os.get_volume(id).await; assert!(volume3.is_err()); } + +#[tokio::test] +async fn test_volume_create_with_fields() { + let os = set_up().await; + + let volume = os + .new_volume(1 as u64) + .with_name("test_volume") + .with_description("test_description") + .create() + .await + .expect("Could not create volume"); + assert_eq!(volume.name(), "test_volume"); + assert_eq!(*volume.description(), Some("test_description".to_string())); + assert_eq!(*volume.size(), 1 as u64); + assert_eq!(*volume.status(), VolumeStatus::Available); + + volume.delete().await.expect("Could not delete volume"); +} From 4bef6198dac02bdffa1715c47cae3084ef5137e8 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 16 Mar 2024 23:42:06 +0100 Subject: [PATCH 39/58] test(block_storage): remove status asserts from integration tests We probably cannot assume any state here, it could be creating or available. Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index fd3a988af2..46e99428d5 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -40,7 +40,6 @@ async fn test_volume_create_get_delete_simple() { assert!(volume.name().is_empty()); assert!(volume.description().is_none()); assert_eq!(*volume.size(), 1 as u64); - assert_eq!(*volume.status(), VolumeStatus::Available); let volume2 = os.get_volume(&id).await.expect("Could not get volume"); assert_eq!(volume2.id(), volume.id()); @@ -65,7 +64,6 @@ async fn test_volume_create_with_fields() { assert_eq!(volume.name(), "test_volume"); assert_eq!(*volume.description(), Some("test_description".to_string())); assert_eq!(*volume.size(), 1 as u64); - assert_eq!(*volume.status(), VolumeStatus::Available); volume.delete().await.expect("Could not delete volume"); } From c2be624c7414238d8f43fc92f75e2550a7e2a2a7 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 17 Mar 2024 08:25:04 +0100 Subject: [PATCH 40/58] refactor(block_storage): make VolumeCreate.name a String with default "" Even though the cinder documentation states that name is an optional parameter, the API returns an error when this parameter is not serialized. However when setting the default to an empty string Openstack reacts as intended and sets the name to the UUID. Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 5 ++--- src/block_storage/volumes.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 2e6bb12572..43e387eeea 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -161,8 +161,7 @@ pub struct VolumeCreate { pub snapshot_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub backup_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, + pub name: String, // not optional in spec, but doesn't work with None/null, only with "" #[serde(skip_serializing_if = "Option::is_none", rename = "imageRef")] pub image_id: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -192,7 +191,7 @@ impl VolumeCreate { description: None, snapshot_id: None, backup_id: None, - name: None, + name: "".to_string(), image_id: None, volume_type: None, metadata: None, diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 78cad2c707..1ef96d7be7 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -428,7 +428,7 @@ impl NewVolume { creation_inner_field! { #[doc = "Set the name."] - set_name, with_name -> name: optional String + set_name, with_name -> name: String } creation_inner_field! { From a70d9ae45bcfb30aad00434a0742717aabc172ef Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 17 Mar 2024 08:33:35 +0100 Subject: [PATCH 41/58] test(block_storage): fix test_volume_create_with_fields, name is String Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index 46e99428d5..3ffd23a9a0 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -56,7 +56,7 @@ async fn test_volume_create_with_fields() { let volume = os .new_volume(1 as u64) - .with_name("test_volume") + .with_name("test_volume".to_string()) .with_description("test_description") .create() .await From 21b348a277bcf0ce66a6bd7dc21f1c6e232e18ef Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 17 Mar 2024 08:33:56 +0100 Subject: [PATCH 42/58] test(block_storage): fix test_volume_create_get_delete_simple, name becomes id Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index 3ffd23a9a0..f81830dba4 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -37,7 +37,7 @@ async fn test_volume_create_get_delete_simple() { .await .expect("Could not create volume"); let id = volume.id().clone(); - assert!(volume.name().is_empty()); + assert_eq!(*volume.name(), id); assert!(volume.description().is_none()); assert_eq!(*volume.size(), 1 as u64); From 61fa269c6775f2280f638805880bf083146f710d Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 17 Mar 2024 08:34:27 +0100 Subject: [PATCH 43/58] test(block_storage): remove unused import VolumeStatus Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index f81830dba4..cdf3b0a2c9 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use openstack::block_storage::VolumeStatus; use std::sync::Once; static INIT: Once = Once::new(); From 9aeece22a0cebebab23f94b66b17b91e811703ab Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 17 Mar 2024 08:56:56 +0100 Subject: [PATCH 44/58] test(block_storage): fix test_volume_create_get_delete_simple, name is empty Even though its renamed with the UUID later on, the volume returned on creation has an empty string as name. Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index cdf3b0a2c9..5953feb375 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -36,7 +36,7 @@ async fn test_volume_create_get_delete_simple() { .await .expect("Could not create volume"); let id = volume.id().clone(); - assert_eq!(*volume.name(), id); + assert!(volume.name().is_empty()); assert!(volume.description().is_none()); assert_eq!(*volume.size(), 1 as u64); From 6d34c5080445096c169945d0910e00c25fa0a9bb Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sun, 17 Mar 2024 09:28:21 +0100 Subject: [PATCH 45/58] test(block_storage): add sleeps to test_router_create_delete_simple If the get is executed to shortly after the create, it might fail. Same for the delete, it might still exist. Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index 5953feb375..0dbe1c56b0 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -40,11 +40,13 @@ async fn test_volume_create_get_delete_simple() { assert!(volume.description().is_none()); assert_eq!(*volume.size(), 1 as u64); + tokio::time::sleep(std::time::Duration::from_secs(3)).await; let volume2 = os.get_volume(&id).await.expect("Could not get volume"); assert_eq!(volume2.id(), volume.id()); volume.delete().await.expect("Could not delete volume"); + tokio::time::sleep(std::time::Duration::from_secs(3)).await; let volume3 = os.get_volume(id).await; assert!(volume3.is_err()); } From c17a9fb74b6086535436e891bacd2858c62efbf1 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 18 Mar 2024 08:17:53 +0100 Subject: [PATCH 46/58] refactor(block_storage): rename Volume.service_uuid to service_id Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 3 ++- src/block_storage/volumes.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 43e387eeea..80bac15f0c 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -128,7 +128,8 @@ pub struct Volume { pub group_id: Option, pub volumes_links: Option>, pub provider_id: Option, - pub service_uuid: Option, // not optional in spec + #[serde(rename = "service_uuid")] + pub service_id: Option, // not optional in spec pub shared_targets: Option, // not optional in spec pub cluster_name: Option, pub consumes_quota: Option, diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 1ef96d7be7..f1aa70eaa5 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -225,7 +225,7 @@ impl Volume { transparent_property! { #[doc = "UUID of the service the volume is served on."] - service_uuid: ref Option + service_id: ref Option } transparent_property! { From 6e57ee99fde6d0d03f90175bd883954c2eda1e3b Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 18 Mar 2024 08:28:27 +0100 Subject: [PATCH 47/58] refactor(block_storage): remove unneeded refs in Volume transparent properties Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index f1aa70eaa5..5d9ebadba2 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -90,7 +90,7 @@ impl Volume { transparent_property! { #[doc = "Whether the volume is encrypted."] - encrypted: ref bool + encrypted: bool } transparent_property! { @@ -120,7 +120,7 @@ impl Volume { transparent_property! { #[doc = "Size of the volume in GiB."] - size: ref u64 + size: u64 } transparent_property! { @@ -145,7 +145,7 @@ impl Volume { transparent_property! { #[doc = "Status of the volume."] - status: ref protocol::VolumeStatus + status: protocol::VolumeStatus } transparent_property! { @@ -160,7 +160,7 @@ impl Volume { transparent_property! { #[doc = "Whether the volume is multi-attachable."] - multi_attachable: ref bool + multi_attachable: bool } transparent_property! { @@ -230,7 +230,7 @@ impl Volume { transparent_property! { #[doc = "Whether the volume has shared targets."] - shared_targets: ref Option + shared_targets: Option } transparent_property! { @@ -240,12 +240,12 @@ impl Volume { transparent_property! { #[doc = "Whether the volume consumes quota."] - consumes_quota: ref Option + consumes_quota: Option } transparent_property! { #[doc = "Total count of volumes requested before pagination."] - count: ref Option + count: Option } /// Delete the volume. From ca13264008854df8fd71c3b73ce52b92d0868846 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 18 Mar 2024 08:31:36 +0100 Subject: [PATCH 48/58] refactor(block_storage): remove unneeded allow for snake case in protocol Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 80bac15f0c..1665d725ca 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -14,7 +14,6 @@ //! JSON structures and protocol bits for the Block Storage API. -#![allow(non_snake_case)] #![allow(missing_docs)] use serde::{Deserialize, Serialize}; From 9ca4aa40836f3c29c43676740e08954100afd074 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 18 Mar 2024 08:33:48 +0100 Subject: [PATCH 49/58] feat(block_storage): expose VolumeAttachment Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block_storage/mod.rs b/src/block_storage/mod.rs index 0e486af7c3..d41e67faaa 100644 --- a/src/block_storage/mod.rs +++ b/src/block_storage/mod.rs @@ -18,5 +18,5 @@ mod api; mod protocol; mod volumes; -pub use self::protocol::{VolumeSortKey, VolumeStatus}; +pub use self::protocol::{VolumeAttachment, VolumeSortKey, VolumeStatus}; pub use self::volumes::{NewVolume, Volume, VolumeQuery}; From 830cbae808c2d7fefc1485890a97987063019123 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 18 Mar 2024 14:50:25 +0100 Subject: [PATCH 50/58] test(block_storage): remove unneeded dereferenciation of volume.size() Signed-off-by: Sandro-Alessio Gierens --- tests/integration-block-storage.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration-block-storage.rs b/tests/integration-block-storage.rs index 0dbe1c56b0..9ebf3e67a1 100644 --- a/tests/integration-block-storage.rs +++ b/tests/integration-block-storage.rs @@ -38,7 +38,7 @@ async fn test_volume_create_get_delete_simple() { let id = volume.id().clone(); assert!(volume.name().is_empty()); assert!(volume.description().is_none()); - assert_eq!(*volume.size(), 1 as u64); + assert_eq!(volume.size(), 1 as u64); tokio::time::sleep(std::time::Duration::from_secs(3)).await; let volume2 = os.get_volume(&id).await.expect("Could not get volume"); @@ -64,7 +64,7 @@ async fn test_volume_create_with_fields() { .expect("Could not create volume"); assert_eq!(volume.name(), "test_volume"); assert_eq!(*volume.description(), Some("test_description".to_string())); - assert_eq!(*volume.size(), 1 as u64); + assert_eq!(volume.size(), 1 as u64); volume.delete().await.expect("Could not delete volume"); } From 6daf488e0a780ceee3bfc477b9f4b20545cf9aa0 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 18 Mar 2024 15:50:41 +0100 Subject: [PATCH 51/58] refactor(block_storage): make Volume.links/volume_links non-transparent Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/volumes.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 5d9ebadba2..08fb880281 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -73,11 +73,6 @@ impl Volume { attachments: ref Vec } - transparent_property! { - #[doc = "Volume links."] - links: ref Vec - } - transparent_property! { #[doc = "Name of the availability zone."] availability_zone: ref Option @@ -213,11 +208,6 @@ impl Volume { group_id: ref Option } - transparent_property! { - #[doc = "A list of volume links."] - volumes_links: ref Option> - } - transparent_property! { #[doc = "UUID of the provider for the volume."] provider_id: ref Option From a3108a55d4341a7fc28df7d0b305c1bb8215c6df Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 18 Mar 2024 16:03:02 +0100 Subject: [PATCH 52/58] fix(block_storage): make VolumeAttachment.host_name an Option Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 1665d725ca..ac9d009d0d 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -67,7 +67,7 @@ pub struct VolumeAttachment { pub server_id: String, // this should be a reference to a server pub attachment_id: String, pub attached_at: String, - pub host_name: String, + pub host_name: Option, pub volume_id: String, // this should be a reference to a volume pub device: String, pub id: String, From 5932e9224663244fe7d85a40543a0b71e7a24a7f Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Mon, 18 Mar 2024 16:10:29 +0100 Subject: [PATCH 53/58] refactor(block_storage): comment out Volume.migstat for now We are not actually sure what this field does or how it is different from the migration_status, and the naming is a little uninformative. So, we simply skip it for now. Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 7 +++++-- src/block_storage/volumes.rs | 5 ----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index ac9d009d0d..917c2370e6 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -103,8 +103,11 @@ pub struct Volume { pub user_id: String, #[serde(rename = "os-vol-tenant-attr:tenant_id")] pub tenant_id: Option, - #[serde(rename = "os-vol-mig-status-attr:migstat")] - pub migstat: Option, // consider enum + // The naming of this field is a little unintuitive and we are not actually + // sure what it does or how it is different from the migration_status field. + // So we skip it. + // #[serde(rename = "os-vol-mig-status-attr:migstat")] + // pub migstat: Option, // consider enum pub metadata: HashMap, pub status: VolumeStatus, #[serde(rename = "volume_image_metadata")] diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index 08fb880281..bf3dbf8db0 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -128,11 +128,6 @@ impl Volume { tenant_id: ref Option } - transparent_property! { - #[doc = "Migration status."] - migstat: ref Option - } - transparent_property! { #[doc = "Metadata of the volume."] metadata: ref HashMap From e9a3ad8708c5ab8322527db7cd8ac079f1c08b12 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Fri, 22 Mar 2024 23:24:26 +0100 Subject: [PATCH 54/58] feat(block_storage): add bool_from_bootable_string to protocol Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 917c2370e6..59e4f6043a 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -16,7 +16,7 @@ #![allow(missing_docs)] -use serde::{Deserialize, Serialize}; +use serde::{de, Deserializer, Deserialize, Serialize}; use std::collections::HashMap; protocol_enum! { @@ -79,6 +79,20 @@ pub struct Link { pub href: String, } +fn bool_from_bootable_string<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + match String::deserialize(deserializer)?.as_ref() { + "true" => Ok(true), + "false" => Ok(false), + other => Err(de::Error::invalid_value( + de::Unexpected::Str(other), + &"true or false", + )), + } +} + /// A volume. #[derive(Debug, Clone, Deserialize)] pub struct Volume { From f9250dad36acfa40f3a847b7f86c62be89c06980 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Fri, 22 Mar 2024 23:24:56 +0100 Subject: [PATCH 55/58] refactor(block_storage): make protocol::Volume.bootable a boolean field Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 5 +++-- src/block_storage/volumes.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 59e4f6043a..cafd275cd1 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -16,7 +16,7 @@ #![allow(missing_docs)] -use serde::{de, Deserializer, Deserialize, Serialize}; +use serde::{de, Deserialize, Deserializer, Serialize}; use std::collections::HashMap; protocol_enum! { @@ -136,7 +136,8 @@ pub struct Volume { #[serde(rename = "os-vol-mig-status-attr:name_id")] pub name_id: Option, pub name: String, - pub bootable: String, + #[serde(deserialize_with = "bool_from_bootable_string")] + pub bootable: bool, pub created_at: String, pub volumes: Option>, // not optional in spec pub volume_type: String, // consider enum diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index bf3dbf8db0..c3c58454cf 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -175,7 +175,7 @@ impl Volume { transparent_property! { #[doc = "Whether the volume is bootable."] - bootable: ref String + bootable: bool } transparent_property! { From 0b50523b3347edbed329da5d17df3988682f77b1 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 23 Mar 2024 17:51:46 +0100 Subject: [PATCH 56/58] feat(block_storage): add non_exhaustive to VolumeAttachment Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index cafd275cd1..162019d053 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -63,6 +63,7 @@ impl Default for VolumeSortKey { /// A volume attachment. #[derive(Debug, Clone, Deserialize)] +#[non_exhaustive] pub struct VolumeAttachment { pub server_id: String, // this should be a reference to a server pub attachment_id: String, From 92b456bd830957175fadbf76f26cb5b53c25b76d Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 23 Mar 2024 17:52:18 +0100 Subject: [PATCH 57/58] feat(block_storage): add wrapper enum for chrono DateTime and NaiveDateTime Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 162019d053..855d6aa7e4 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -55,6 +55,73 @@ protocol_enum! { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DateTime { + WithTz(chrono::DateTime), + WithoutTz(chrono::NaiveDateTime), +} + +impl<'de> Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match chrono::DateTime::parse_from_rfc3339(&s) { + Ok(dt) => Ok(DateTime::WithTz(dt)), + Err(_) => match chrono::NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S.%f") { + Ok(dt) => Ok(DateTime::WithoutTz(dt)), + Err(_) => Err(serde::de::Error::custom("invalid date format")), + }, + } + } +} + +impl Serialize for DateTime { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + match self { + DateTime::WithTz(dt) => dt.to_rfc3339().serialize(serializer), + DateTime::WithoutTz(dt) => dt + .format("%Y-%m-%dT%H:%M:%S.%f") + .to_string() + .serialize(serializer), + } + } +} + +impl From> for DateTime { + fn from(dt: chrono::DateTime) -> DateTime { + DateTime::WithTz(dt) + } +} + +impl From for DateTime { + fn from(dt: chrono::NaiveDateTime) -> DateTime { + DateTime::WithoutTz(dt) + } +} + +impl From for String { + fn from(dt: DateTime) -> String { + match dt { + DateTime::WithTz(dt) => dt.to_rfc3339(), + DateTime::WithoutTz(dt) => dt.format("%Y-%m-%dT%H:%M:%S.%f").to_string(), + } + } +} + +impl std::fmt::Display for DateTime { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + DateTime::WithTz(dt) => write!(f, "{}", dt.to_rfc3339()), + DateTime::WithoutTz(dt) => write!(f, "{}", dt.format("%Y-%m-%dT%H:%M:%S.%f")), + } + } +} + impl Default for VolumeSortKey { fn default() -> VolumeSortKey { VolumeSortKey::CreatedAt From 4fe60cf57f7cebd5014f580c4d515b8060cbdd83 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Sat, 23 Mar 2024 17:53:42 +0100 Subject: [PATCH 58/58] refactor(block_storage): make Volume.created_at/updated_at a DateTime Signed-off-by: Sandro-Alessio Gierens --- src/block_storage/protocol.rs | 4 ++-- src/block_storage/volumes.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/block_storage/protocol.rs b/src/block_storage/protocol.rs index 855d6aa7e4..711b0d197f 100644 --- a/src/block_storage/protocol.rs +++ b/src/block_storage/protocol.rs @@ -177,7 +177,7 @@ pub struct Volume { pub host: Option, pub encrypted: bool, pub encryption_key_id: Option, - pub updated_at: Option, + pub updated_at: Option, pub replication_status: Option, // not optional in spec, also consider enum pub snapshot_id: Option, pub id: String, @@ -206,7 +206,7 @@ pub struct Volume { pub name: String, #[serde(deserialize_with = "bool_from_bootable_string")] pub bootable: bool, - pub created_at: String, + pub created_at: DateTime, pub volumes: Option>, // not optional in spec pub volume_type: String, // consider enum pub volume_type_id: Option>, // not optional in spec diff --git a/src/block_storage/volumes.rs b/src/block_storage/volumes.rs index c3c58454cf..59f5863a86 100644 --- a/src/block_storage/volumes.rs +++ b/src/block_storage/volumes.rs @@ -95,7 +95,7 @@ impl Volume { transparent_property! { #[doc = "When the volume was last updated."] - updated_at: ref Option + updated_at: Option } transparent_property! { @@ -180,7 +180,7 @@ impl Volume { transparent_property! { #[doc = "When the volume was created."] - created_at: ref String + created_at: protocol::DateTime } transparent_property! {