blob: 82c69d8b916a933f13e1185ffbf8ec65afd04ccf [file] [log] [blame]
// 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 "resource_provider/storage/disk_profile_utils.hpp"
#include <google/protobuf/util/json_util.h>
#include <stout/bytes.hpp>
#include <stout/check.hpp>
#include <stout/foreach.hpp>
#include <stout/unreachable.hpp>
using std::string;
using mesos::resource_provider::DiskProfileMapping;
namespace mesos {
namespace internal {
namespace storage {
Try<DiskProfileMapping> parseDiskProfileMapping(
const string& data)
{
// Use Google's JSON utility function to parse the JSON string.
DiskProfileMapping output;
google::protobuf::util::JsonParseOptions options;
options.ignore_unknown_fields = true;
google::protobuf::util::Status status =
google::protobuf::util::JsonStringToMessage(data, &output, options);
if (!status.ok()) {
return Error(
"Failed to parse DiskProfileMapping message: " +
status.ToString());
}
Option<Error> validation = validate(output);
if (validation.isSome()) {
return Error(
"Fetched profile mapping failed validation with: " +
validation->message);
}
return output;
}
bool isSelectedResourceProvider(
const DiskProfileMapping::CSIManifest& profileManifest,
const ResourceProviderInfo& resourceProviderInfo)
{
switch (profileManifest.selector_case()) {
case DiskProfileMapping::CSIManifest::kResourceProviderSelector: {
CHECK(profileManifest.has_resource_provider_selector());
const auto& selector = profileManifest.resource_provider_selector();
foreach (const auto& resourceProvider, selector.resource_providers()) {
if (resourceProviderInfo.type() == resourceProvider.type() &&
resourceProviderInfo.name() == resourceProvider.name()) {
return true;
}
}
return false;
}
case DiskProfileMapping::CSIManifest::kCsiPluginTypeSelector: {
CHECK(profileManifest.has_csi_plugin_type_selector());
const auto& selector = profileManifest.csi_plugin_type_selector();
return resourceProviderInfo.has_storage() &&
resourceProviderInfo.storage().plugin().type() ==
selector.plugin_type();
}
case DiskProfileMapping::CSIManifest::SELECTOR_NOT_SET: {
UNREACHABLE();
}
}
UNREACHABLE();
}
static Option<Error> validateSelector(
const DiskProfileMapping::CSIManifest& profileManifest)
{
switch (profileManifest.selector_case()) {
case DiskProfileMapping::CSIManifest::kResourceProviderSelector: {
if (!profileManifest.has_resource_provider_selector()) {
return Error("Expecting 'resource_provider_selector' to be present");
}
const auto& selector = profileManifest.resource_provider_selector();
foreach (const auto& resourceProvider, selector.resource_providers()) {
if (resourceProvider.type().empty()) {
return Error(
"'type' is a required field for ResourceProviderSelector");
}
if (resourceProvider.name().empty()) {
return Error(
"'name' is a required field for ResourceProviderSelector");
}
}
break;
}
case DiskProfileMapping::CSIManifest::kCsiPluginTypeSelector: {
if (!profileManifest.has_csi_plugin_type_selector()) {
return Error("Expecting 'csi_plugin_type_selector' to be present");
}
const auto& selector = profileManifest.csi_plugin_type_selector();
if (selector.plugin_type().empty()) {
return Error(
"'plugin_type' is a required field for CSIPluginTypeSelector");
}
break;
}
case DiskProfileMapping::CSIManifest::SELECTOR_NOT_SET: {
return Error("Expecting a selector to be present");
}
}
return None();
}
Option<Error> validate(const DiskProfileMapping& mapping)
{
foreach (const auto& entry, mapping.profile_matrix()) {
Option<Error> selector = validateSelector(entry.second);
if (selector.isSome()) {
return Error(
"Profile '" + entry.first + "' has no valid selector: " +
selector->message);
}
if (!entry.second.has_volume_capabilities()) {
return Error(
"Profile '" + entry.first +
"' is missing the required field 'volume_capabilities'");
}
Option<Error> capabilities = validate(entry.second.volume_capabilities());
if (capabilities.isSome()) {
return Error(
"Profile '" + entry.first + "' has an invalid VolumeCapability: " +
capabilities->message);
}
// NOTE: The `create_parameters` field is optional and needs no further
// validation after parsing.
}
return None();
}
// TODO(chhsiao): Move this to CSI validation implementation file.
Option<Error> validate(const csi::v0::VolumeCapability& capability)
{
if (capability.has_mount()) {
// The total size of this repeated field may not exceed 4 KB.
//
// TODO(josephw): The specification does not state how this maximum
// size is calculated. So this check is conservative and does not
// include padding or array separators in the size calculation.
size_t size = 0;
foreach (const string& flag, capability.mount().mount_flags()) {
size += flag.size();
}
if (Bytes(size) > Kilobytes(4)) {
return Error("Size of 'mount_flags' may not exceed 4 KB");
}
}
if (!capability.has_access_mode()) {
return Error("'access_mode' is a required field");
}
if (capability.access_mode().mode() ==
csi::v0::VolumeCapability::AccessMode::UNKNOWN) {
return Error("'access_mode.mode' is unknown or not set");
}
return None();
}
} // namespace storage {
} // namespace internal {
} // namespace mesos {