lxcpp: cgroups API implementation (part 2) 27/49827/9
authorKrzysztof Dynowski <k.dynowski@samsung.com>
Mon, 12 Oct 2015 15:38:27 +0000 (17:38 +0200)
committerJan Olszak <j.olszak@samsung.com>
Tue, 27 Oct 2015 11:08:53 +0000 (04:08 -0700)
[Feature]       Control-groups API for containers
[Cause]         N/A
[Solution]      N/A
[Verification]  build, install, run unit tests

Change-Id: Ica81fcc9b9f31118dfe6167a6b55f814c6ec0453

common/utils/fs.cpp
common/utils/fs.hpp
common/utils/text.cpp
common/utils/text.hpp
libs/lxcpp/cgroups/cgroup.cpp
libs/lxcpp/cgroups/cgroup.hpp
libs/lxcpp/cgroups/devices.cpp [new file with mode: 0644]
libs/lxcpp/cgroups/devices.hpp [new file with mode: 0644]
libs/lxcpp/cgroups/subsystem.cpp
tests/unit_tests/lxcpp/ut-cgroups.cpp

index bae21f5..cd70e3b 100644 (file)
@@ -47,6 +47,11 @@ namespace fs = boost::filesystem;
 
 namespace utils {
 
+std::string readFileStream(const std::string& path)
+{
+    std::ifstream is(path);
+    return std::string(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>());
+}
 
 std::string readFileContent(const std::string& path)
 {
index 2b95073..e533670 100644 (file)
 namespace utils {
 
 /**
- * Reads the content of a file; Throws exception on error
+ * Reads the content of file stream (no seek); Throws exception on error
+ */
+std::string readFileStream(const std::string& path);
+
+/**
+ * Reads the content of a file (performs seek); Throws exception on error
  */
 std::string readFileContent(const std::string& path);
 
index 72ba65f..eebc8f4 100644 (file)
@@ -22,7 +22,6 @@
  */
 
 #include "utils/text.hpp"
-#include <sstream>
 
 namespace utils {
 namespace {
@@ -41,18 +40,6 @@ std::string toHexString(const void *data, unsigned len)
     return s;
 }
 
-std::string join(const std::vector<std::string>& vec, const char *delim)
-{
-    std::stringstream res;
-    for (const auto& s : vec) {
-        if (res.tellp() > 0) {
-            res << delim;
-        }
-        res << s;
-    }
-    return res.str();
-}
-
 std::vector<std::string> split(const std::string& str, const std::string& delim)
 {
     std::vector<std::string> tokens;
index 465f649..5920b9f 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <string>
 #include <vector>
+#include <sstream>
 
 namespace utils {
 
@@ -50,7 +51,19 @@ inline bool endsWith(std::string const &value, std::string const &part)
  */
 std::string toHexString(const void *data, unsigned len);
 
-std::string join(const std::vector<std::string>& vec, const char *delim);
+template<typename T>
+std::string join(const std::vector<T>& vec, const char *delim)
+{
+    std::stringstream res;
+    for (const auto& s : vec) {
+        if (res.tellp()>0) {
+            res << delim;
+        }
+        res << s;
+    }
+    return res.str();
+}
+
 std::vector<std::string> split(const std::string& str, const std::string& delim);
 
 } // namespace utils
index 688058d..5e72d21 100644 (file)
@@ -24,6 +24,7 @@
 #include "lxcpp/cgroups/cgroup.hpp"
 #include "lxcpp/exception.hpp"
 #include "utils/fs.hpp"
+#include "utils/text.hpp"
 #include "logger/logger.hpp"
 
 namespace fs = boost::filesystem;
@@ -31,24 +32,28 @@ namespace fs = boost::filesystem;
 namespace lxcpp {
 
 namespace {
-std::string getSubsysName(const std::string& s) {
+std::string getSubsysName(const std::string& s)
+{
     auto p = s.find(':');
     if (p == std::string::npos) {
-        const std::string msg = "wgrong cgroup format";
+        const std::string msg = "wrong subsys format " + s;
         LOGE(msg);
         throw CGroupException(msg);
     }
     return s.substr(0, p);
 }
-std::string getCGroupName(const std::string& s) {
+
+std::string getCGroupName(const std::string& s)
+{
     auto p = s.find(':');
     if (p == std::string::npos) {
-        const std::string msg = "wgrong cgroup format";
+        const std::string msg = "wrong cgroup format " + s;
         LOGE(msg);
         throw CGroupException(msg);
     }
     return s.substr(p + 1);
 }
+
 } // namespace
 
 CGroup::CGroup(const std::string& subsysAndCgroup) :
@@ -72,25 +77,85 @@ void CGroup::create()
 void CGroup::destroy()
 {
     const fs::path path = fs::path(mSubsys.getMountPoint()) / mName;
-    fs::remove_all(path);
+    //remove_all is not good for cgroup filesystem
+    fs::remove(path);
+}
+
+void CGroup::setCommonValue(const std::string& param, const std::string& value)
+{
+    const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / ("cgroup." + param);
+    if (!utils::saveFileContent(path.string(), value)) {
+        const std::string msg = "Invalid param " + param;
+        LOGE(msg);
+        throw CGroupException(msg);
+    }
+}
+
+std::string CGroup::getCommonValue(const std::string& param) const
+{
+    const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / ("cgroup." + param);
+    return utils::readFileStream(path.string());
 }
 
 void CGroup::setValue(const std::string& param, const std::string& value)
 {
     const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / (mSubsys.getName() + "." + param);
-    utils::saveFileContent(path.string(), value);
+    if (!utils::saveFileContent(path.string(), value)) {
+        const std::string msg = "Invalid param " + param;
+        LOGE(msg);
+        throw CGroupException(msg);
+    }
 }
 
 std::string CGroup::getValue(const std::string& param) const
 {
     const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / (mSubsys.getName() + "." + param);
-    return utils::readFileContent(path.string());
+    return utils::readFileStream(path.string());
+}
+
+void CGroup::assignGroup(pid_t pid)
+{
+    setCommonValue("procs", std::to_string(pid));
 }
 
-void CGroup::assignProcess(pid_t pid)
+void CGroup::assignPid(pid_t pid)
 {
     const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / "tasks";
     utils::saveFileContent(path.string(),  std::to_string(pid));
 }
 
+std::vector<pid_t> CGroup::getPids() const
+{
+    const fs::path path = fs::path(mSubsys.getMountPoint()) / mName / "tasks";
+    std::ifstream fileStream(path.string());
+    if (!fileStream.good()) {
+        const std::string msg = "Failed to open " + path.string();
+        LOGE(msg);
+        throw CGroupException(msg);
+    }
+
+    std::vector<pid_t> pids;
+    while (fileStream.good()) {
+        int pid;
+        fileStream >> pid;
+        pids.push_back(pid);
+    }
+
+    return pids;
+}
+
+CGroup CGroup::getCGroup(const std::string& subsys, pid_t pid)
+{
+    std::vector<std::string> cgroups = Subsystem::getCGroups(pid);
+    for (const auto& i : cgroups) {
+        if (utils::beginsWith(i, subsys + ":")) {
+            return CGroup(i);
+        }
+    }
+
+    const std::string msg = "cgroup not found for pid " + std::to_string(pid);
+    LOGE(msg);
+    throw CGroupException(msg);
+}
+
 } //namespace lxcpp
index f8d5698..c65c12d 100644 (file)
@@ -65,6 +65,18 @@ public:
     void destroy();
 
     /**
+     * Set common 'cgroup' paramter
+     * Equivalent of: echo value > mSubsys_path/mName/cgroup.param
+     */
+    void setCommonValue(const std::string& param, const std::string& value);
+
+    /**
+     * Get common 'cgroup' paramter
+     * Equivalent of: cat mSubsys_path/mName/cgroup.param
+     */
+    std::string getCommonValue(const std::string& param) const;
+
+    /**
      * Set cgroup parameter to value (name validity depends on subsystem)
      * Equivalent of: echo value > mSubsys_path/mName/mSubsys_name.param
      */
@@ -77,10 +89,27 @@ public:
     std::string getValue(const std::string& param) const;
 
     /**
-     * Assign process to this cgroup (will be removed from previous cgroup automatically)
+     * Assign all processes in threadgroup of pid to this cgroup
+     * Equivalent of: echo pid > mSubsys_path/mName/cgroup.procs
+     */
+    void assignGroup(pid_t pid);
+
+    /**
+     * Assign single process to this cgroup (will be removed from previous cgroup automatically)
      * Equivalent of: echo pid > mSubsys_path/mName/tasks
      */
-    void assignProcess(pid_t pid);
+    void assignPid(pid_t pid);
+
+    /**
+     * Get list of pid assigned to this group
+     * Equivalent of: cat mSubsys_path/mName/tasks
+     */
+    std::vector<pid_t> getPids() const;
+
+    /**
+     * Get cgroup of process pid in given subsystem
+     */
+    static CGroup getCGroup(const std::string& subsys, pid_t pid);
 
 private:
     const Subsystem mSubsys; // referred subsystem
diff --git a/libs/lxcpp/cgroups/devices.cpp b/libs/lxcpp/cgroups/devices.cpp
new file mode 100644 (file)
index 0000000..6bf8201
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ *  Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License version 2.1 as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * @file
+ * @author  Krzysztof Dynowski (k.dynowski@samsumg.com)
+ * @brief   Control-groups management, devices
+ */
+
+#include "lxcpp/cgroups/devices.hpp"
+#include "lxcpp/exception.hpp"
+#include "utils/text.hpp"
+#include "logger/logger.hpp"
+
+#include <regex>
+
+namespace lxcpp {
+
+namespace {
+std::string devString(int n)
+{
+    return  n >= -1 ? std::to_string(n) : "*";
+}
+
+DevicePermission& parsePerms(DevicePermission& p,const std::string& line)
+{
+    std::string re = "^([a-z]) ([0-9]+|\\*):([0-9]+|\\*) ([a-z]+)$";
+    std::smatch match;
+    try {
+        std::regex rgx(re);
+        if (!std::regex_search(line, match, rgx)) {
+            throw CGroupException("wrong input: " + line);
+        }
+    } catch (CGroupException) {
+        throw;
+    } catch (std::runtime_error e) {
+        throw std::runtime_error(e.what() + std::string(" update your c++ libs"));
+    }
+
+    p.type = match.str(1).at(0);
+    p.major = match.str(2) == "*" ? -1 : std::stoi(match.str(2));
+    p.minor = match.str(3) == "*" ? -1 : std::stoi(match.str(3));
+    p.permission = match.str(4);
+    return p;
+}
+
+} //namespace
+
+void DevicesCGroup::allow(DevicePermission p)
+{
+    allow(p.type, p.major, p.minor, p.permission);
+}
+
+void DevicesCGroup::deny(DevicePermission p)
+{
+    deny(p.type, p.major, p.minor, p.permission);
+}
+
+void DevicesCGroup::allow(char type, int major, int minor, const std::string& perm)
+{
+    setValue("allow", type + std::string(" ") + devString(major) + ":" + devString(minor) + " " + perm);
+}
+
+void DevicesCGroup::deny(char type, int major, int minor, const std::string& perm)
+{
+    setValue("deny", type + std::string(" ") + devString(major) + ":" + devString(minor) + " " + perm);
+}
+
+std::vector<DevicePermission> DevicesCGroup::list()
+{
+    std::vector<DevicePermission> list;
+    DevicePermission p;
+    for (const auto& ln : utils::split(getValue("list"), "\n")) {
+        if (!ln.empty()) {
+            list.push_back(parsePerms(p, ln));
+        }
+    }
+    return list;
+}
+
+} //namespace lxcpp
diff --git a/libs/lxcpp/cgroups/devices.hpp b/libs/lxcpp/cgroups/devices.hpp
new file mode 100644 (file)
index 0000000..83d2fe2
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ *  Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License version 2.1 as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * @file
+ * @author  Krzysztof Dynowski (k.dynowski@samsumg.com)
+ * @brief   Control-groups management, devices
+ */
+
+#ifndef LXCPP_CGROUPS_DEVICES_HPP
+#define LXCPP_CGROUPS_DEVICES_HPP
+
+#include "lxcpp/cgroups/cgroup.hpp"
+
+namespace lxcpp {
+
+struct DevicePermission {
+    char type;              // 'a'==any, 'b'==block, 'c'==character
+    int major,minor;        // -1==any
+    std::string permission; // combination of "rwm" (r==read,w==write,m==create)
+};
+
+class DevicesCGroup : public CGroup {
+public:
+    DevicesCGroup(const std::string& name) :
+        CGroup("devices", name)
+    {
+    }
+
+    void allow(DevicePermission p);
+    void deny(DevicePermission p);
+    void allow(char type, int major, int minor, const std::string& perm);
+    void deny(char type, int major, int minor, const std::string& perm);
+    std::vector<DevicePermission> list();
+};
+
+} //namespace lxcpp
+
+#endif //LXCPP_CGROUPS_DEVICE_HPP
index dc18683..beb51f9 100644 (file)
@@ -45,7 +45,6 @@ Subsystem::Subsystem(const std::string& name) : mName(name)
         throw CGroupException(msg);
     }
 
-    std::vector<std::string> av = availableSubsystems();
     //find mount point for a name
     std::ifstream fileStream("/proc/mounts");
     if (!fileStream.good()) {
index 510aba3..bc5be16 100644 (file)
@@ -25,7 +25,9 @@
 #include "ut.hpp"
 #include "logger/logger.hpp"
 
-#include "lxcpp/cgroups/subsystem.hpp"
+#include "lxcpp/cgroups/devices.hpp"
+#include "lxcpp/exception.hpp"
+#include "utils/text.hpp"
 
 namespace {
 
@@ -45,10 +47,10 @@ BOOST_AUTO_TEST_CASE(GetAvailable)
 {
     std::vector<std::string> subs;
     BOOST_CHECK_NO_THROW(subs = Subsystem::availableSubsystems());
-    BOOST_CHECK(subs.size() > 0);
+    BOOST_CHECK_MESSAGE(subs.size() > 0, "Control groups not supported");
     for (auto n : subs){
         Subsystem s(n);
-        LOGD(s.getName() << ": " << (s.isAttached()?s.getMountPoint():"[not attached]"));
+        LOGD(s.getName() << ": " << (s.isAttached() ? s.getMountPoint() : "[not attached]"));
     }
 }
 
@@ -59,24 +61,66 @@ BOOST_AUTO_TEST_CASE(GetCGroupsByPid)
     BOOST_CHECK(cg.size() > 0);
 }
 
+BOOST_AUTO_TEST_CASE(GetPidsByCGroup)
+{
+    CGroup cg = CGroup::getCGroup("memory", ::getpid());
+    std::vector<pid_t> pids;
+    BOOST_CHECK_NO_THROW(pids = cg.getPids());
+    BOOST_CHECK(pids.size() > 0);
+}
+
 BOOST_AUTO_TEST_CASE(SubsysAttach)
 {
     Subsystem sub("freezer");
-    BOOST_CHECK_MESSAGE(sub.getName() == "freezer", "freezer not equal");
     BOOST_CHECK_MESSAGE(sub.isAvailable(), "freezer not found");
 
     if (!sub.isAvailable()) return ;
 
-
     if (sub.isAttached()) {
         std::string mp = sub.getMountPoint();
         BOOST_CHECK_NO_THROW(Subsystem::detach(mp));
+        BOOST_CHECK(Subsystem(sub.getName()).isAttached()==false);
         BOOST_CHECK_NO_THROW(Subsystem::attach(mp, {sub.getName()}));
+        BOOST_CHECK(Subsystem(sub.getName()).isAttached()==true);
     }
     else {
         std::string mp = "/sys/fs/cgroup/" + sub.getName();
         BOOST_CHECK_NO_THROW(Subsystem::attach(mp, {sub.getName()}));
+        BOOST_CHECK(Subsystem(sub.getName()).isAttached()==true);
         BOOST_CHECK_NO_THROW(Subsystem::detach(mp));
+        BOOST_CHECK(Subsystem(sub.getName()).isAttached()==false);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(ControlGroupParams)
+{
+    CGroup memg("memory:/ut-params");
+    BOOST_CHECK(memg.exists() == false);
+    BOOST_CHECK_NO_THROW(memg.create());
+    BOOST_CHECK(memg.exists() == true);
+
+    if (!memg.exists()) return ;
+
+    memg.assignPid(::getpid());
+    memg.setValue("limit_in_bytes", "10k");
+    memg.setValue("soft_limit_in_bytes", "10k");
+    BOOST_CHECK_THROW(memg.setValue("non-existing-name", "xxx"), CGroupException);
+
+    LOGD("limit_in_bytes: " << memg.getValue("limit_in_bytes"));
+    LOGD("soft_limit_in_bytes: " << memg.getValue("soft_limit_in_bytes"));
+    LOGD("max_usage_in_bytes: " << memg.getValue("max_usage_in_bytes"));
+
+    CGroup("memory:/").assignPid(::getpid());
+    BOOST_CHECK_NO_THROW(memg.destroy());
+}
+
+BOOST_AUTO_TEST_CASE(DevicesParams)
+{
+    DevicesCGroup devcg("/");
+    std::vector<DevicePermission> list = devcg.list();
+    for (const auto& i : list) {
+        LOGD(std::string("perm = ") + i.type + " " +
+             std::to_string(i.major) + ":" + std::to_string(i.minor) + " " + i.permission);
     }
 }