Commit 7ba5d23f authored by Swann Perarnau's avatar Swann Perarnau

Argo Containers V1

Judicael's code, unchanged, extracted from the master branch of pre-ECP
NodeOS repository.

This is not a filter-branch, just a cp -r. We are not losing anything
important in the git history, as it was only 3 commits long, without any
details on the history of the code.
parents
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
1- Back-to-back invocation of argo_nodeos_config for container manipulation
could fail. This is due to the cgroup filesystem (sysfs in general) taking
some time to do its processing. It is recommended to use sync or put some
sleep between the invocations.
2- While altering a container, argo_nodeos_config is not particularly forgiving
of "mindless" user inputs. Here are a few examples:
-If one tries to add to a container a resource that it already has, the
operation will fail (with an explicit error message). The same happens for a
resource being removed. This should be changed to exhibit an idempotent
behavior instead (with respect to the particular hardware resource being
added or removed).
Basically, if a container has the cpus cores {0,1,4,5}; an "alter_container"
command that tries to add {0,1,8,9} will fail because of the attempt to
"re-add" {0,1}. Ideally, the operation should actually succeed and lead the
container to now possess {0,1,4,5,8,9}.
3- alter_service_os is partially implemented (and is not working yet). Instead,
the user can delete and recreate (in the meantime).
4- json files as input was planned from the beginning; but is not implemented
(didn't see this as a high priority feature).
This diff is collapsed.
A-Daemon mode (--daemon, needs a client)
-Beacause the startup might be a bit heavy; the deamon mode is recommended
when the config is expected to keep changing. The daemon is expected to run
on the serviceOS
B- Each new supported resource controller leads to the implementation of two
interfaces:
-IResource_controller: A container aggregates some of this
-IResource_controller_state: the container manager aggregates these and
keeps acocunting and all
1-)Build with make
Be root while building (or make sure that the final binary is owned by root)
2-)Execute ./argo_nodeos_config --help="misc help_sections"; read the message and
start aving fun. The full help can be displayed by simply executing
./argo_nodeos_config --help
-Update process hosting in cgroups with cpuset.procs instead of tasks
-Update so the argo_reserved_mems knob could be manipulated from user-space
-Add support for mask-based mem_exclusive instead of the current ALL or NOTHING situation
create_container
delete_container
alter_container
attach //attach a process or a set of processes
detach //detach a process or a set of processes
#include "acl.hpp"
#include "utils.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstdlib>
Acl::Acl(string config_file, uid_t uid):
_config_file(config_file)
{
if(uid == INVALID_UID)
_uid = ruid;
else
_uid = uid;
check_config_file();
_syslog_disable_info = DEFAULT_ACL_CONTROL;
_syslog_disable_warning = DEFAULT_ACL_CONTROL;
_syslog_disable_warning = DEFAULT_ACL_CONTROL;
_syslog_enable_debug = DEFAULT_ACL_CONTROL;
}
Acl::~Acl()
{
}
void Acl::check_config_file()
{
struct stat stat_buf;
if(stat(_config_file.c_str(), &stat_buf) == -1)
return;
if(stat_buf.st_uid != 0)
argo_exit(EXIT_FAILURE, "The ACL config file must be owned by root");
if((stat_buf.st_mode&S_IWGRP || stat_buf.st_mode& S_IWOTH))
argo_exit(EXIT_FAILURE,
"The ACL config file must be writable only for root. " +
string(APP_NAME) + " cannot use it! ");
}
bool Acl::is_allowed(Acl_token token)
{
if(_uid == 0)
return true; //always allowed for root
map<Acl_token, bool>::const_iterator it =
_single_value_privileges.find(token);
if(it == _single_value_privileges.end())
return DEFAULT_ACL_CONTROL;
return it->second;
}
#ifndef __ACL_H__
#define __ACL_H__
#include "utils.hpp"
#include "defaults.hpp"
#include <map>
using std::map;
#define INVALID_UID 0xffffffff
#ifndef DEFAULT_ACL_CONTROL
#define DEFAULT_ACL_CONTROL true; //for tests
#endif
enum Acl_token
{
ACL_DUMMY,
ACL_ENABLE_SYSLOG_DEBUG,
ACL_DISABLE_SYSLOG_INFO,
ACL_DISABLE_SYSLOG_WARNING,
ACL_DISABLE_SYSLOG_ERROR,
ACL_REMOVE_PROCESS, //from a container
ACL_ADD_PROCESS, //to a container
};
class Acl
{
private:
string _config_file;
uid_t _uid;
map<Acl_token, bool> _single_value_privileges;
bool _syslog_enable_debug;
bool _syslog_disable_info;
bool _syslog_disable_warning;
bool _syslog_disable_error;
//These below are not meant to be used
Acl(const Acl& orig){}
Acl& operator = (const Acl& orig){ return *this;}
void check_config_file();
public:
/*uid=-1 means the real uid of the process will be used*/
Acl(string config_path=DEFAULT_ACL_CONFIG, uid_t uid=INVALID_UID);
~Acl();
inline uid_t get_uid() const {return _uid;}
bool is_allowed(Acl_token token);
template <typename T>
bool is_allowed_on(Acl_token token, T& args);
};
template <typename T>
bool Acl::is_allowed_on(Acl_token token, T& args)
{
return false;
}
#endif //__ACL_H__
#include "aggregatelogger.hpp"
Aggregatelogger::Aggregatelogger(uid_t ruid):
ILogger(ruid, LOG_DEST_DEV_NULL)
{
}
Aggregatelogger::~Aggregatelogger()
{
}
void Aggregatelogger::log(Log_type type, const string& message, int errnum)
{
for(int i=0; i<(int)_loggers.size(); i++)
_loggers[i]->log(type, message, errnum);
}
void Aggregatelogger::add_logger(ILogger* logger)
{
for(int i=0; i<(int)_loggers.size(); i++)
{
if(_loggers[i]->log_destination() == logger->log_destination())
{
_loggers[i] = logger;
return;
}
}
_log_dest |= logger->log_destination();
_loggers.push_back(logger);
}
#ifndef __AGGREGATELOGGER_HPP__
#define __AGGREGATELOGGER_HPP__
#include "ilogger.hpp"
#include "syslogger.hpp"
#include "stderrlogger.hpp"
#include "filelogger.hpp"
class Aggregatelogger: public ILogger
{
Log_dest _log_dest;
vector<ILogger*> _loggers;
public:
Aggregatelogger(uid_t ruid);
~Aggregatelogger();
virtual void log(Log_type type, const string& message, int errnum = 0);
virtual inline Log_dest log_destination() const {return _log_dest;}
/*A maximum of 1 instance of each type of logger. If a type of logger
* is added that already existed, it is overwritten.
* The added loggers must be deleted outside thsi object*/
void add_logger(ILogger* logger);
inline void clear_loggers() {_loggers.clear(); _log_dest = LOG_DEST_DEV_NULL;}
};
#endif
#!/bin/bash
rm -rf /etc/argo/nodeos/
rm -rf /var/run/argo/nodeos/
rm -rf /var/lock/argo/
rm -f .argo_nodeos_config_exit_message #One of this is created in each
#folder from which argo_nodeos_config has
#been
#executed. So might need to execute the
#script (or manually delete the file)
#from each of the concerned folders
This diff is collapsed.
#ifndef __COMPUTE_CONTAINER_HPP__
#define __COMPUTE_CONTAINER_HPP__
#include "utils.hpp"
#include <string>
#include <vector>
#include <algorithm>
#include <sys/types.h>
#include "resource_type.hpp"
using std::string;
using std::vector;
#include "string_parser.hpp"
/*The type below would be an enum in real word; but need it to be a used as a
* bitmask. It can only take the values defined below though*/
typedef uint64_t Container_commit_type;
#define CCT_NONE 0
#define CCT_NAME 1LLU
#define CCT_CPUS (1LLU<<1)
#define CCT_MEMS (1LLU<<2)
#define CCT_TASKS (1LLU<<3)
#define CCT_MEM_EXCLUSIVE (1LLU<<4)
#define CCT_CPU_EXCLUSIVE (1LLU<<5)
#define CCT_LOAD_BALANCING (1LLU<<6)
#define CCT_MEM_MIGRATE (1LLU<<7)
#define CCT_ALL 0xffffffffffffffff
class Argo_container
{
private:
bool _created;
string _parent_path;
Container_commit_type _commit_type;
string _older_name; //The last committed one
string _name;
uid_t _owner;
vector<int> _cpus;
vector<int> _mems;
bool _cpu_exclusive;
bool _mem_exclusive;
bool _load_balancing;
bool _mem_migrate;
vector<int> _tasks;
vector<int> _added_tasks; //added and not committed
Argo_container(){}
Argo_container(Argo_container& original);
Argo_container& operator = (const Argo_container & orig);
public:
Argo_container(const string& parent_path, String_parser& sp);
/*This one builds a non-functional (empty) Argo_container and then
* reloads it. The state of the container is read from the cgroup fs*/
Argo_container(const string& parent_path,
const string& name, uid_t owner);
~Argo_container();
inline const string& get_name() const {return _name;}
inline const string& get_parent_path() const {return _parent_path;}
inline const uid_t get_owner() const {return _owner;}
inline void set_owner(uid_t owner) {_owner = owner;}
inline bool has_cpu(int cpu) const
{return std::find(_cpus.begin(), _cpus.end(), cpu) != _cpus.end();}
/*If no argument, checks if exclusive_cpus is enabled;
* otherwise, checks if a cpu is owned exclusively*/
inline bool has_exclusive_cpu(int cpu = -1) const
{return _cpu_exclusive && (cpu == -1 || has_cpu(cpu));}
void set_cpu_exclusive(bool status);
inline bool has_mem(int mem) const
{return std::find(_mems.begin(), _mems.end(), mem) != _mems.end();}
/*If no argument, checks if _mem_exclusive is enabled;
* otherwise, checks if a memory node is owned exclusively*/
inline bool has_exclusive_mem(int mem = -1) const
{return _mem_exclusive && (mem == -1 || has_mem(mem));}
void set_mem_exclusive(bool status);
inline bool has_load_balancing() const {return _load_balancing;}
void set_load_balancing(bool status);
void set_mem_migrate(bool status);
void add_cpu(int cpu);
void add_cpus(const vector<int>& cpus);
void replace_cpus(const vector<int>& cpus);
void remove_cpu(int cpu);
void remove_cpus(const vector<int>& cpus);
void get_cpus(vector<int>& out) const;
void add_mem(int mem);
void add_mems(const vector<int>& mems);
void replace_mems(const vector<int>& mems);
void remove_mem(int mem);
void remove_mems(const vector<int>& mems);
void get_mems(vector<int>& out) const;
template <typename T>
void get_resource(vector<T>& out, Resource_type rtype)
{
switch(rtype)
{
case RT_CPU:
get_cpus(out); break;
case RT_MEM:
get_mems(out); break;
default:
ASSERT_MSG(false, "Unsupported resource");
THROW("Unsupported resource");
}
}
void add_task(int task);
void add_tasks(const vector<int>& tasks);
void remove_task(int task);
void remove_tasks(const vector<int>& tasks);
void get_tasks(vector<int>& out) const;
int get_nb_tasks() const {return (int) _tasks.size();}
inline bool has_any_task() const {return _tasks.size() != 0;}
inline bool has_task(int task)
{return get_object_index<int>(_tasks, task) != -1;}
inline bool has_all_these_tasks(
const vector<int>& tasks){return is_superset(_tasks, tasks);}
inline bool has_any_of_these_tasks(
const vector<int>& tasks){return has_intersect(_tasks, tasks);}
void rename(const string& new_name);
void reload_task_list();
void reload(bool load_tasks = true);
void display() const;
void display_resources() const;
void commit_last_changes();
void commit_all();
void create();
void remove();
void alter(String_parser& sp);
bool check_task_ownership(bool reload=true, bool throw_on_failure=false);
/*It is recommended to invoke this method after having invoked
* check_task_ownership*/
void kill_content(bool reload=true);
};
#endif //__COMPUTE_CONTAINER_HPP__
This diff is collapsed.
#ifndef __COMMON_HPP__
#define __COMMON_HPP__
#include <sys/types.h>
#include <sys/stat.h>
#include "defaults.hpp"
#if 0
#include "acl.hpp"
#include "argo_container.hpp"
#include "container_manager.hpp"
#include "aggregatelogger.hpp"
#endif
#include <stdexcept>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <stdint.h>
#include <sys/types.h>
#include <string>
#include <vector>
using std::string;
using std::vector;
#define UID_INVALID (uid_t)(-1)
typedef uint32_t Log_dest; //Can be ored with the values below
#define LOG_DEST_DEV_NULL 0
#define LOG_DEST_SYSLOG 1
#define LOG_DEST_STDERR 2
#define LOG_DEST_FILE 4
typedef uint32_t Log_type;
#define LOG_TYPE_INVALID 0
#define LOG_TYPE_INFO 1
#define LOG_TYPE_WARNING 2
#define LOG_TYPE_ERROR 4
#define LOG_TYPE_DEBUG 8
#define LOG_TYPE_ALL 0xf
#define ADD_LOG_TYPE(type0,type_to_add)\
(LOG_TYPE_ALL & ((type0) | (type_to_add)))
#define SUBSTRACT_LOG_TYPE(type0,type_to_remove)\
(LOG_TYPE_ALL & ((type0) & (~type_to_remove)))
enum Error_behavior
{
ON_ERROR_STOP,
ON_ERROR_CONTINUE
};
enum Activation
{
ACTIVATION_UNDEFINED,
ACTIVATION_ENABLED,
ACTIVATION_DISABLED
};
struct Args
{
Error_behavior error_behavior;
};
extern Args args;
class ILogger;
extern ILogger* logger;
class Acl;
extern Acl* acl;
extern uid_t ruid;
extern uid_t euid;
#endif //__COMMON_HPP__
This diff is collapsed.
#ifndef __CONTAINER_MANAGER_HPP__
#define __CONTAINER_MANAGER_HPP__
#include "utils.hpp"
#include "argo_container.hpp"
#include <string>
#include "resource_ownership.hpp"
#include "ilogger.hpp"
#include "string_parser.hpp"
#include "resource_ownership_set.hpp"
#include <vector>
#include "resource_type.hpp"
using std::string;
using std::vector;
class Container_manager
{
bool _no_separate_service_os;
Error_behavior _error_behavior;
ILogger* _logger;
Argo_container* _root; //is named argo for all resource controllers
Argo_container* _service_os_root;
Argo_container* _argo_containers_root;
vector<Argo_container*> _containers;
Resource_ownership_set<int> _cpu_ownerships;
Resource_ownership_set<int> _mem_ownerships;
Resource_ownership_set<int> _sos_cpu_ownerships;
Resource_ownership_set<int> _sos_mem_ownerships;
string _cgroup_root;
string _argo_argo_container_root;
string _argo_service_os_cgroup;
string _argo_argo_containers_root;
Container_manager(Container_manager& orig) = delete;
Container_manager& operator = (const Container_manager& orig) = delete;
void get_cpus_breakdown(vector<int>& all_cpus,
vector<int>& service_cpus, vector<int>& compute_cpus);
void get_mems_breakdown(vector<int>& all_mems,
vector<int>& service_mems,
vector<int>& compute_mems);
void init_cgroup_fs();
Argo_container* find_by_name(const string& name);
void create_root_cgroups();
void migrate_service_os();
void load_root_cgroups();
void load_existing_containers_as_root(); //After this, everything first belong to root
void apply_ownership_map();
void grant_resources(Argo_container* cc);
void reclaim_resources(Argo_container* cc);
/*Here, exclusively means that the ownership does not exist yet; but when it
* is obtained, it is expected to be exclusively if exclusively is true
* */
template <typename T>
bool are_resources_available(Resource_ownership_set<T>& res_ownship_set,
const vector<T>& res, bool exclusively = false)
{
for(int i=0; i<(int)res.size(); i++)
{
Resource_ownership<T> *ro =
res_ownship_set.find_resource_ownership(res[i]);
THROW_ON_BAD_INPUT_IF(!ro,
"At least 1 requested hardware resource does not exist on the system");
if(!ro->can_increment_owners())
return false;
if(exclusively && !ro->can_own_exclusively())
return false;
}
return true;
};
bool is_name_available(const string& name);
bool is_legal_create_command(String_parser& sp);
/*The resource ownership manipulation methods will throw
* on invalid, inconsistent or conflicting situations*/
template<typename T>
void simulate_containers_final_resource_state(
const Resource_ownership_set<T> &ros,
const vector<T> &current, const vector<T> &the_overwrite,
const vector<T> &added, const vector<T> &removed, bool is_exclcusive,
vector<T> &simulated_final_res_state,
Resource_ownership_set<T> &simulated_final_ros_state)
{
const vector<T>* ref_content = the_overwrite.empty() ? &current :
&the_overwrite;
THROW_ON_BAD_INPUT_IF(!is_superset(*ref_content, removed),
"Trying to remove from a container resources that it does not own.");
#if 0
THROW_ON_BAD_INPUT_IF(has_intersect(*ref_content, added),
"Trying to add to a container resources that it already owns.");
THROW_ON_BAD_INPUT_IF(has_intersect(removed, added),
"Resources are simultaneously removed and added.");
#endif
simulated_final_res_state.clear();
if(!the_overwrite.empty())
{
simulated_final_ros_state.reclaim(current);
simulated_final_ros_state.grant(the_overwrite, is_exclcusive);
simulated_final_res_state.assign(the_overwrite.begin(),
the_overwrite.end());
}
else
{
simulated_final_res_state.assign(current.begin(),
current.end());
}
simulated_final_ros_state.grant(added, is_exclcusive);
simulated_final_res_state.insert(simulated_final_res_state.end(),
added.begin(), added.end());
simulated_final_ros_state.reclaim(removed);
for(int i=0; i<(int)removed.size(); i++)
{
typename vector<T>::iterator it;
it = std::find(simulated_final_res_state.begin(),
simulated_final_res_state.end(), removed[i]);
if(it != simulated_final_res_state.end())
simulated_final_res_state.erase(it);
}
}
/*The resource ownership manipulation methods will throw
* on invalid, inconsistent or conflicting situations*/
template<typename T>
void simulate_sos_final_resource_state(
const Resource_ownership_set<T> &sos_ros,
const Resource_ownership_set<T> &cc_ros,
const vector<T> &current, const vector<T> &the_overwrite,
const vector<T> &added, const vector<T> &removed,
vector<T> &simulated_final_res_state,
Resource_ownership_set<T> &simulated_final_sos_ros,
Resource_ownership_set<T> &simulated_final_cc_ros)
{
const vector<T>* ref_content = the_overwrite.empty() ? &current :
&the_overwrite;
THROW_ON_BAD_INPUT_IF(!is_superset(*ref_content, removed),
"Trying to remove from the service_os resources that it does not own.");
THROW_ON_BAD_INPUT_IF(!has_intersect(*ref_content, added),
"Trying to add to rthe service_os resources that it already owns.");
THROW_ON_BAD_INPUT_IF(!has_intersect(removed, added),
"Resources are simultaneously removed and added.");
simulated_final_res_state.clear();
if(!the_overwrite.empty())
{
simulated_final_sos_ros.transfer_to(current, simulated_final_cc_ros);
simulated_final_cc_ros.transfer_to(the_overwrite, simulated_final_sos_ros);
simulated_final_res_state.assign(the_overwrite.begin(),
the_overwrite.end());
}
else
simulated_final_res_state.assign(current.begin(),
current.end());
simulated_final_sos_ros.transfer_to(removed, simulated_final_cc_ros);
simulated_final_cc_ros.transfer_to(added, simulated_final_sos_ros);
for(int i=0; i<(int)removed.size(); i++)
{
typename vector<T>::iterator it;
it = std::find(simulated_final_res_state.begin(),
simulated_final_res_state.end(), removed[i]);
if(it != simulated_final_res_state.end())
simulated_final_res_state.erase(it);
}
simulated_final_res_state.insert(simulated_final_res_state.end(),
added.begin(), added.end());
}
/*Check if the alteration wouldn't violate resource ownership rules.
* Is used for alter command and assumes an existing container whose name