This is an automated email from the ASF dual-hosted git repository. chhsiao pushed a commit to branch 1.8.x in repository https://gitbox.apache.org/repos/asf/mesos.git
commit e22d60212d3859b69b64b8b7c4d063856df29665 Author: Chun-Hung Hsiao <[email protected]> AuthorDate: Thu Apr 4 16:27:48 2019 -0700 Added capabilities and evolve/devolve helpers for CSI v1. This patch adds capability helper structures for CSI v1 services, and evolve/devolve helpers to convert between CSI v1 protobufs and their unversioned counterparts. Review: https://reviews.apache.org/r/70400 --- src/CMakeLists.txt | 1 + src/Makefile.am | 2 + src/csi/v1_utils.cpp | 242 ++++++++++++++++++++++++++++++++++++++++++ src/csi/v1_utils.hpp | 218 +++++++++++++++++++++++++++++++++++++ src/tests/csi_utils_tests.cpp | 50 +++++++-- 5 files changed, 507 insertions(+), 6 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51c7a04..d36d0be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -243,6 +243,7 @@ set(CSI_SRC csi/v0_volume_manager.cpp csi/v1.cpp csi/v1_client.cpp + csi/v1_utils.cpp csi/volume_manager.cpp) set(DOCKER_SRC diff --git a/src/Makefile.am b/src/Makefile.am index 9c0180f..64898df 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1598,6 +1598,8 @@ libcsi_la_SOURCES = \ csi/v1.cpp \ csi/v1_client.cpp \ csi/v1_client.hpp \ + csi/v1_utils.cpp \ + csi/v1_utils.hpp \ csi/volume_manager.cpp \ csi/volume_manager.hpp diff --git a/src/csi/v1_utils.cpp b/src/csi/v1_utils.cpp new file mode 100644 index 0000000..e74138b --- /dev/null +++ b/src/csi/v1_utils.cpp @@ -0,0 +1,242 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#include "csi/v1_utils.hpp" + +using google::protobuf::RepeatedPtrField; + +namespace mesos { +namespace csi { +namespace v1 { + +// Helper for repeated field devolving to `T1` from `T2`. +template <typename T1, typename T2> +RepeatedPtrField<T1> devolve(RepeatedPtrField<T2> from) +{ + RepeatedPtrField<T1> to; + foreach (const T2& value, from) { + *to.Add() = devolve(value); + } + + return to; +} + + +types::VolumeCapability::BlockVolume devolve( + const VolumeCapability::BlockVolume& block) +{ + return types::VolumeCapability::BlockVolume(); +} + + +types::VolumeCapability::MountVolume devolve( + const VolumeCapability::MountVolume& mount) +{ + types::VolumeCapability::MountVolume result; + result.set_fs_type(mount.fs_type()); + *result.mutable_mount_flags() = mount.mount_flags(); + return result; +} + + +types::VolumeCapability::AccessMode devolve( + const VolumeCapability::AccessMode& accessMode) +{ + types::VolumeCapability::AccessMode result; + + switch (accessMode.mode()) { + case VolumeCapability::AccessMode::UNKNOWN: { + result.set_mode(types::VolumeCapability::AccessMode::UNKNOWN); + break; + } + case VolumeCapability::AccessMode::SINGLE_NODE_WRITER: { + result.set_mode(types::VolumeCapability::AccessMode::SINGLE_NODE_WRITER); + break; + } + case VolumeCapability::AccessMode::SINGLE_NODE_READER_ONLY: { + result.set_mode( + types::VolumeCapability::AccessMode::SINGLE_NODE_READER_ONLY); + break; + } + case VolumeCapability::AccessMode::MULTI_NODE_READER_ONLY: { + result.set_mode( + types::VolumeCapability::AccessMode::MULTI_NODE_READER_ONLY); + break; + } + case VolumeCapability::AccessMode::MULTI_NODE_SINGLE_WRITER: { + result.set_mode( + types::VolumeCapability::AccessMode::MULTI_NODE_SINGLE_WRITER); + break; + } + case VolumeCapability::AccessMode::MULTI_NODE_MULTI_WRITER: { + result.set_mode( + types::VolumeCapability::AccessMode::MULTI_NODE_MULTI_WRITER); + break; + } + // NOTE: We avoid using a default clause for the following values in + // proto3's open enum to enable the compiler to detect missing enum cases + // for us. See: https://github.com/google/protobuf/issues/3917 + case google::protobuf::kint32min: + case google::protobuf::kint32max: { + UNREACHABLE(); + } + } + + return result; +} + + +types::VolumeCapability devolve(const VolumeCapability& capability) +{ + types::VolumeCapability result; + + switch (capability.access_type_case()) { + case VolumeCapability::kBlock: { + *result.mutable_block() = devolve(capability.block()); + break; + } + case VolumeCapability::kMount: { + *result.mutable_mount() = devolve(capability.mount()); + break; + } + case VolumeCapability::ACCESS_TYPE_NOT_SET: { + break; + } + } + + if (capability.has_access_mode()) { + *result.mutable_access_mode() = devolve(capability.access_mode()); + } + + return result; +} + + +RepeatedPtrField<types::VolumeCapability> devolve( + const RepeatedPtrField<VolumeCapability>& capabilities) +{ + return devolve<types::VolumeCapability>(capabilities); +} + + +// Helper for repeated field evolving to `T1` from `T2`. +template <typename T1, typename T2> +RepeatedPtrField<T1> evolve(RepeatedPtrField<T2> from) +{ + RepeatedPtrField<T1> to; + foreach (const T2& value, from) { + *to.Add() = evolve(value); + } + + return to; +} + + +VolumeCapability::BlockVolume evolve( + const types::VolumeCapability::BlockVolume& block) +{ + return VolumeCapability::BlockVolume(); +} + + +VolumeCapability::MountVolume evolve( + const types::VolumeCapability::MountVolume& mount) +{ + VolumeCapability::MountVolume result; + result.set_fs_type(mount.fs_type()); + *result.mutable_mount_flags() = mount.mount_flags(); + return result; +} + + +VolumeCapability::AccessMode evolve( + const types::VolumeCapability::AccessMode& accessMode) +{ + VolumeCapability::AccessMode result; + + switch (accessMode.mode()) { + case types::VolumeCapability::AccessMode::UNKNOWN: { + result.set_mode(VolumeCapability::AccessMode::UNKNOWN); + break; + } + case types::VolumeCapability::AccessMode::SINGLE_NODE_WRITER: { + result.set_mode(VolumeCapability::AccessMode::SINGLE_NODE_WRITER); + break; + } + case types::VolumeCapability::AccessMode::SINGLE_NODE_READER_ONLY: { + result.set_mode(VolumeCapability::AccessMode::SINGLE_NODE_READER_ONLY); + break; + } + case types::VolumeCapability::AccessMode::MULTI_NODE_READER_ONLY: { + result.set_mode(VolumeCapability::AccessMode::MULTI_NODE_READER_ONLY); + break; + } + case types::VolumeCapability::AccessMode::MULTI_NODE_SINGLE_WRITER: { + result.set_mode(VolumeCapability::AccessMode::MULTI_NODE_SINGLE_WRITER); + break; + } + case types::VolumeCapability::AccessMode::MULTI_NODE_MULTI_WRITER: { + result.set_mode(VolumeCapability::AccessMode::MULTI_NODE_MULTI_WRITER); + break; + } + // NOTE: We avoid using a default clause for the following values in + // proto3's open enum to enable the compiler to detect missing enum cases + // for us. See: https://github.com/google/protobuf/issues/3917 + case google::protobuf::kint32min: + case google::protobuf::kint32max: { + UNREACHABLE(); + } + } + + return result; +} + + +VolumeCapability evolve(const types::VolumeCapability& capability) +{ + VolumeCapability result; + + switch (capability.access_type_case()) { + case types::VolumeCapability::kBlock: { + *result.mutable_block() = evolve(capability.block()); + break; + } + case types::VolumeCapability::kMount: { + *result.mutable_mount() = evolve(capability.mount()); + break; + } + case types::VolumeCapability::ACCESS_TYPE_NOT_SET: { + break; + } + } + + if (capability.has_access_mode()) { + *result.mutable_access_mode() = evolve(capability.access_mode()); + } + + return result; +} + + +RepeatedPtrField<VolumeCapability> devolve( + const RepeatedPtrField<types::VolumeCapability>& capabilities) +{ + return evolve<VolumeCapability>(capabilities); +} + +} // namespace v1 { +} // namespace csi { +} // namespace mesos { diff --git a/src/csi/v1_utils.hpp b/src/csi/v1_utils.hpp new file mode 100644 index 0000000..11a64d7 --- /dev/null +++ b/src/csi/v1_utils.hpp @@ -0,0 +1,218 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#ifndef __CSI_V1_UTILS_HPP__ +#define __CSI_V1_UTILS_HPP__ + +#include <google/protobuf/message.h> + +#include <mesos/csi/types.hpp> +#include <mesos/csi/v1.hpp> + +#include <stout/foreach.hpp> +#include <stout/unreachable.hpp> + +namespace mesos { +namespace csi { +namespace v1 { + +struct PluginCapabilities +{ + PluginCapabilities() = default; + + template <typename Iterable> + PluginCapabilities(const Iterable& capabilities) + { + foreach (const auto& capability, capabilities) { + if (capability.has_service() && + PluginCapability::Service::Type_IsValid( + capability.service().type())) { + switch (capability.service().type()) { + case PluginCapability::Service::UNKNOWN: + break; + case PluginCapability::Service::CONTROLLER_SERVICE: + controllerService = true; + break; + case PluginCapability::Service::VOLUME_ACCESSIBILITY_CONSTRAINTS: + volumeAccessibilityConstraints = true; + break; + + // NOTE: We avoid using a default clause for the following values in + // proto3's open enum to enable the compiler to detect missing enum + // cases for us. See: https://github.com/google/protobuf/issues/3917 + case google::protobuf::kint32min: + case google::protobuf::kint32max: + UNREACHABLE(); + } + } + + if (capability.has_volume_expansion() && + PluginCapability::VolumeExpansion::Type_IsValid( + capability.volume_expansion().type())) { + switch (capability.volume_expansion().type()) { + case PluginCapability::VolumeExpansion::UNKNOWN: + break; + case PluginCapability::VolumeExpansion::ONLINE: + volumeExpansionOnline = true; + volumeExpansionOffline = false; + break; + case PluginCapability::VolumeExpansion::OFFLINE: + volumeExpansionOnline = false; + volumeExpansionOffline = true; + break; + + // NOTE: We avoid using a default clause for the following values in + // proto3's open enum to enable the compiler to detect missing enum + // cases for us. See: https://github.com/google/protobuf/issues/3917 + case google::protobuf::kint32min: + case google::protobuf::kint32max: + UNREACHABLE(); + } + } + } + } + + bool controllerService = false; + bool volumeAccessibilityConstraints = false; + bool volumeExpansionOnline = false; + bool volumeExpansionOffline = false; +}; + + +struct ControllerCapabilities +{ + ControllerCapabilities() = default; + + template <typename Iterable> + ControllerCapabilities(const Iterable& capabilities) + { + foreach (const auto& capability, capabilities) { + if (capability.has_rpc() && + ControllerServiceCapability::RPC::Type_IsValid( + capability.rpc().type())) { + switch (capability.rpc().type()) { + case ControllerServiceCapability::RPC::UNKNOWN: + break; + case ControllerServiceCapability::RPC::CREATE_DELETE_VOLUME: + createDeleteVolume = true; + break; + case ControllerServiceCapability::RPC::PUBLISH_UNPUBLISH_VOLUME: + publishUnpublishVolume = true; + break; + case ControllerServiceCapability::RPC::LIST_VOLUMES: + listVolumes = true; + break; + case ControllerServiceCapability::RPC::GET_CAPACITY: + getCapacity = true; + break; + case ControllerServiceCapability::RPC::CREATE_DELETE_SNAPSHOT: + createDeleteSnapshot = true; + break; + case ControllerServiceCapability::RPC::LIST_SNAPSHOTS: + listSnapshots = true; + break; + case ControllerServiceCapability::RPC::CLONE_VOLUME: + cloneVolume = true; + break; + case ControllerServiceCapability::RPC::PUBLISH_READONLY: + publishReadonly = true; + break; + case ControllerServiceCapability::RPC::EXPAND_VOLUME: + expandVolume = true; + break; + + // NOTE: We avoid using a default clause for the following values in + // proto3's open enum to enable the compiler to detect missing enum + // cases for us. See: https://github.com/google/protobuf/issues/3917 + case google::protobuf::kint32min: + case google::protobuf::kint32max: + UNREACHABLE(); + } + } + } + } + + bool createDeleteVolume = false; + bool publishUnpublishVolume = false; + bool listVolumes = false; + bool getCapacity = false; + bool createDeleteSnapshot = false; + bool listSnapshots = false; + bool cloneVolume = false; + bool publishReadonly = false; + bool expandVolume = false; +}; + + +struct NodeCapabilities +{ + NodeCapabilities() = default; + + template <typename Iterable> + NodeCapabilities(const Iterable& capabilities) + { + foreach (const auto& capability, capabilities) { + if (capability.has_rpc() && + NodeServiceCapability::RPC::Type_IsValid(capability.rpc().type())) { + switch (capability.rpc().type()) { + case NodeServiceCapability::RPC::UNKNOWN: + break; + case NodeServiceCapability::RPC::STAGE_UNSTAGE_VOLUME: + stageUnstageVolume = true; + break; + case NodeServiceCapability::RPC::GET_VOLUME_STATS: + getVolumeStats = true; + break; + case NodeServiceCapability::RPC::EXPAND_VOLUME: + expandVolume = true; + break; + + // NOTE: We avoid using a default clause for the following values in + // proto3's open enum to enable the compiler to detect missing enum + // cases for us. See: https://github.com/google/protobuf/issues/3917 + case google::protobuf::kint32min: + case google::protobuf::kint32max: + UNREACHABLE(); + } + } + } + } + + bool stageUnstageVolume = false; + bool getVolumeStats = false; + bool expandVolume = false; +}; + + +// Helpers to devolve CSI v1 protobufs to their unversioned counterparts. +types::VolumeCapability devolve(const VolumeCapability& capability); + +google::protobuf::RepeatedPtrField<types::VolumeCapability> devolve( + const google::protobuf::RepeatedPtrField<VolumeCapability>& capabilities); + + +// Helpers to evolve unversioned CSI protobufs to their v1 counterparts. +VolumeCapability evolve(const types::VolumeCapability& capability); + +google::protobuf::RepeatedPtrField<VolumeCapability> evolve( + const google::protobuf::RepeatedPtrField<types::VolumeCapability>& + capabilities); + +} // namespace v1 { +} // namespace csi { +} // namespace mesos { + +#endif // __CSI_V1_UTILS_HPP__ diff --git a/src/tests/csi_utils_tests.cpp b/src/tests/csi_utils_tests.cpp index b9d0ca8..5ebd0e1 100644 --- a/src/tests/csi_utils_tests.cpp +++ b/src/tests/csi_utils_tests.cpp @@ -23,23 +23,62 @@ #include <mesos/csi/types.hpp> #include <mesos/csi/v0.hpp> +#include <mesos/csi/v1.hpp> #include "csi/v0_utils.hpp" +#include "csi/v1_utils.hpp" namespace util = google::protobuf::util; using std::string; using std::vector; +// NOTE: We define the following function templates in proper namespaces so +// argument-dependent lookup can do the magic to pick up the right functions +// for a given protobuf in the tests. +namespace csi { +namespace v0 { + +template <typename T> +T devolveAndEvolve(const T& t) +{ + return mesos::csi::v0::evolve(mesos::csi::v0::devolve(t)); +} + +} // namespace v0 { + + +namespace v1 { + +template <typename T> +T devolveAndEvolve(const T& t) +{ + return mesos::csi::v1::evolve(mesos::csi::v1::devolve(t)); +} + +} // namespace v1 { +} // namespace csi { + + namespace mesos { namespace internal { namespace tests { +template <typename T> +class CSIUtilsTest : public testing::Test {}; + + +using VolumeCapabilityTypes = testing::Types< + csi::v0::VolumeCapability, + csi::v1::VolumeCapability>; + + +TYPED_TEST_CASE(CSIUtilsTest, VolumeCapabilityTypes); + + // This test verifies that a versioned CSI `VolumeCapability` protobuf can be // devolved to an unversioned protobuf then be evolved back to the same one. -// -// TODO(chhsiao): Parameterize this test with CSI versions. -TEST(CsiUtilsTest, VolumeCapabilityEvolve) +TYPED_TEST(CSIUtilsTest, DevolveAndEvolve) { // The following JSON examples contains both valid and invalid CSI volume // capabilities. However, no matter if the capability is valid, they should be @@ -155,10 +194,9 @@ TEST(CsiUtilsTest, VolumeCapabilityEvolve) foreach (const string& example, examples) { // NOTE: We use Google's JSON utility functions for proto3. - csi::v0::VolumeCapability versioned; + TypeParam versioned; ASSERT_EQ(util::Status::OK, util::JsonStringToMessage(example, &versioned)); - EXPECT_EQ(versioned, csi::v0::evolve(csi::v0::devolve(versioned))) - << example; + EXPECT_EQ(versioned, devolveAndEvolve(versioned)) << example; } }
