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>(&quota)) == -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>(&quota)) == -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__