You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ya...@apache.org on 2016/04/09 01:52:10 UTC
[6/6] mesos git commit: Add utility functions to manipulate XFS
project quotas.
Add utility functions to manipulate XFS project quotas.
Review: https://reviews.apache.org/r/44946/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/a0e96bd2
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/a0e96bd2
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/a0e96bd2
Branch: refs/heads/master
Commit: a0e96bd22da7a39086600c3186fbad61c554e262
Parents: 0313707
Author: James Peach <jp...@apache.org>
Authored: Fri Apr 8 13:49:16 2016 -0700
Committer: Jiang Yan Xu <ya...@jxu.me>
Committed: Fri Apr 8 16:46:08 2016 -0700
----------------------------------------------------------------------
src/Makefile.am | 6 +
.../containerizer/mesos/isolators/xfs/utils.cpp | 384 +++++++++++++++++++
.../containerizer/mesos/isolators/xfs/utils.hpp | 81 ++++
3 files changed, 471 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/a0e96bd2/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 4375b03..f235a6a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -896,6 +896,12 @@ MESOS_LINUX_FILES += \
slave/containerizer/mesos/provisioner/backends/bind.hpp \
slave/containerizer/mesos/provisioner/backends/overlay.hpp
+if ENABLE_XFS_DISK_ISOLATOR
+MESOS_LINUX_FILES += \
+ slave/containerizer/mesos/isolators/xfs/utils.cpp \
+ slave/containerizer/mesos/isolators/xfs/utils.hpp
+endif
+
MESOS_NETWORK_ISOLATOR_FILES = \
linux/routing/handle.cpp \
linux/routing/route.cpp \
http://git-wip-us.apache.org/repos/asf/mesos/blob/a0e96bd2/src/slave/containerizer/mesos/isolators/xfs/utils.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/isolators/xfs/utils.cpp b/src/slave/containerizer/mesos/isolators/xfs/utils.cpp
new file mode 100644
index 0000000..9285183
--- /dev/null
+++ b/src/slave/containerizer/mesos/isolators/xfs/utils.cpp
@@ -0,0 +1,384 @@
+// 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.
+
+// The XFS API headers come from the xfsprogs package. xfsprogs versions
+// earlier than 4.5 contain various internal macros that conflict with
+// libstdc++.
+
+// If ENABLE_GETTEXT is not defined, then the XFS headers will define
+// textdomain() to a while(0) loop. When C++ standard headers try to
+// use textdomain(), compilation errors ensue.
+#define ENABLE_GETTEXT
+#include <xfs/xfs.h>
+#include <xfs/xqm.h>
+#undef ENABLE_GETTEXT
+
+// xfs/platform_defs-x86_64.h defines min() and max() macros which conflict
+// with various min() and max() function definitions.
+#undef min
+#undef max
+
+#include <fts.h>
+
+#include <blkid/blkid.h>
+#include <linux/quota.h>
+#include <sys/quota.h>
+
+#include <stout/check.hpp>
+#include <stout/error.hpp>
+#include <stout/numify.hpp>
+#include <stout/path.hpp>
+
+#include <stout/fs.hpp>
+#include <stout/os.hpp>
+
+#include "slave/containerizer/mesos/isolators/xfs/utils.hpp"
+
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace xfs {
+
+// The quota API defines space limits in terms of in basic
+// blocks (512 bytes).
+static constexpr Bytes BASIC_BLOCK_SIZE = Bytes(512u);
+
+
+// Although XFS itself doesn't define any invalid project IDs,
+// we need a way to know whether or not a project ID was assigned
+// so we use 0 as our sentinel value.
+static constexpr prid_t NON_PROJECT_ID = 0u;
+
+
+static Error nonProjectError()
+{
+ return Error("Invalid project ID '0'");
+}
+
+
+static Try<int> openPath(
+ const string& path,
+ const struct stat& stat)
+{
+ int flags = O_NOFOLLOW | O_RDONLY | O_CLOEXEC;
+
+ // Directories require O_DIRECTORY.
+ flags |= S_ISDIR(stat.st_mode) ? O_DIRECTORY : 0;
+ return os::open(path, flags);
+}
+
+
+static Try<Nothing> setAttributes(
+ int fd,
+ struct fsxattr& attr)
+{
+ if (::xfsctl(nullptr, fd, XFS_IOC_FSSETXATTR, &attr) == -1) {
+ return ErrnoError();
+ }
+
+ return Nothing();
+}
+
+
+static Try<struct fsxattr> getAttributes(int fd)
+{
+ struct fsxattr attr;
+
+ if (::xfsctl(nullptr, fd, XFS_IOC_FSGETXATTR, &attr) == -1) {
+ return ErrnoError();
+ }
+
+ return attr;
+}
+
+
+// Return the path of the device backing the filesystem containing
+// the given path.
+static Try<string> getDeviceForPath(const string& path)
+{
+ struct stat statbuf;
+
+ if (::lstat(path.c_str(), &statbuf) == -1) {
+ return ErrnoError("Unable to access '" + path + "'");
+ }
+
+ char* name = blkid_devno_to_devname(statbuf.st_dev);
+ if (name == nullptr) {
+ return ErrnoError("Unable to get device for '" + path + "'");
+ }
+
+ string devname(name);
+ free(name);
+
+ return devname;
+}
+
+
+namespace internal {
+
+static Try<Nothing> setProjectQuota(
+ const string& path,
+ prid_t projectId,
+ Bytes limit)
+{
+ Try<string> devname = getDeviceForPath(path);
+ if (devname.isError()) {
+ return Error(devname.error());
+ }
+
+ fs_disk_quota_t quota = {0};
+
+ quota.d_version = FS_DQUOT_VERSION;
+
+ // Specify that we are setting a project quota for this ID.
+ quota.d_id = projectId;
+ quota.d_flags = XFS_PROJ_QUOTA;
+
+ // Set both the hard and the soft limit to the same quota, just
+ // for consistency. Functionally all we need is the hard quota.
+ quota.d_fieldmask = FS_DQ_BSOFT | FS_DQ_BHARD;
+
+ quota.d_blk_hardlimit = limit.bytes() / BASIC_BLOCK_SIZE.bytes();
+ quota.d_blk_softlimit = limit.bytes() / BASIC_BLOCK_SIZE.bytes();
+
+ if (::quotactl(QCMD(Q_XSETQLIM, PRJQUOTA),
+ devname.get().c_str(),
+ projectId,
+ reinterpret_cast<caddr_t>("a)) == -1) {
+ return ErrnoError("Failed to set quota for project ID " +
+ stringify(projectId));
+ }
+
+ return Nothing();
+}
+
+
+static Try<Nothing> setProjectId(
+ const string& path,
+ const struct stat& stat,
+ prid_t projectId)
+{
+ Try<int> fd = openPath(path, stat);
+ if (fd.isError()) {
+ return Error("Failed to open '" + path + "': " + fd.error());
+ }
+
+ Try<struct fsxattr> attr = getAttributes(fd.get());
+ if (attr.isError()) {
+ os::close(fd.get());
+ return Error("Failed to get XFS attributes for '" + path + "': " +
+ attr.error());
+ }
+
+ attr->fsx_projid = projectId;
+
+ if (projectId == NON_PROJECT_ID) {
+ attr->fsx_xflags &= ~XFS_XFLAG_PROJINHERIT;
+ } else {
+ attr->fsx_xflags |= XFS_XFLAG_PROJINHERIT;
+ }
+
+ Try<Nothing> status = setAttributes(fd.get(), attr.get());
+ os::close(fd.get());
+
+ if (status.isError()) {
+ return Error("Failed to set XFS attributes for '" + path + "': " +
+ status.error());
+ }
+
+ return Nothing();
+}
+
+} // namespace internal {
+
+
+Result<QuotaInfo> getProjectQuota(
+ const string& path,
+ prid_t projectId)
+{
+ if (projectId == NON_PROJECT_ID) {
+ return nonProjectError();
+ }
+
+ Try<string> devname = getDeviceForPath(path);
+ if (devname.isError()) {
+ return Error(devname.error());
+ }
+
+ fs_disk_quota_t quota = {0};
+
+ quota.d_version = FS_DQUOT_VERSION;
+ quota.d_id = projectId;
+ quota.d_flags = XFS_PROJ_QUOTA;
+
+ // In principle, we should issue a Q_XQUOTASYNC to get an accurate accounting.
+ // However, we don't want to affect performance by continually syncing the
+ // disks, so we accept that the quota information will be slightly out of
+ // date.
+
+ if (::quotactl(QCMD(Q_XGETQUOTA, PRJQUOTA),
+ devname.get().c_str(),
+ projectId,
+ reinterpret_cast<caddr_t>("a)) == -1) {
+ return ErrnoError("Failed to get quota for project ID " +
+ stringify(projectId));
+ }
+
+ // Zero quota means that no quota is assigned.
+ if (quota.d_blk_hardlimit == 0 && quota.d_bcount == 0) {
+ return None();
+ }
+
+ QuotaInfo info;
+ info.limit = BASIC_BLOCK_SIZE * quota.d_blk_hardlimit;
+ info.used = BASIC_BLOCK_SIZE * quota.d_bcount;
+
+ return info;
+}
+
+
+Try<Nothing> setProjectQuota(
+ const string& path,
+ prid_t projectId,
+ Bytes limit)
+{
+ if (projectId == NON_PROJECT_ID) {
+ return nonProjectError();
+ }
+
+ // A 0 limit deletes the quota record. Since the limit is in basic
+ // blocks that effectively means > 512 bytes.
+ if (limit < BASIC_BLOCK_SIZE) {
+ return Error("Quota limit must be >= " + stringify(BASIC_BLOCK_SIZE));
+ }
+
+ return internal::setProjectQuota(path, projectId, limit);
+}
+
+
+Try<Nothing> clearProjectQuota(
+ const string& path,
+ prid_t projectId)
+{
+ if (projectId == NON_PROJECT_ID) {
+ return nonProjectError();
+ }
+
+ return internal::setProjectQuota(path, projectId, Bytes(0));
+}
+
+
+Result<prid_t> getProjectId(
+ const string& directory)
+{
+ struct stat stat;
+
+ if (::lstat(directory.c_str(), &stat) == -1) {
+ return ErrnoError("Failed to access '" + directory);
+ }
+
+ Try<int> fd = openPath(directory, stat);
+ if (fd.isError()) {
+ return Error("Failed to open '" + directory + "': " + fd.error());
+ }
+
+ Try<struct fsxattr> attr = getAttributes(fd.get());
+ os::close(fd.get());
+
+ if (attr.isError()) {
+ return Error("Failed to get XFS attributes for '" + directory + "': " +
+ attr.error());
+ }
+
+ if (attr->fsx_projid == NON_PROJECT_ID) {
+ return None();
+ }
+
+ return attr->fsx_projid;
+}
+
+
+static Try<Nothing> setProjectIdRecursively(
+ const string& directory,
+ prid_t projectId)
+{
+ if (os::stat::islink(directory) || !os::stat::isdir(directory)) {
+ return Error(directory + " is not a directory");
+ }
+
+ char* directory_[] = {const_cast<char*>(directory.c_str()), nullptr};
+
+ FTS* tree = ::fts_open(
+ directory_, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, nullptr);
+ if (tree == nullptr) {
+ return ErrnoError("Failed to open '" + directory + "'");
+ }
+
+ for (FTSENT *node = ::fts_read(tree);
+ node != nullptr; node = ::fts_read(tree)) {
+ if (node->fts_info == FTS_D || node->fts_info == FTS_F) {
+ Try<Nothing> status = internal::setProjectId(
+ node->fts_path, *node->fts_statp, projectId);
+ if (status.isError()) {
+ ::fts_close(tree);
+ return Error(status.error());
+ }
+ }
+ }
+
+ if (errno != 0) {
+ Error error = ErrnoError();
+ ::fts_close(tree);
+ return error;
+ }
+
+ return Nothing();
+}
+
+
+Try<Nothing> setProjectId(
+ const string& directory,
+ prid_t projectId)
+{
+ if (projectId == NON_PROJECT_ID) {
+ return nonProjectError();
+ }
+
+ return setProjectIdRecursively(directory, projectId);
+}
+
+
+Try<Nothing> clearProjectId(
+ const string& directory)
+{
+ return setProjectIdRecursively(directory, NON_PROJECT_ID);
+}
+
+
+Option<Error> validateProjectIds(const IntervalSet<prid_t>& projectRange)
+{
+ if (projectRange.contains(NON_PROJECT_ID)) {
+ return Error("XFS project ID range contains illegal " +
+ stringify(NON_PROJECT_ID) + " value");
+ }
+
+ return None();
+}
+
+} // namespace xfs {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/a0e96bd2/src/slave/containerizer/mesos/isolators/xfs/utils.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/isolators/xfs/utils.hpp b/src/slave/containerizer/mesos/isolators/xfs/utils.hpp
new file mode 100644
index 0000000..654dc73
--- /dev/null
+++ b/src/slave/containerizer/mesos/isolators/xfs/utils.hpp
@@ -0,0 +1,81 @@
+// 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 __XFS_UTILS_HPP__
+#define __XFS_UTILS_HPP__
+
+#include <string>
+
+#include <stout/bytes.hpp>
+#include <stout/interval.hpp>
+#include <stout/nothing.hpp>
+#include <stout/try.hpp>
+
+#include <xfs/xfs_types.h>
+
+namespace mesos {
+namespace internal {
+namespace xfs {
+
+struct QuotaInfo
+{
+ Bytes limit;
+ Bytes used;
+};
+
+
+inline bool operator==(const QuotaInfo& left, const QuotaInfo& right)
+{
+ return left.limit == right.limit && left.used == right.used;
+}
+
+
+Option<Error> validateProjectIds(const IntervalSet<prid_t>& projectRange);
+
+
+Result<QuotaInfo> getProjectQuota(
+ const std::string& path,
+ prid_t projectId);
+
+
+Try<Nothing> setProjectQuota(
+ const std::string& path,
+ prid_t projectId,
+ Bytes limit);
+
+
+Try<Nothing> clearProjectQuota(
+ const std::string& path,
+ prid_t projectId);
+
+
+Result<prid_t> getProjectId(
+ const std::string& directory);
+
+
+Try<Nothing> setProjectId(
+ const std::string& directory,
+ prid_t projectId);
+
+
+Try<Nothing> clearProjectId(
+ const std::string& directory);
+
+} // namespace xfs {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __XFS_UTILS_HPP__