├── debian ├── compat ├── dirs ├── source │ └── format ├── python-portopy.install ├── python3-portopy.install ├── yandex-porto.dirs ├── yandex-porto.docs ├── yandex-portodshim.install ├── yandex-porto.bash-completion ├── yandex-porto.maintscript ├── yandex-porto.install ├── yandex-porto.cron.d ├── yandex-portodshim.logrotate ├── yandex-porto.logrotate ├── yandex-portodshim.postinst ├── bash-completion.portod ├── yandex-porto.postinst ├── yandex-porto.upstart ├── yandex-porto.service ├── yandex-portodshim.service ├── copyright ├── rules ├── yandex-porto.init └── control ├── .gitattributes ├── layers ├── common-update.sh ├── .gitignore ├── bootstrap2_ubuntu_bionic.sh ├── bootstrap2_debian_stretch.sh ├── common-locale.sh ├── bootstrap2_ubuntu_xenial.sh ├── common-timezone.sh ├── common-dns.sh ├── common-hosts.sh ├── common-devel.sh ├── bootstrap_debian_stretch.sh ├── bootstrap_ubuntu_xenial.sh ├── bootstrap_ubuntu_bionic.sh ├── bootstrap_ubuntu_precise.sh ├── base_debian_stretch.sh ├── common-openssh.sh ├── common-debootstrap.sh ├── common-cleanup.sh ├── base_ubuntu_bionic.sh ├── bootstrap_ubuntu_xenial_upstart.sh ├── common-misc.sh ├── base_ubuntu_xenial.sh ├── base_ubuntu_xenial_uptart.sh ├── base_ubuntu_precise.sh └── Makefile ├── src ├── api │ ├── python │ │ ├── MANIFEST.in │ │ ├── porto │ │ │ ├── __init__.py │ │ │ └── exceptions.py │ │ ├── CMakeLists.txt │ │ ├── README.rst │ │ └── setup.py │ ├── cpp │ │ └── CMakeLists.txt │ └── go │ │ ├── porto │ │ ├── go.mod │ │ └── go.sum │ │ └── CMakeLists.txt ├── fmt │ ├── CMakeLists.txt │ ├── ostream.cc │ ├── LICENSE.rst │ ├── time.h │ └── ostream.h ├── util │ ├── crc32.hpp │ ├── nlohmann │ │ └── download.sh │ ├── cpp-httplib │ │ └── download.sh │ ├── locks.hpp │ ├── md5.hpp │ ├── signal.hpp │ ├── mutex.hpp │ ├── proc.hpp │ ├── CMakeLists.txt │ ├── thread.hpp │ ├── http.hpp │ ├── task.hpp │ ├── namespace.hpp │ ├── hgram.hpp │ ├── idmap.hpp │ ├── namespace.cpp │ ├── mutex.cpp │ ├── quota.hpp │ ├── signal.cpp │ ├── worker.hpp │ ├── http.cpp │ ├── error.hpp │ ├── error.cpp │ └── unix.hpp ├── version.hpp ├── main.cpp ├── kv.proto ├── config.hpp ├── portod.hpp ├── rpc.hpp ├── core.hpp ├── kvalue.hpp ├── event.hpp ├── helpers.hpp ├── stream.hpp ├── epoll.hpp ├── waiter.hpp ├── device.hpp ├── task.hpp ├── env.hpp ├── filesystem.hpp ├── storage.hpp ├── event.cpp ├── cli.hpp ├── portoinit.c ├── CMakeLists.txt └── epoll.cpp ├── scripts ├── cleanup_test ├── version ├── prepare_test └── standalone_test ├── test ├── idle_client.py ├── module │ ├── Makefile │ ├── build.sh │ └── porto_kernel.c ├── test-stats.py ├── multi-thread_app.py ├── test-legacy-root-loop.py ├── test-portoctl-wait.py ├── test-portoctl-attach.py ├── test-hijack.py ├── test-volume-restore.py ├── test-volume_sync.py ├── test-dirty-limit.py ├── test-portoctl-bind.py ├── test-portoctl-exec.py ├── test-systemd.py ├── test-wait.py ├── test-place.py ├── porto-509-repro.py ├── test-mem_limit_total.py ├── test-tc-rebuild.py ├── test-userns.py ├── test-jobs.py ├── test-io-limit.py ├── test-symlink.py ├── mem_touch.c ├── test-clear.py ├── test-retriability.py ├── test-ulimit.py ├── test-import-layer-cgroup.py ├── test-locate-process.py ├── test-docker.py ├── test-htb-restore.py ├── test-cpu_policy.py ├── test-net-ifup.py ├── test-aufs.py ├── portotest.cpp ├── test-std-streams.py ├── test-volume_queue.py ├── test-isolation.py ├── test-tc-classes.py ├── test-portod_cli.py ├── test-vcheck.py └── test-unpriv-cred.py ├── .travis.yml ├── .gitignore ├── portodshim ├── logshim │ ├── CMakeLists.txt │ └── main.go ├── config.go ├── CMakeLists.txt ├── main.go ├── registry.go ├── README.md ├── go.mod └── logger.go ├── AUTHORS ├── spec ├── build └── porto.spec.in ├── release-porto └── README.md /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | usr/sbin 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.proto text 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /layers/common-update.sh: -------------------------------------------------------------------------------- 1 | apt-get update 2 | -------------------------------------------------------------------------------- /debian/python-portopy.install: -------------------------------------------------------------------------------- 1 | usr/lib/python2* 2 | -------------------------------------------------------------------------------- /debian/python3-portopy.install: -------------------------------------------------------------------------------- 1 | usr/lib/python3* 2 | -------------------------------------------------------------------------------- /src/api/python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | -------------------------------------------------------------------------------- /debian/yandex-porto.dirs: -------------------------------------------------------------------------------- 1 | etc/portod.conf.d 2 | usr/lib/porto 3 | -------------------------------------------------------------------------------- /layers/.gitignore: -------------------------------------------------------------------------------- 1 | *.tgz 2 | *.txz 3 | put_authorized_keys.sh 4 | -------------------------------------------------------------------------------- /debian/yandex-porto.docs: -------------------------------------------------------------------------------- 1 | porto.md 2 | src/rpc.proto 3 | src/config.proto 4 | -------------------------------------------------------------------------------- /layers/bootstrap2_ubuntu_bionic.sh: -------------------------------------------------------------------------------- 1 | /debootstrap/debootstrap --second-stage 2 | -------------------------------------------------------------------------------- /layers/bootstrap2_debian_stretch.sh: -------------------------------------------------------------------------------- 1 | /debootstrap/debootstrap --second-stage 2 | -------------------------------------------------------------------------------- /src/fmt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(fmt) 2 | 3 | add_library(fmt STATIC format.cc ostream.cc) 4 | -------------------------------------------------------------------------------- /debian/yandex-portodshim.install: -------------------------------------------------------------------------------- 1 | usr/sbin/portodshim usr/sbin 2 | usr/sbin/logshim usr/sbin 3 | -------------------------------------------------------------------------------- /layers/common-locale.sh: -------------------------------------------------------------------------------- 1 | : ${LANG=en_US.UTF-8} 2 | locale-gen ${LANG} 3 | update-locale --reset LANG=${LANG} 4 | -------------------------------------------------------------------------------- /src/util/crc32.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | uint32_t Crc32(const std::string &s); 6 | -------------------------------------------------------------------------------- /src/version.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern const char PORTO_VERSION[]; 4 | extern const char PORTO_REVISION[]; 5 | -------------------------------------------------------------------------------- /debian/yandex-porto.bash-completion: -------------------------------------------------------------------------------- 1 | debian/bash-completion.portoctl portoctl 2 | debian/bash-completion.portod portod 3 | -------------------------------------------------------------------------------- /src/util/nlohmann/download.sh: -------------------------------------------------------------------------------- 1 | VERSION=v3.10.5 2 | 3 | wget https://github.com/nlohmann/json/releases/download/$VERSION/json.hpp 4 | -------------------------------------------------------------------------------- /src/api/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(libporto) 2 | add_library(porto STATIC libporto.cpp) 3 | target_link_libraries(porto rpc_proto) 4 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | int PortodMain(int argc, char **argv); 2 | 3 | int main(int argc, char **argv) { 4 | return PortodMain(argc, argv); 5 | } 6 | -------------------------------------------------------------------------------- /src/util/cpp-httplib/download.sh: -------------------------------------------------------------------------------- 1 | VERSION=v0.10.7 2 | 3 | wget https://raw.githubusercontent.com/yhirose/cpp-httplib/$VERSION/httplib.h 4 | -------------------------------------------------------------------------------- /debian/yandex-porto.maintscript: -------------------------------------------------------------------------------- 1 | rm_conffile /etc/porto/logrotate-porto.conf 4.14.0~ 2 | rm_conffile /etc/bash_completion.d/yandex-porto 4.14.0~ 3 | -------------------------------------------------------------------------------- /src/api/go/porto/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yandex/porto/src/api/go/porto 2 | 3 | go 1.18 4 | 5 | require google.golang.org/protobuf v1.28.1 6 | -------------------------------------------------------------------------------- /layers/bootstrap2_ubuntu_xenial.sh: -------------------------------------------------------------------------------- 1 | # Do not create devices in package makedev 2 | cp -a /dev/null /dev/.devfsd 3 | 4 | /debootstrap/debootstrap --second-stage 5 | -------------------------------------------------------------------------------- /scripts/cleanup_test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for u in porto-alice porto-bob porto-charlie porto-david ; do 4 | id $u 1>/dev/null 2>&1 && userdel $u 1>/dev/null 2>&1 5 | done 6 | -------------------------------------------------------------------------------- /test/idle_client.py: -------------------------------------------------------------------------------- 1 | import porto, sys 2 | 3 | l = [] 4 | for i in range(int(sys.argv[1])): 5 | c = porto.Connection(); 6 | c.connect(); 7 | l.append(c) 8 | 9 | sys.stdin.read() 10 | -------------------------------------------------------------------------------- /test/module/Makefile: -------------------------------------------------------------------------------- 1 | obj-m += porto_kernel.o 2 | 3 | all: 4 | make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules 5 | clean: 6 | make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean 7 | -------------------------------------------------------------------------------- /debian/yandex-porto.install: -------------------------------------------------------------------------------- 1 | usr/sbin/portoctl usr/sbin 2 | usr/sbin/portod usr/sbin 3 | usr/lib/porto/portoinit usr/lib/porto 4 | usr/lib/porto/portoctl-top usr/lib/porto 5 | usr/share/man/man8/porto.8 usr/share/man/man8 6 | -------------------------------------------------------------------------------- /src/kv.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package kv; 4 | 5 | message TPair { 6 | required string key = 1; 7 | required string val = 2; 8 | } 9 | 10 | message TNode { 11 | repeated TPair pairs = 1; 12 | } 13 | -------------------------------------------------------------------------------- /test/module/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SRC=$1 4 | DST=$2 5 | 6 | mkdir -p $DST 7 | 8 | cp $SRC/Makefile $DST/Makefile 9 | cp $SRC/porto_kernel.c $DST/porto_kernel.c 10 | 11 | cd $DST 12 | 13 | make clean 14 | 15 | make 16 | -------------------------------------------------------------------------------- /src/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "common.hpp" 6 | 7 | #include "config.pb.h" 8 | 9 | extern cfg::TConfig &config(); 10 | void ReadConfigs(bool silent = false); 11 | TError ValidateConfig(); 12 | -------------------------------------------------------------------------------- /debian/yandex-porto.cron.d: -------------------------------------------------------------------------------- 1 | 13 * * * * root sleep $(($(hexdump -n 2 -e '/2 "\%u"' /dev/urandom)\%300)) && test -x /usr/sbin/logrotate && flock -n /run/logrotate-porto.lock -c '/usr/sbin/logrotate /etc/logrotate.d/yandex-porto 1>/dev/null 2>&1' 2 | -------------------------------------------------------------------------------- /scripts/version: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | FMT='const char PORTO_VERSION[] = "%s";\nconst char PORTO_REVISION[] = "%s";\n' 3 | VER=`sed -nE 's#.*\((.*)\).*#\1#p;q' debian/changelog` 4 | REV=`git describe --tags --dirty=-dirty` 5 | printf "$FMT" "$VER" "$REV" 6 | -------------------------------------------------------------------------------- /scripts/prepare_test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for u in porto-alice porto-bob; do 4 | id $u 1>/dev/null 2>&1 || useradd $u -G porto 5 | done 6 | 7 | id porto-charlie 1>/dev/null 2>&1 || useradd porto-charlie 8 | id porto-david 1>/dev/null 2>&1 || useradd porto-david 9 | -------------------------------------------------------------------------------- /debian/yandex-portodshim.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/portodshim/portodshim.log { 2 | size 100M 3 | missingok 4 | rotate 3 5 | compress 6 | notifempty 7 | nocreate 8 | su root syslog 9 | 10 | postrotate 11 | /usr/bin/killall -HUP portodshim 12 | endscript 13 | 14 | } 15 | -------------------------------------------------------------------------------- /layers/common-timezone.sh: -------------------------------------------------------------------------------- 1 | : ${TIMEZONE=Europe/Moscow} 2 | 3 | echo "${TIMEZONE}" > /etc/timezone 4 | 5 | # https://bugs.launchpad.net/ubuntu/+source/tzdata/+bug/1554806 6 | ln -fs /usr/share/zoneinfo/"${TIMEZONE}" /etc/localtime 7 | 8 | dpkg-reconfigure -f noninteractive tzdata 9 | -------------------------------------------------------------------------------- /layers/common-dns.sh: -------------------------------------------------------------------------------- 1 | # recolv_conf binds etc/resolv.conf 2 | rm -f etc/resolv.conf || exit 0 3 | 4 | # https://dns.yandex.ru 5 | tee etc/resolv.conf < 4 | 5 | typedef std::unique_lock TScopedLock; 6 | 7 | class TLockable { 8 | public: 9 | TScopedLock ScopedLock() { 10 | return TScopedLock(Mutex); 11 | } 12 | private: 13 | std::mutex Mutex; 14 | }; 15 | -------------------------------------------------------------------------------- /src/util/md5.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util/path.hpp" 4 | 5 | TError Md5Sum(TFile &file, std::string &sum); 6 | void Md5Sum(const std::string &value, std::string &sum); 7 | void Md5Sum(const std::string &salt, const std::string &value, std::string &sum); 8 | 9 | std::string GenerateSalt(); 10 | -------------------------------------------------------------------------------- /test/test-stats.py: -------------------------------------------------------------------------------- 1 | import porto 2 | from test_common import * 3 | 4 | c = porto.Connection(timeout=10) 5 | root_stats = c.GetProperty("/", "porto_stat").split(';') 6 | 7 | print "Porto stats:" 8 | 9 | for s in root_stats: 10 | pair = s.split(':') 11 | print "{} : {}".format(pair[0], pair[1]) 12 | 13 | -------------------------------------------------------------------------------- /layers/common-devel.sh: -------------------------------------------------------------------------------- 1 | apt-get install -y build-essential cmake libc6-dbg strace gdb gdbserver 2 | 3 | apt-get install -y libncurses5-dev 4 | 5 | apt-get install -y libprotobuf-dev protobuf-compiler 6 | 7 | apt-get install -y python-pip 8 | 9 | pip install --index-url=https://pypi.python.org/simple protobuf 10 | -------------------------------------------------------------------------------- /layers/bootstrap_debian_stretch.sh: -------------------------------------------------------------------------------- 1 | debootstrap --foreign --variant=minbase --include systemd-sysv --arch amd64 stretch . http://mirror.yandex.ru/debian/ 2 | 3 | # Do not mount/umount anything 4 | tee -a debootstrap/functions < 5 | #include 6 | } 7 | 8 | void Crash(); 9 | void FatalSignal(int sig); 10 | void CatchFatalSignals(); 11 | void ResetBlockedSignals(); 12 | void ResetIgnoredSignals(); 13 | 14 | void Signal(int signum, void (*handler)(int)); 15 | int SignalFd(); 16 | -------------------------------------------------------------------------------- /layers/bootstrap_ubuntu_xenial.sh: -------------------------------------------------------------------------------- 1 | debootstrap --foreign --variant=minbase --arch amd64 xenial . http://mirror.yandex.ru/ubuntu 2 | 3 | # Do not mknod/mount/umount anything 4 | tee -a debootstrap/functions < EpollLoop; 7 | extern std::unique_ptr EventQueue; 8 | 9 | extern std::string PreviousVersion; 10 | extern bool PortodFrozen; 11 | extern bool ShutdownPortod; 12 | 13 | void ReopenMasterLog(); 14 | void CheckPortoSocket(); 15 | -------------------------------------------------------------------------------- /test/multi-thread_app.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | def spawn_threads(): 5 | class DummyThread(threading.Thread): 6 | def run(self): 7 | time.sleep(300) 8 | 9 | for i in range(10): 10 | th = DummyThread() 11 | th.daemon = True 12 | th.start() 13 | 14 | spawn_threads() 15 | time.sleep(30) 16 | -------------------------------------------------------------------------------- /layers/bootstrap_ubuntu_bionic.sh: -------------------------------------------------------------------------------- 1 | debootstrap --foreign --variant=minbase --include systemd-sysv,tzdata,locales --arch amd64 bionic . http://mirror.yandex.ru/ubuntu 2 | 3 | # Do not mknod/mount/umount anything 4 | tee -a debootstrap/functions < debootstrap/devices.tar.gz 5 | 6 | # Do not mount/umount anything 7 | tee -a debootstrap/functions < /dev/null || groupadd --gid 1333 porto 9 | if [ -x "/bin/systemctl" ] ; then 10 | /bin/systemctl enable yandex-porto.service || true 11 | /bin/systemctl reload-or-restart yandex-porto.service || true 12 | else 13 | invoke-rc.d yandex-porto reload || true 14 | fi 15 | ;; 16 | esac 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /layers/common-debootstrap.sh: -------------------------------------------------------------------------------- 1 | export DEBIAN_FRONTEND="noninteractive" 2 | apt-get --yes install 'debootstrap' '^(ubuntu-archive-keyring|ubuntu-keyring)$' 'debian-archive-keyring' 'debian-keyring' 3 | 4 | [ -e usr/share/debootstrap/scripts/wily ] || ln -s gutsy usr/share/debootstrap/scripts/wily 5 | [ -e usr/share/debootstrap/scripts/xenial ] || ln -s gutsy usr/share/debootstrap/scripts/xenial 6 | 7 | # Ignore nodev for root 8 | sed -e 's/check_sane_mount/true/' -i usr/sbin/debootstrap 9 | -------------------------------------------------------------------------------- /layers/common-cleanup.sh: -------------------------------------------------------------------------------- 1 | apt-get clean 2 | find var/cache/apt/archives -iname '*.deb' -delete 3 | find var/lib/apt/lists -type f -delete 4 | find var/cache/apt -iname '*.bin' -delete 5 | 6 | find tmp -mindepth 1 -delete 7 | 8 | rm -f etc/hostname 9 | : > etc/hostname 10 | 11 | ln -sf ../proc/self/mounts etc/mtab 12 | 13 | find var/log -iname '*.gz' -delete 14 | find var/log -iname '*.[0-9]' -delete 15 | find var/log -type f -print0 | xargs -0 -t tee < /dev/null 16 | 17 | : > /root/.bash_history 18 | -------------------------------------------------------------------------------- /src/util/mutex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util/path.hpp" 4 | 5 | #include 6 | 7 | class MeasuredMutex: public std::mutex { 8 | const std::string Name; 9 | 10 | public: 11 | MeasuredMutex(const std::string &name); 12 | 13 | void lock(); 14 | std::unique_lock UniqueLock(); 15 | }; 16 | 17 | 18 | class TFileMutex { 19 | TFile File; 20 | 21 | public: 22 | TFileMutex() = delete; 23 | TFileMutex(const TPath &path, int flags = 0); 24 | ~TFileMutex(); 25 | }; 26 | -------------------------------------------------------------------------------- /layers/base_ubuntu_bionic.sh: -------------------------------------------------------------------------------- 1 | umask 0022 2 | 3 | tee etc/apt/sources.list < debootstrap/devices.tar.gz 8 | 9 | # Do not mount/umount anything 10 | tee -a debootstrap/functions < 7 | 2014 Stanislav Fomichev 8 | 2014 Konstantin Khlebnikov 9 | 2014 Evgeniy Kilimchuk 10 | 2014 Michael Mayorov 11 | 2014 Stanislav Ivanichkin 12 | 2014 Vsevolod Minkov 13 | 2014 Vsevolod Velichko 14 | License: LGPL-3.0 15 | -------------------------------------------------------------------------------- /portodshim/logshim/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(logshim) 2 | cmake_minimum_required(VERSION 3.0) 3 | find_library(golang REQUIRED) 4 | 5 | set(GOPATH ${CMAKE_CURRENT_BINARY_DIR}) 6 | set(SRCS main.go) 7 | set(TARGET logshim) 8 | 9 | add_custom_command( 10 | OUTPUT ${TARGET} 11 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 12 | DEPENDS ${SRCS} 13 | COMMAND env GOPATH=${GOPATH} GOCACHE=/tmp go build -modcacherw -buildmode=exe -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}" 14 | ) 15 | 16 | add_custom_target(logshim ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}) 17 | 18 | install( 19 | FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET} 20 | DESTINATION sbin 21 | ) 22 | -------------------------------------------------------------------------------- /portodshim/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | // sockets 5 | PortodshimSocket = "/run/portodshim.sock" 6 | PortoSocket = "/run/portod.socket" 7 | // common dirs 8 | LogsDir = "/var/log/portodshim" 9 | ImagesDir = "/place/porto_docker" 10 | VolumesDir = "/place/portodshim_volumes" 11 | // cni dirs 12 | NetworkPluginConfDir = "/etc/cni/net.d" 13 | NetworkPluginBinDir = "/opt/cni/bin" 14 | NetnsDir = "/var/run/netns" 15 | // paths 16 | PortodshimLogPath = LogsDir + "/portodshim.log" 17 | // k8s 18 | KubeResourceDomain = "yandex.net" 19 | // streaming 20 | StreamingServerAddress = "[::]" 21 | StreamingServerPort = "7255" 22 | ) 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following authors have created the source code of "Porto" 2 | published and distributed by YANDEX LLC as the owner: 3 | 4 | Roman Gushchin 5 | Stanislav Fomichev 6 | Konstantin Khlebnikov 7 | Evgeniy Kilimchuk 8 | Michael Mayorov 9 | Stanislav Ivanichkin 10 | Vsevolod Minkov 11 | Vsevolod Velichko 12 | Maxim Samoylov 13 | Dmitry Yakunin 14 | Alexander Kuznetsov 15 | Lev Pantyukhin 16 | -------------------------------------------------------------------------------- /spec/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | set -e 5 | 6 | # sudo dnf install @development-tools fedora-packager rpmdevtools 7 | # rpmdev-setuptree 8 | 9 | rootdir=$(cd $(dirname $0)/../ && pwd) 10 | spec=~/rpmbuild/SPECS/porto.spec 11 | 12 | version=$(head -n1 $rootdir/debian/changelog | sed -e 's/[^(]*(\([^)]*\).*/\1/') 13 | version=$(echo $version | cut -d '-' -f 1) 14 | cp $rootdir/spec/porto.spec.in $spec 15 | sed -i -e "s/@VERSION@/$version/" $spec 16 | 17 | # create source tar.gz from git repository 18 | pushd $rootdir 19 | git archive --format=tar --prefix=porto/ HEAD | gzip >~/rpmbuild/SOURCES/yandex-porto-${version}.tar.gz 20 | popd 21 | # build package 22 | (cd ~/rpmbuild/SPECS/ && rpmbuild -vv -ba porto.spec) 23 | -------------------------------------------------------------------------------- /src/rpc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hpp" 4 | 5 | class TClient; 6 | 7 | class TRequest { 8 | public: 9 | std::shared_ptr Client; 10 | rpc::TContainerRequest Req; 11 | 12 | uint64_t QueueTime; 13 | uint64_t StartTime; 14 | uint64_t FinishTime; 15 | 16 | bool RoReq; 17 | bool IoReq; 18 | bool VlReq; 19 | bool SecretReq; 20 | 21 | std::string Cmd; 22 | std::string Arg; 23 | std::string Opt; 24 | 25 | void Classify(); 26 | void Parse(); 27 | TError Check(); 28 | void Handle(); 29 | void ChangeId(); 30 | }; 31 | 32 | void StartRpcQueue(); 33 | void StopRpcQueue(); 34 | void QueueRpcRequest(std::unique_ptr &req); 35 | uint64_t RpcRequestsTopRunningTime(); 36 | -------------------------------------------------------------------------------- /src/api/go/porto/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 5 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 6 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 7 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 8 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 9 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DESTDIR=$(CURDIR)/debian/yandex-porto 4 | SBINDIR=$(DESTDIR)/usr/sbin 5 | BUILD_TYPE?=Release 6 | OPENSSL_TGZ_URL?=https://github.com/openssl/openssl/archive/refs/tags/OpenSSL_1_1_1o.tar.gz 7 | export DH_VERBOSE=1 8 | 9 | override_dh_auto_configure: 10 | dh_auto_configure -- -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DOPENSSL_TGZ_URL=$(OPENSSL_TGZ_URL) 11 | 12 | override_dh_installinit: 13 | dh_installinit --no-restart-on-upgrade 14 | 15 | override_dh_shlibdeps: 16 | dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info 17 | 18 | override_dh_install: 19 | dh_install 20 | dh_bash-completion 21 | $(SBINDIR)/portoctl --help 22 | 23 | override_dh_auto_test: 24 | 25 | %: 26 | dh $@ --with python2,python3 --without python-support --parallel 27 | -------------------------------------------------------------------------------- /src/util/thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "unix.hpp" 8 | 9 | extern std::mutex TidsMutex; 10 | extern std::unordered_set PortoTids; 11 | 12 | template 13 | void ThreadWrapper(F&& f, Args&&... args) { 14 | pid_t tid = GetTid(); 15 | 16 | std::unique_lock lock(TidsMutex); 17 | PortoTids.insert(tid); 18 | lock.unlock(); 19 | 20 | std::ref(f)(std::forward(args)...); 21 | } 22 | 23 | // Attention, Args are taken by values 24 | template 25 | std::thread *NewThread(F&& f, Args... args) { 26 | return new std::thread(&ThreadWrapper, std::forward(f), std::forward(args)...); 27 | } 28 | -------------------------------------------------------------------------------- /test/test-portoctl-wait.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from test_common import * 3 | 4 | AsAlice() 5 | 6 | assert subprocess.call([portoctl, 'run', 'test-a', 'command=true']) == 0 7 | 8 | assert subprocess.check_output([portoctl, 'wait', 'test-a']) == 'test-a\n' 9 | 10 | assert subprocess.check_output([portoctl, 'wait', 'test-*']) == 'test-a\n' 11 | 12 | assert subprocess.call([portoctl, 'destroy', 'test-a']) == 0 13 | 14 | # test async wait with target state 15 | assert subprocess.call([portoctl, 'run', 'test-a', 'command=sleep 1']) == 0 16 | res = subprocess.check_output([portoctl, 'wait', '-A', '-S', 'dead']) 17 | assert len(res.strip().split('\n')) == 1 18 | assert res.count('dead\ttest-a\texit_code = 0') == 1 19 | 20 | assert subprocess.call([portoctl, 'destroy', 'test-a']) == 0 21 | -------------------------------------------------------------------------------- /test/test-portoctl-attach.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from test_common import * 3 | 4 | output = subprocess.check_output([portoctl, 'exec', 'test', 5 | 'command=bash -c "' + portoctl + ' create self/test; ' + 6 | portoctl + ' set self/test isolate false; ' + 7 | portoctl + ' start self/test; bash -c \\"' + 8 | portoctl + ' get self absolute_name; ' + 9 | portoctl + ' attach self/test \\\$\\\$; ' + 10 | portoctl + ' get self absolute_name\\""'], 11 | stdin=subprocess.PIPE, stderr=subprocess.PIPE) 12 | assert output == '/porto/test\n/porto/test/test\n', "unexpected output: " + output 13 | -------------------------------------------------------------------------------- /src/util/http.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "error.hpp" 6 | 7 | struct THttpClient { 8 | THttpClient(const std::string &host); 9 | ~THttpClient(); 10 | 11 | using THeader = std::pair; 12 | using THeaders = std::vector; 13 | 14 | struct TRequest { 15 | std::string Body; 16 | const char *ContentType; 17 | }; 18 | 19 | TError MakeRequest(const std::string &path, std::string &response, const THeaders &headers = {}, const TRequest *request = nullptr) const; 20 | static TError SingleRequest(const std::string &url, std::string &response, const THeaders &headers = {}, const TRequest *request = nullptr); 21 | 22 | private: 23 | struct TImpl; 24 | std::unique_ptr Impl; 25 | }; 26 | -------------------------------------------------------------------------------- /portodshim/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(portodshim) 2 | cmake_minimum_required(VERSION 3.0) 3 | find_library(golang REQUIRED) 4 | 5 | set(GOPATH ${CMAKE_CURRENT_BINARY_DIR}) 6 | set(SRCS config.go cri_api.go go.mod go.sum image_mapper.go main.go runtime_mapper.go server.go registry.go streaming.go) 7 | set(TARGET portodshim) 8 | 9 | add_custom_command( 10 | OUTPUT ${TARGET} 11 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 12 | DEPENDS ${SRCS} 13 | COMMAND env GOPATH=${GOPATH} GOCACHE=/tmp go build -modcacherw -buildmode=exe -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}" 14 | ) 15 | 16 | add_custom_target(cri_plugin ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}) 17 | 18 | install( 19 | FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET} 20 | DESTINATION sbin 21 | ) 22 | 23 | add_subdirectory(logshim) 24 | -------------------------------------------------------------------------------- /src/api/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(python-portopy) 2 | 3 | add_custom_command( 4 | OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/porto/rpc_pb2.py 5 | DEPENDS ${porto_SOURCE_DIR}/rpc.proto 6 | COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --python_out=${CMAKE_CURRENT_SOURCE_DIR}/porto/ --proto_path=${porto_SOURCE_DIR} ${porto_SOURCE_DIR}/rpc.proto 7 | VERBATIM) 8 | 9 | add_custom_target(python_rpc_proto ALL DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/porto/rpc_pb2.py) 10 | 11 | install(CODE "execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${PYTHON_EXECUTABLE} -uB setup.py install --root=\$ENV{DESTDIR} --prefix=\${CMAKE_INSTALL_PREFIX})") 12 | install(CODE "execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND python3 -uB setup.py install --root=\$ENV{DESTDIR} --prefix=\${CMAKE_INSTALL_PREFIX})") 13 | -------------------------------------------------------------------------------- /test/test-hijack.py: -------------------------------------------------------------------------------- 1 | import os 2 | import porto 3 | import datetime 4 | from test_common import * 5 | 6 | def Hijack(iterations=1): 7 | c = porto.Connection() 8 | a = c.Run('test', command='sleep 86400', isolate='true', enable_porto='isolate') 9 | for i in range(1, iterations + 1): 10 | print('{} hijack iteration {}/{}'.format(datetime.datetime.now(), i, iterations)) 11 | b = c.Run('test/hijack', command=os.getcwd() + '/hijack', isolate='false', stdout_path='/dev/fd/1', stderr_path='/dev/fd/2') 12 | b.Wait() 13 | assert b.GetProperty('exit_code') == "1" 14 | b.Destroy() 15 | a.Destroy() 16 | 17 | ConfigurePortod('test-hijack', """ 18 | container { 19 | ptrace_on_start: true 20 | } 21 | """ 22 | ) 23 | 24 | Hijack() 25 | 26 | ConfigurePortod('test-hijack', "") 27 | -------------------------------------------------------------------------------- /src/util/task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "error.hpp" 6 | 7 | struct TTask { 8 | pid_t Pid = 0; 9 | int Status = 0; 10 | bool Running = false; 11 | 12 | TError Fork(bool detach = false); 13 | TError Wait(bool interruptible = false, 14 | const std::atomic_bool &stop = false, 15 | const std::atomic_bool &disconnected = false); 16 | static bool Deliver(pid_t pid, int code, int status); 17 | 18 | bool Exists() const; 19 | bool IsZombie() const; 20 | pid_t GetPPid() const; 21 | TError Kill(int signal) const; 22 | TError KillPg(int signal) const; 23 | }; 24 | 25 | void LocalTime(const time_t *time, struct tm &tm); 26 | void TaintPostFork(std::string message); 27 | TError TranslatePid(pid_t pid, pid_t pidns, pid_t &result); 28 | -------------------------------------------------------------------------------- /src/util/namespace.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "common.hpp" 7 | #include "util/unix.hpp" 8 | #include "util/path.hpp" 9 | 10 | class TNamespaceFd : public TNonCopyable { 11 | int Fd; 12 | public: 13 | TNamespaceFd() : Fd(-1) {} 14 | ~TNamespaceFd() { Close(); } 15 | TNamespaceFd& operator=(TNamespaceFd&& other) { 16 | Close(); 17 | Fd = other.Fd; 18 | other.Fd = -1; 19 | return *this; 20 | } 21 | TError Open(TPath path); 22 | TError Open(pid_t pid, std::string type); 23 | int GetFd() const { return Fd; } 24 | void Close(); 25 | TError SetNs(int type = 0) const; 26 | TError Chroot() const; 27 | TError Chdir() const; 28 | ino_t Inode() const; 29 | static ino_t PidInode(pid_t pid, std::string type); 30 | }; 31 | -------------------------------------------------------------------------------- /layers/base_ubuntu_xenial_uptart.sh: -------------------------------------------------------------------------------- 1 | # Do not create devices in package makedev 2 | cp -a /dev/null /dev/.devfsd 3 | 4 | /debootstrap/debootstrap --second-stage 5 | 6 | umask 0022 7 | 8 | tee etc/apt/sources.list <>> from porto import Connection 7 | >>> rpc = Connection() 8 | >>> rpc.connect() 9 | 10 | To create container *test*:: 11 | 12 | >>> container = rpc.Create('test') 13 | 14 | List all properties:: 15 | 16 | >>> print rpc.Plist() 17 | 18 | Get *command* property of container *test*:: 19 | 20 | >>> print rpc.GetProperty('test', 'command') 21 | >>> print container.GetProperty('command') # the same 22 | 23 | Get all properties of container:: 24 | 25 | >>> print container.GetProperties() 26 | 27 | List containers:: 28 | 29 | >>> print rpc.List() 30 | 31 | Destroy container *test*:: 32 | 33 | >>> rpc.Destroy('test') 34 | 35 | Close connection:: 36 | 37 | >>> rpc.disconnect() 38 | -------------------------------------------------------------------------------- /layers/base_ubuntu_precise.sh: -------------------------------------------------------------------------------- 1 | /debootstrap/debootstrap --second-stage 2 | 3 | umask 0022 4 | 5 | tee etc/apt/sources.list < 4 | #include 5 | #include 6 | 7 | #include "util/error.hpp" 8 | 9 | class THistogram { 10 | const std::vector Buckets; 11 | std::vector> Values; 12 | 13 | public: 14 | THistogram(const std::vector &buckets) 15 | : Buckets(buckets) 16 | , Values(buckets.size()) 17 | {} 18 | 19 | inline int GetBucket(unsigned value) const { 20 | return (std::upper_bound(Buckets.begin(), Buckets.end(), value) - Buckets.begin()) - 1; 21 | } 22 | 23 | void Add(unsigned value) { 24 | int pos = GetBucket(value); 25 | if (pos != -1) 26 | ++Values[pos]; 27 | } 28 | 29 | std::string Format() const { 30 | std::stringstream ss; 31 | 32 | for (size_t i = 0; i < Buckets.size(); ++i) 33 | ss << (i ? ";" : "") << Buckets[i] << ":" << Values[i]; 34 | 35 | return ss.str(); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/core.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hpp" 4 | #include "util/path.hpp" 5 | #include "libporto.hpp" 6 | 7 | struct TCore { 8 | pid_t Pid; 9 | pid_t Tid; 10 | pid_t Vpid; 11 | pid_t Vtid; 12 | int Signal; 13 | int Dumpable; 14 | uint64_t Ulimit; 15 | TPath DefaultPattern; 16 | TPath Pattern; 17 | 18 | std::string ProcessName; 19 | std::string ThreadName; 20 | 21 | TPath ExePath; 22 | std::string RootPath; 23 | std::string ExeName; 24 | 25 | std::string Container; 26 | std::string CoreCommand; 27 | std::string User; 28 | std::string Group; 29 | std::string Cwd; 30 | std::string OwnerUser; 31 | std::string OwnerGroup; 32 | std::string State; 33 | 34 | uid_t OwnerUid = -1; 35 | gid_t OwnerGid = -1; 36 | std::string Prefix; 37 | std::string Slot; 38 | 39 | Porto::Connection Conn; 40 | 41 | static TError Register(const TPath &portod); 42 | static TError Unregister(); 43 | 44 | TError Handle(const TTuple &args); 45 | TError Identify(); 46 | TError Forward(); 47 | TError Save(); 48 | }; 49 | -------------------------------------------------------------------------------- /test/module/porto_kernel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | MODULE_LICENSE("GPL"); 9 | MODULE_AUTHOR("Maxim Samoylov"); 10 | MODULE_DESCRIPTION("porto_kernel"); 11 | MODULE_VERSION("1.0"); 12 | 13 | static struct task_struct *d_thread; 14 | 15 | static int d_thread_pid = 0; 16 | 17 | int threadfn(void *data) { 18 | while (!kthread_should_stop()) { 19 | set_current_state(TASK_UNINTERRUPTIBLE); 20 | schedule(); 21 | } 22 | 23 | return 0; 24 | } 25 | 26 | static int __init porto_kernel_init(void) { 27 | d_thread = kthread_run(threadfn, NULL, "porto_kernel"); 28 | d_thread_pid = d_thread->pid; 29 | return 0; 30 | } 31 | 32 | static void __exit porto_kernel_exit(void) { 33 | kthread_stop(d_thread); 34 | wake_up_process(d_thread); 35 | } 36 | 37 | module_param(d_thread_pid, int, 0444); 38 | module_init(porto_kernel_init); 39 | module_exit(porto_kernel_exit); 40 | -------------------------------------------------------------------------------- /test/test-portoctl-bind.py: -------------------------------------------------------------------------------- 1 | from test_common import * 2 | import porto 3 | import subprocess 4 | 5 | conn = porto.Connection(timeout=30) 6 | w = conn.Create("w", weak=True) 7 | v = conn.CreateVolume(layers=["ubuntu-xenial"], containers='w') 8 | 9 | def test(enable_porto, command, stdout, **kwargs): 10 | exit_code = 0 11 | if enable_porto == 'false': 12 | exit_code = 2 13 | 14 | a = conn.Run('a', wait=1, root=v.path, command=command, 15 | enable_porto=enable_porto, weak=True, **kwargs) 16 | ExpectEq(a['exit_code'], str(exit_code)) 17 | ExpectEq(a['stdout'], stdout) 18 | a.Destroy() 19 | 20 | # volume is the same so we check enable_porto=false initially 21 | test('false', 'ls /usr/sbin/portoctl', '') 22 | ExpectException(test, porto.exceptions.InvalidCommand, 'false', 'portoctl --version', '') 23 | test('isolate', 'ls /usr/sbin/portoctl', '/usr/sbin/portoctl\n', root_readonly=True) 24 | test('isolate', 'ls /usr/sbin/portoctl', '/usr/sbin/portoctl\n') 25 | test('isolate', 'portoctl --version', subprocess.check_output(['portoctl', '--version']).decode("utf-8")) 26 | -------------------------------------------------------------------------------- /src/kvalue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "common.hpp" 7 | #include "util/path.hpp" 8 | 9 | class TKeyValue { 10 | public: 11 | TPath Path; 12 | int Id = 0; 13 | std::string Name; 14 | std::map Data; 15 | 16 | TKeyValue(const TPath &path) : Path(path) { } 17 | 18 | friend bool operator<(const TKeyValue &lhs, const TKeyValue &rhs) { 19 | return lhs.Name < rhs.Name; 20 | } 21 | 22 | bool Has(const std::string &key) const { 23 | return Data.count(key); 24 | } 25 | 26 | std::string Get(const std::string &key) const { 27 | auto it = Data.find(key); 28 | return it == Data.end() ? "" : it->second; 29 | } 30 | 31 | void Set(const std::string &key, const std::string &val) { 32 | Data[key] = val; 33 | } 34 | 35 | void Del(const std::string &key) { 36 | Data.erase(key); 37 | } 38 | 39 | TError Load(); 40 | TError Save(); 41 | 42 | static TError Mount(const TPath &root); 43 | static TError ListAll(const TPath &root, std::list &nodes); 44 | static void DumpAll(const TPath &root); 45 | }; 46 | -------------------------------------------------------------------------------- /src/event.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "util/worker.hpp" 7 | 8 | class TContainer; 9 | class TContainerWaiter; 10 | 11 | enum class EEventType { 12 | Exit, 13 | ChildExit, 14 | RotateLogs, 15 | Respawn, 16 | OOM, 17 | WaitTimeout, 18 | DestroyAgedContainer, 19 | DestroyWeakContainer, 20 | }; 21 | 22 | class TEventWorker; 23 | 24 | class TEvent { 25 | public: 26 | EEventType Type; 27 | std::weak_ptr Container; 28 | 29 | struct { 30 | int Pid = 0; 31 | int Status = 0; 32 | } Exit; 33 | 34 | struct { 35 | std::weak_ptr Waiter; 36 | } WaitTimeout; 37 | 38 | uint64_t DueMs = 0; 39 | 40 | TEvent(EEventType type, std::shared_ptr container = nullptr) : 41 | Type(type), Container(container) {} 42 | 43 | bool operator<(const TEvent& rhs) const; 44 | 45 | std::string GetMsg() const; 46 | }; 47 | 48 | class TEventQueue { 49 | std::shared_ptr Worker; 50 | 51 | public: 52 | TEventQueue(); 53 | void Start(); 54 | void Stop(); 55 | 56 | void Add(uint64_t timeoutMs, const TEvent &e); 57 | }; 58 | -------------------------------------------------------------------------------- /test/test-portoctl-exec.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from test_common import * 3 | 4 | cp = subprocess.run(['ldd', portoctl], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 5 | deps = cp.stdout.decode('utf-8') 6 | if len(deps) == 0: 7 | deps = cp.stderr.decode('utf-8') 8 | 9 | ExpectEq(len(list(filter(len, deps.split('\n')))), 1) 10 | ExpectEq(deps.find('libc'), -1) 11 | 12 | ExpectEq(subprocess.call([portoctl, 'exec', 'test', 'command=true'], 13 | stdout=subprocess.PIPE, 14 | stderr=subprocess.PIPE), 15 | 0) 16 | 17 | ExpectEq(subprocess.call([portoctl, 'exec', 'test', 'command=false'], 18 | stderr=subprocess.DEVNULL, 19 | stdout=subprocess.PIPE), 20 | 1) 21 | 22 | ExpectEq(subprocess.call([portoctl, 'exec', 'test'], 23 | stdin=subprocess.DEVNULL, 24 | stderr=subprocess.PIPE, 25 | stdout=subprocess.PIPE), 26 | 0) 27 | 28 | ExpectEq(subprocess.check_output([portoctl, 'exec', 'test', 'command=seq 3'], 29 | stdin=subprocess.PIPE, 30 | stderr=subprocess.PIPE), 31 | b'1\n2\n3\n') 32 | -------------------------------------------------------------------------------- /test/test-systemd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import porto 5 | import subprocess 6 | from test_common import * 7 | 8 | c = porto.Connection(timeout=10) 9 | 10 | portod_cg = GetSystemdCg(GetPortodPid()) 11 | if WithSystemd(): 12 | ExpectNe(portod_cg, "/") 13 | else: 14 | ExpectEq(portod_cg, "/") 15 | 16 | a = c.Run("a", virt_mode='os', root_volume={'layers': ["ubuntu-precise"]}, **{"controllers[systemd]": True}) 17 | a_pid = a['root_pid'] 18 | 19 | b = c.Run("b", command="sleep 1000") 20 | b_pid = b['root_pid'] 21 | 22 | a_cg = GetSystemdCg(a_pid) 23 | ExpectEq(a_cg, "/porto%a") 24 | 25 | b_cg = GetSystemdCg(b_pid) 26 | ExpectEq(b_cg, portod_cg) 27 | 28 | mnt = ParseMountinfo(a_pid) 29 | 30 | Expect("/sys/fs/cgroup" in mnt) 31 | Expect('ro' in mnt["/sys/fs/cgroup"]['flag']) 32 | 33 | Expect("/sys/fs/cgroup/systemd" in mnt) 34 | Expect('ro' in mnt["/sys/fs/cgroup/systemd"]['flag']) 35 | 36 | Expect("/sys/fs/cgroup/systemd/porto%a" in mnt) 37 | Expect('rw' in mnt["/sys/fs/cgroup/systemd/porto%a"]['flag']) 38 | 39 | if WithSystemd(): 40 | subprocess.check_call(["systemctl", "daemon-reexec"]) 41 | ExpectEq(GetSystemdCg(a_pid), a_cg) 42 | ExpectEq(GetSystemdCg(b_pid), b_cg) 43 | 44 | a.Destroy() 45 | b.Destroy() 46 | -------------------------------------------------------------------------------- /portodshim/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | 9 | "go.uber.org/zap" 10 | ) 11 | 12 | func getCurrentFuncName() string { 13 | pc, _, _, _ := runtime.Caller(1) 14 | return runtime.FuncForPC(pc).Name() 15 | } 16 | 17 | func main() { 18 | debug := flag.Bool("debug", false, "show debug logs") 19 | flag.Parse() 20 | 21 | err := os.Mkdir(LogsDir, 0755) 22 | if err != nil && !os.IsExist(err) { 23 | _, _ = fmt.Fprintf(os.Stderr, "cannot create logs dir: %v", err) 24 | return 25 | } 26 | 27 | err = CreateZapLogger(*debug) 28 | if err != nil { 29 | _, _ = fmt.Fprintf(os.Stderr, "%v", err) 30 | return 31 | } 32 | 33 | err = os.Mkdir(VolumesDir, 0755) 34 | if err != nil && !os.IsExist(err) { 35 | zap.S().Fatalf("cannot create volumes dir: %v", err) 36 | return 37 | } 38 | 39 | err = InitKnownRegistries() 40 | if err != nil { 41 | zap.S().Fatalf("cannot init known registries: %v", err) 42 | return 43 | } 44 | 45 | server, err := NewPortodshimServer(PortodshimSocket) 46 | if err != nil { 47 | zap.S().Fatalf("server init error: %v", err) 48 | return 49 | } 50 | 51 | if err := server.Serve(); err != nil { 52 | zap.S().Fatalf("serve error: %v", err) 53 | return 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/fmt/ostream.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Formatting library for C++ - std::ostream support 3 | 4 | Copyright (c) 2012 - 2016, Victor Zverovich 5 | All rights reserved. 6 | 7 | For the license information refer to format.h. 8 | */ 9 | 10 | #include "ostream.h" 11 | 12 | namespace fmt { 13 | 14 | namespace { 15 | // Write the content of w to os. 16 | void write(std::ostream &os, Writer &w) { 17 | const char *data = w.data(); 18 | typedef internal::MakeUnsigned::Type UnsignedStreamSize; 19 | UnsignedStreamSize size = w.size(); 20 | UnsignedStreamSize max_size = 21 | internal::to_unsigned((std::numeric_limits::max)()); 22 | do { 23 | UnsignedStreamSize n = size <= max_size ? size : max_size; 24 | os.write(data, static_cast(n)); 25 | data += n; 26 | size -= n; 27 | } while (size != 0); 28 | } 29 | } 30 | 31 | FMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) { 32 | MemoryWriter w; 33 | w.write(format_str, args); 34 | write(os, w); 35 | } 36 | 37 | FMT_FUNC int fprintf(std::ostream &os, CStringRef format, ArgList args) { 38 | MemoryWriter w; 39 | printf(w, format, args); 40 | write(os, w); 41 | return static_cast(w.size()); 42 | } 43 | } // namespace fmt 44 | -------------------------------------------------------------------------------- /src/helpers.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "util/path.hpp" 5 | 6 | TError RunCommand(const std::vector &command, 7 | const TFile &dir = TFile(), 8 | const TFile &input = TFile(), 9 | const TFile &output = TFile(), 10 | const TCapabilities &caps = HelperCapabilities, 11 | bool verboseError = false, 12 | bool interruptible = false); 13 | TError RunCommand(const std::vector &command, 14 | const std::vector &env, 15 | const TFile &dir = TFile(), 16 | const TFile &input = TFile(), 17 | const TFile &output = TFile(), 18 | const TCapabilities &caps = HelperCapabilities, 19 | const std::string &memCgroup = PORTO_HELPERS_CGROUP, 20 | bool verboseError = false, 21 | bool interruptible = false); 22 | 23 | TError CopyRecursive(const TPath &src, const TPath &dst); 24 | TError ClearRecursive(const TPath &path); 25 | TError RemoveRecursive(const TPath &path, bool interruptible = false); 26 | 27 | TError DownloadFile(const std::string &url, const TPath &path, const std::vector &headers = {}); 28 | -------------------------------------------------------------------------------- /test/test-wait.py: -------------------------------------------------------------------------------- 1 | from test_common import * 2 | import porto 3 | 4 | c = porto.Connection() 5 | 6 | # stopped 7 | a = c.Create("a") 8 | ExpectEq(c.WaitContainers(["a"]), "a") 9 | a.Destroy() 10 | 11 | # dead 12 | a = c.Run("a", command="true") 13 | ExpectEq(c.WaitContainers(["a"]), "a") 14 | a.Destroy() 15 | 16 | # non-block 17 | a = c.Run("a", command="sleep 1000") 18 | ExpectEq(Catch(c.WaitContainers, ["a"], timeout=0), porto.exceptions.WaitContainerTimeout) 19 | a.Destroy() 20 | 21 | # timeout 22 | a = c.Run("a", command="sleep 1000") 23 | ExpectEq(Catch(c.WaitContainers, ["a"], timeout=0.1), porto.exceptions.WaitContainerTimeout) 24 | a.Destroy() 25 | 26 | # setup async 27 | events = [] 28 | def wait_event(name, state, when): 29 | ExpectEq((name, state), events.pop(0)) 30 | 31 | c.AsyncWait(["a"], wait_event) 32 | 33 | # full async cycle 34 | events=[('a', 'stopped'), ('a', 'starting'), ('a', 'running'), ('a', 'stopping'), ('a', 'stopped'), ('a', 'destroyed')] 35 | a = c.Run("a", command="true") 36 | a.Destroy() 37 | ExpectEq(events, []) 38 | 39 | # restore async at reconnect 40 | events=[('a', 'stopped'), ('a', 'starting'), ('a', 'running'), ('a', 'running'), ('a', 'stopping'), ('a', 'stopped'), ('a', 'destroyed')] 41 | a = c.Run("a", weak=False, command="sleep 1000") 42 | ReloadPortod() 43 | a.Destroy() 44 | ExpectEq(events, []) 45 | -------------------------------------------------------------------------------- /test/test-place.py: -------------------------------------------------------------------------------- 1 | import porto 2 | from test_common import * 3 | 4 | c = porto.Connection() 5 | 6 | a = c.Run('a', place_limit="/place: 1G") 7 | ExpectEq(a['place_usage'], "") 8 | ExpectEq(a['place_limit'], "/place: 1073741824") 9 | 10 | v = c.CreateVolume(space_limit='1G', owner_container='a') 11 | ExpectEq(a['place_usage'], "/place: 1073741824; total: 1073741824") 12 | ExpectEq(Catch(c.CreateVolume, space_limit='1G', owner_container='a'), porto.exceptions.ResourceNotAvailable) 13 | ExpectEq(Catch(c.CreateVolume, space_limit='0', owner_container='a'), porto.exceptions.ResourceNotAvailable) 14 | ExpectEq(Catch(c.CreateVolume, owner_container='a'), porto.exceptions.ResourceNotAvailable) 15 | v.Destroy() 16 | 17 | ExpectEq(a['place_usage'], "/place: 0; total: 0") 18 | ExpectEq(Catch(c.CreateVolume, owner_container='a'), porto.exceptions.ResourceNotAvailable) 19 | ExpectEq(Catch(c.CreateVolume, space_limit='0', owner_container='a'), porto.exceptions.ResourceNotAvailable) 20 | ExpectEq(Catch(c.CreateVolume, backend='plain', owner_container='a'), porto.exceptions.ResourceNotAvailable) 21 | ExpectEq(Catch(c.CreateVolume, space_limit='2G', owner_container='a'), porto.exceptions.ResourceNotAvailable) 22 | 23 | v = c.CreateVolume(space_limit='1M', owner_container='a') 24 | ExpectEq(a['place_usage'], "/place: 1048576; total: 1048576") 25 | v.Destroy() 26 | 27 | a.Destroy() 28 | -------------------------------------------------------------------------------- /test/porto-509-repro.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | import os 4 | import uuid 5 | import subprocess 6 | 7 | target = sys.argv[2] 8 | 9 | # Create new volume to deal with place policy 10 | volume = subprocess.check_output(['/portoctl', 'vcreate', '-A']).strip() 11 | random = uuid.uuid4().hex 12 | inner_place_path = os.path.join(volume, random) 13 | outer_place_path = os.path.join(volume.replace('/porto/volume_', '/place/porto_volumes/'), 'native', random) 14 | print('volume path: %s' % volume) 15 | print('inner place path: %s' % inner_place_path) 16 | print('outer place path: %s' % outer_place_path) 17 | 18 | os.makedirs(inner_place_path) 19 | 20 | if sys.argv[1] == 'layer': 21 | # Export Layer 22 | os.makedirs(os.path.join(inner_place_path, 'porto_layers')) 23 | os.symlink(target, os.path.join(inner_place_path, 'porto_layers', 'test')) 24 | subprocess.check_call(['/portoctl', 'raw', 'exportLayer { volume: "" tarball: "/layer.tar.gz" layer: "test" place: "'+ outer_place_path +'" }']) 25 | 26 | elif sys.argv[1] == 'storage': 27 | # Export Storage 28 | os.makedirs(os.path.join(inner_place_path, 'porto_storage')) 29 | os.symlink(target, os.path.join(inner_place_path, 'porto_storage', 'test')) 30 | subprocess.check_call(['/portoctl', 'raw', 'exportStorage { tarball: "/layer.tar.gz" name: "test" place: "'+ outer_place_path +'" }']) 31 | -------------------------------------------------------------------------------- /src/stream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class TContainer; 7 | class TClient; 8 | 9 | class TStdStream { 10 | public: 11 | int Stream; /* 0 - stdin, 1 - stdout, 2 - stderr */ 12 | TPath Path; 13 | bool Outside = false; 14 | uint64_t Limit = 0; 15 | uint64_t Offset = 0; 16 | struct stat PathStat; 17 | 18 | TStdStream(int stream): Stream(stream) { memset(&PathStat, 0, sizeof(PathStat)); } 19 | 20 | void SetOutside(const std::string &path) { 21 | Path = path; 22 | Outside = true; 23 | } 24 | 25 | TError SetInside(const std::string &path, const TClient &client, bool restore = false); 26 | static bool IsNull(const std::string &path); 27 | bool IsNull(void) const; 28 | static bool IsRedirect(const std::string &path); 29 | bool IsRedirect(void) const; 30 | TPath ResolveOutside(const TContainer &container) const; 31 | 32 | TError Open(const TPath &path, const TCred &cred); 33 | TError OpenOutside(const TContainer &container, const TClient &client); 34 | TError OpenInside(const TContainer &container); 35 | 36 | TError Remove(const TContainer &container); 37 | 38 | TError Rotate(const TContainer &container); 39 | TError Read(const TContainer &container, std::string &text, 40 | const std::string &range = "") const; 41 | }; 42 | -------------------------------------------------------------------------------- /src/fmt/LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 - 2016, Victor Zverovich 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /src/epoll.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "common.hpp" 7 | #include "util/locks.hpp" 8 | 9 | constexpr int EPOLL_EVENT_OOM = 1; 10 | 11 | class TContainer; 12 | class TEpollLoop; 13 | 14 | class TEpollSource : public TNonCopyable { 15 | public: 16 | int Fd; 17 | int Flags; 18 | std::weak_ptr Container; 19 | 20 | TEpollSource(int fd, int flags, std::weak_ptr container) : 21 | Fd(fd), Flags(flags), Container(container) {} 22 | TEpollSource(int fd) : Fd(fd), Flags(0), Container() {} 23 | TEpollSource() : Fd(-1), Flags(0), Container() {} 24 | }; 25 | 26 | class TEpollLoop : public TLockable, public TNonCopyable { 27 | int EpollFd = -1; 28 | 29 | size_t MaxEvents = 0; 30 | struct epoll_event *Events = nullptr; 31 | 32 | std::vector> Sources; 33 | 34 | TError ModifySourceEvents(int fd, uint32_t events) const; 35 | 36 | public: 37 | TError Create(); 38 | void Destroy(); 39 | ~TEpollLoop(); 40 | 41 | TError AddSource(std::shared_ptr source); 42 | void RemoveSource(int fd); 43 | std::shared_ptr GetSource(int fd); 44 | 45 | TError StartInput(int fd) const; 46 | TError StopInput(int fd) const; 47 | TError StartOutput(int fd) const; 48 | 49 | TError GetEvents(std::vector &evts, int timeout); 50 | }; 51 | -------------------------------------------------------------------------------- /src/waiter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "common.hpp" 7 | 8 | class TClient; 9 | class TContainer; 10 | 11 | struct TContainerReport { 12 | std::string Name; 13 | std::string State; 14 | std::string Label, Value; 15 | time_t When; 16 | 17 | TContainerReport(const std::string &name, const std::string &state, time_t when, 18 | const std::string &label, const std::string &value): 19 | Name(name), State(state), Label(label), Value(value), When(when) {} 20 | }; 21 | 22 | class TContainerWaiter : public std::enable_shared_from_this { 23 | public: 24 | std::weak_ptr Client; 25 | std::vector Names; 26 | std::vector Wildcards; 27 | std::vector Labels; 28 | std::string TargetState; 29 | bool Async; 30 | bool Active = false; 31 | 32 | TContainerWaiter(bool async) : Async(async) { } 33 | ~TContainerWaiter(); 34 | 35 | bool operator==(const TContainerWaiter &waiter) const; 36 | 37 | void Activate(std::shared_ptr &client); 38 | void Deactivate(); 39 | 40 | bool ShouldReport(TContainer &ct); 41 | bool ShouldReportLabel(const std::string &label); 42 | void Timeout(); 43 | 44 | static void ReportAll(TContainer &ct, const std::string &label = "", const std::string &value = ""); 45 | static TError Remove(const TContainerWaiter &waiter, const TClient &client); 46 | }; 47 | -------------------------------------------------------------------------------- /test/test-mem_limit_total.py: -------------------------------------------------------------------------------- 1 | import porto 2 | from test_common import * 3 | import os 4 | 5 | conn = porto.Connection() 6 | 7 | a = conn.Run("a", memory_limit="10M", anon_limit="5M") 8 | ExpectProp(a, 'memory_limit', "10485760") 9 | ExpectProp(a, 'memory_limit_total', "10485760") 10 | ExpectProp(a, 'anon_limit', "5242880") 11 | ExpectProp(a, 'anon_limit_total', "5242880") 12 | 13 | b = conn.Run("a/b") 14 | ExpectProp(b, 'memory_limit', "0") 15 | ExpectProp(b, 'memory_limit_total', "10485760") 16 | ExpectProp(b, 'anon_limit', "0") 17 | ExpectProp(b, 'anon_limit_total', "5242880") 18 | 19 | c = conn.Run("a/c", memory_limit="2M", anon_limit="1M") 20 | ExpectProp(c, 'memory_limit', "2097152") 21 | ExpectProp(c, 'memory_limit_total', "2097152") 22 | ExpectProp(c, 'anon_limit', "1048576") 23 | ExpectProp(c, 'anon_limit_total', "1048576") 24 | 25 | d = conn.Run("a/d", memory_limit="20M", anon_limit="10M") 26 | ExpectProp(d, 'memory_limit', "20971520") 27 | ExpectProp(d, 'memory_limit_total', "10485760") 28 | ExpectProp(d, 'anon_limit', "10485760") 29 | ExpectProp(d, 'anon_limit_total', "5242880") 30 | 31 | 32 | e = conn.Run("e") 33 | 34 | total = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') 35 | def_limit = max(total - (2<<30), total * 3/4) 36 | def_anon = max(def_limit - (16<<20), def_limit * 3/4) 37 | 38 | ExpectProp(e, 'memory_limit', '0') 39 | ExpectProp(e, 'memory_limit_total', str(total)) 40 | ExpectProp(e, 'anon_limit', '0') 41 | ExpectProp(e, 'anon_limit_total', str(total)) 42 | -------------------------------------------------------------------------------- /spec/porto.spec.in: -------------------------------------------------------------------------------- 1 | Name: yandex-porto 2 | Version: @VERSION@ 3 | Release: 1 4 | URL: https://github.com/yandex/porto 5 | Source: https://github.com/yandex/porto/archive/@VERSION@.tar.gz#/%{name}-%{version}.tar.gz 6 | Summary: Porto allows to run processes in containers 7 | Group: Applications/System 8 | License: Apache 9 | Requires: ncurses, logrotate, libnl3 10 | BuildRequires: protobuf, protobuf-compiler, ncurses-devel, python2-devel, gcc-c++, systemd, libnl3-devel 11 | 12 | %description 13 | Porto allows to run processes in containers 14 | 15 | %package -n python2-%{name} 16 | Summary: Python API for %{name} 17 | Group: System Environment/Libraries 18 | Requires: %{name} 19 | 20 | %description -n python2-%{name} 21 | The python2-%{name} package contains Python API for %{name}. 22 | 23 | %prep 24 | %setup -qn porto 25 | 26 | %build 27 | %cmake -DUSE_SYSTEM_LIBNL=1 -DBUILD_SHARED_LIBS=OFF . 28 | make %{?_smp_mflags} 29 | 30 | %install 31 | %define _unpackaged_files_terminate_build 0 32 | 33 | make install DESTDIR=%{buildroot} 34 | 35 | %{__install} -D debian/yandex-porto.service %{buildroot}/%{_unitdir}/%{name}.service 36 | %{__install} -D debian/yandex-porto.cron.d %{buildroot}/%{_sysconfdir}/cron.d/%{name} 37 | %{__install} -D debian/logrotate-porto.conf %{buildroot}/%{_sysconfdir}/porto/%{name} 38 | 39 | %files 40 | %defattr(755,root,root) 41 | %{_sbindir}/* 42 | %{_unitdir}/* 43 | %config(noreplace) %{_sysconfdir}/cron.d/%{name} 44 | %config(noreplace) %{_sysconfdir}/porto/%{name} 45 | 46 | %files -n python2-%{name} 47 | %defattr(755,root,root) 48 | %{python2_sitelib}/porto/* 49 | -------------------------------------------------------------------------------- /test/test-tc-rebuild.py: -------------------------------------------------------------------------------- 1 | import porto 2 | import subprocess 3 | import test_common 4 | from test_common import * 5 | import re 6 | import os 7 | 8 | # disabled because tc do not used 9 | sys.exit(0) 10 | 11 | conn = porto.Connection() 12 | 13 | expected_errors = int(conn.GetData('/', 'porto_stat[errors]')) 14 | expected_warnings = int(conn.GetData('/', 'porto_stat[warnings]')) 15 | 16 | def has_qdisc(link): 17 | return re.match('qdisc (htb|hfsc) ', subprocess.check_output(['tc', 'qdisc', 'show', 'dev', link])) 18 | 19 | def del_qdisc(link): 20 | subprocess.check_call(['tc', 'qdisc', 'del', 'root', 'dev', link]) 21 | 22 | ConfigurePortod('test-tc-rebuild', """ 23 | network { 24 | enable_host_net_classes: true, 25 | default_qdisc: "default: codel", 26 | container_qdisc: "default: fq_codel", 27 | }""") 28 | 29 | try: 30 | managed_links = [link for link in os.listdir('/sys/class/net') if has_qdisc(link)] 31 | assert managed_links 32 | 33 | for link in managed_links: 34 | del_qdisc(link) 35 | assert not has_qdisc(link) 36 | 37 | # start test container 38 | test = conn.Create('test') 39 | test.SetProperty('command', 'true') 40 | test.Start() 41 | test.Wait() 42 | assert test.GetData('exit_status') == '0' 43 | test.Destroy() 44 | 45 | # recheck qdisc 46 | for link in managed_links: 47 | assert has_qdisc(link) 48 | 49 | ExpectEq(int(conn.GetData('/', 'porto_stat[errors]')), expected_errors) 50 | ExpectEq(int(conn.GetData('/', 'porto_stat[warnings]')), expected_warnings) 51 | 52 | finally: 53 | ConfigurePortod('test-tc-rebuild', "") 54 | -------------------------------------------------------------------------------- /src/util/idmap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "common.hpp" 7 | #include "log.hpp" 8 | 9 | class TIdMap : public TNonCopyable { 10 | private: 11 | int Base; 12 | int Last = -1; 13 | std::vector Used; 14 | public: 15 | TIdMap(int base, int size) { 16 | Base = base; 17 | Resize(size); 18 | } 19 | 20 | void Resize(int size) { 21 | Used.resize(size, false); 22 | } 23 | 24 | TError GetAt(int id) { 25 | if (id < Base || id >= Base + (int)Used.size()) 26 | return TError("Id " + std::to_string(id) + " out of range"); 27 | if (Used[id - Base]) 28 | return TError("Id " + std::to_string(id) + " already used"); 29 | Used[id - Base] = true; 30 | return OK; 31 | } 32 | 33 | TError Get(int &id) { 34 | auto it = std::find(Used.begin() + Last + 1, Used.end(), false); 35 | if (Last && it == Used.end()) 36 | it = std::find(Used.begin(), Used.end(), false); 37 | if (it == Used.end()) { 38 | id = -1; 39 | return TError(EError::ResourceNotAvailable, "Cannot allocate id"); 40 | } 41 | Last = it - Used.begin(); 42 | id = Base + Last; 43 | *it = true; 44 | return OK; 45 | } 46 | 47 | TError Put(int id) { 48 | if (id < Base || id >= Base + (int)Used.size()) 49 | return TError("Id out of range"); 50 | if (!Used[id - Base]) 51 | return TError("Freeing not allocated id"); 52 | Used[id - Base] = false; 53 | return OK; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: yandex-porto 2 | Maintainer: Maxim Samoylov 3 | Build-Depends: 4 | cmake, debhelper (>= 8.0.0), pkg-config, autoconf, libtool, 5 | protobuf-compiler, libprotobuf-dev, libncurses5-dev, 6 | libnl-3-dev (>=3.2.27), libnl-route-3-dev (>=3.2.27), 7 | libnl-idiag-3-dev (>=3.2.27), 8 | g++ (>= 4:4.7) | g++-4.7, 9 | dh-python, python-all, python-setuptools, python3-setuptools, 10 | bash-completion, pandoc 11 | Standards-Version: 3.9.2 12 | X-Python-Version: >= 2.6 13 | X-Python3-Version: >= 3.2 14 | Homepage: https://github.com/yandex/porto 15 | Vcs-Git: https://github.com/yandex/porto.git 16 | 17 | Package: yandex-porto 18 | Section: utils 19 | Priority: extra 20 | Architecture: amd64 21 | Depends: ${shlibs:Depends}, ${misc:Depends}, logrotate 22 | Recommends: bash-completion 23 | Suggests: linux-image (>=3.18), tar, xz-utils, pigz, pixz, e2fsprogs, 24 | squashfs-tools (>=1:4.3), lvm2, thin-provisioning-tools, 25 | Description: Porto - linux container management system 26 | Requires Linux kernel version >= 3.18 27 | . 28 | Some features requires special kernel. 29 | 30 | Package: yandex-portodshim 31 | Section: utils 32 | Priority: extra 33 | Architecture: amd64 34 | Depends: yandex-porto 35 | Description: CRI plugin for Porto 36 | 37 | Package: python-portopy 38 | Section: python 39 | Priority: optional 40 | Architecture: all 41 | Depends: python-protobuf, ${python:Depends} 42 | Description: Python API for porto 43 | 44 | Package: python3-portopy 45 | Section: python 46 | Priority: optional 47 | Architecture: all 48 | Depends: python3-protobuf, ${python3:Depends} 49 | Description: Python 3 API for porto 50 | -------------------------------------------------------------------------------- /src/device.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "util/string.hpp" 5 | #include "util/path.hpp" 6 | #include "util/cred.hpp" 7 | 8 | class TCgroup; 9 | 10 | struct TDevice { 11 | TPath Path; 12 | TPath PathInside; 13 | 14 | dev_t Node = 0; 15 | uid_t Uid = 0; 16 | gid_t Gid = 0; 17 | mode_t Mode = S_IFCHR | 0666; 18 | 19 | bool MayRead = true; 20 | bool MayWrite = true; 21 | bool MayMknod = true; 22 | 23 | bool Wildcard = false; 24 | bool Optional = false; 25 | 26 | static TError CheckPath(const TPath &path); 27 | 28 | TDevice() {} 29 | TDevice(const TPath &path, dev_t node) : Path(path), PathInside(path), Node(node) {} 30 | 31 | TError Parse(TTuple &opt, const TCred &cred); 32 | std::string FormatAccess() const; 33 | std::string Format() const; 34 | std::string CgroupRule(bool allow) const; 35 | TError Makedev(const TPath &root = "/") const; 36 | 37 | TError Load(const rpc::TContainerDevice &dev, const TCred &cred); 38 | void Dump(rpc::TContainerDevice &dev) const; 39 | }; 40 | 41 | struct TDevices { 42 | std::vector Devices; 43 | bool NeedCgroup = false; 44 | bool AllOptional = false; 45 | 46 | TError Parse(const std::string &str, const TCred &cred); 47 | std::string Format() const; 48 | 49 | void PrepareForUserNs(const TCred &userNsCred); 50 | TError Makedev(const TPath &root = "/") const; 51 | TError Apply(const TCgroup &cg, bool rootUser, bool reset = false) const; 52 | 53 | TError InitDefault(); 54 | void Merge(const TDevices &devices, bool overwrite = false, bool replace = false); 55 | }; 56 | -------------------------------------------------------------------------------- /release-porto: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ver=`sed -nE 's#.*\((.*)\).*#\1#p;q' debian/changelog` 4 | 5 | commit() { 6 | gbp dch --new-version $(echo ${ver} | perl -pe 's/^((\d*\D+)*)(\d+)$/$1.($3+1)/e') --distribution=unstable --urgency=low --ignore-branch 7 | ver=`sed -nE 's#.*\((.*)\).*#\1#p;q' debian/changelog` 8 | git add debian/changelog 9 | git commit -s -m "release: ${ver}" 10 | git tag -f -s "v${ver}" -m "release: ${ver}" 11 | } 12 | 13 | push() { 14 | git push origin 15 | git push origin -f v${ver}:v${ver} 16 | } 17 | 18 | pypi() { 19 | cd src/api/python/ 20 | python setup.py sdist upload -r yandex 21 | } 22 | 23 | sync_upstream() { 24 | BRANCH_TO_SYNC_WITH="${BRANCH_TO_SYNC_WITH:-master}" 25 | last_commit=`git log --oneline upstream | head -n1 | cut -d' ' -f2-` 26 | git checkout $BRANCH_TO_SYNC_WITH 27 | first_commit_to_sync=`git log --oneline | grep "${last_commit}" | cut -d' ' -f1` 28 | commits_to_sync=`git log --pretty=format:"%h" ${BRANCH_TO_SYNC_WITH} ${first_commit_to_sync}..` 29 | commits_to_sync=`echo "$commits_to_sync" | awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }'` 30 | git checkout upstream 31 | git cherry-pick $commits_to_sync 32 | git push 33 | #git checkout github 34 | #git pull 35 | #git rebase upstream 36 | #git push upstream github:master 37 | #git checkout master 38 | } 39 | 40 | case "$1" in 41 | all) 42 | commit 43 | push 44 | ;; 45 | commit) 46 | commit 47 | ;; 48 | push) 49 | push 50 | ;; 51 | pypi) 52 | pypi 53 | ;; 54 | sync_upstream) 55 | sync_upstream 56 | ;; 57 | "") 58 | echo "Usage: $0 all|commit|push|pypi|sync_upstream" 59 | ;; 60 | esac 61 | -------------------------------------------------------------------------------- /src/task.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "util/namespace.hpp" 7 | #include "util/path.hpp" 8 | #include "util/cred.hpp" 9 | #include "util/unix.hpp" 10 | #include "cgroup.hpp" 11 | #include "env.hpp" 12 | #include "filesystem.hpp" 13 | 14 | class TContainer; 15 | class TClient; 16 | 17 | struct TTaskEnv { 18 | std::shared_ptr CT; 19 | TClient *Client; 20 | TFile PortoInit; 21 | TMountNamespace Mnt; 22 | 23 | TNamespaceFd IpcFd; 24 | TNamespaceFd UtsFd; 25 | TNamespaceFd NetFd; 26 | TNamespaceFd PidFd; 27 | TNamespaceFd MntFd; 28 | TNamespaceFd RootFd; 29 | TNamespaceFd CwdFd; 30 | TNamespaceFd CgFd; 31 | TNamespaceFd UserFd; 32 | 33 | TEnv Env; 34 | bool TripleFork; 35 | bool QuadroFork; 36 | std::vector Autoconf; 37 | bool NewMountNs; 38 | std::vector Cgroups; 39 | TCred Cred; 40 | uid_t LoginUid; 41 | 42 | TUnixSocket Sock, MasterSock; 43 | TUnixSocket Sock2, MasterSock2; 44 | int ReportStage = 0; 45 | 46 | TError OpenNamespaces(TContainer &ct); 47 | 48 | TError Start(); 49 | void StartChild(); 50 | 51 | TError ConfigureChild(); 52 | TError WriteResolvConf(); 53 | TError SetHostname(); 54 | TError ApplySysctl(); 55 | 56 | TError WaitAutoconf(); 57 | TError ChildExec(); 58 | 59 | void TracerLoop(pid_t traceePid); 60 | 61 | void ReportPid(pid_t pid); 62 | void Abort(const TError &error); 63 | 64 | void ExecPortoinit(pid_t pid); 65 | }; 66 | 67 | extern std::list IpcSysctls; 68 | void InitIpcSysctl(); 69 | 70 | extern unsigned ProcBaseDirs; 71 | void InitProcBaseDirs(); 72 | -------------------------------------------------------------------------------- /src/api/python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import re 3 | import os 4 | import sys 5 | 6 | def version(): 7 | if os.path.exists("PKG-INFO"): 8 | with open("PKG-INFO") as f: 9 | return re.search(r'\nVersion: (\S+)\n', f.read()).group(1) 10 | if os.path.exists("../../../debian/changelog"): 11 | with open("../../../debian/changelog") as f: 12 | return re.search(r'.*\((.*)\).*', f.readline()).group(1) 13 | return "0.0.0" 14 | 15 | def readme(): 16 | with open('README.rst') as f: 17 | return f.read() 18 | 19 | if __name__ == '__main__': 20 | 21 | if not os.path.exists("porto/rpc.proto"): 22 | with open("../../rpc.proto", 'r') as src, open("porto/rpc.proto", 'w') as dst: 23 | dst.write(src.read()) 24 | 25 | try: 26 | import subprocess 27 | subprocess.check_call(['protoc', '--python_out=porto', '--proto_path=porto', 'porto/rpc.proto']) 28 | except Exception as e: 29 | sys.stderr.write("Cannot compile rpc.proto: {}\n".format(e)) 30 | 31 | if not os.path.exists("porto/rpc_pb2.py"): 32 | sys.stderr.write("Compiled rpc.proto not found\n") 33 | sys.exit(-1) 34 | 35 | setup(name='portopy', 36 | version=version(), 37 | description='Python API for porto', 38 | long_description=readme(), 39 | url='https://github.com/yandex/porto', 40 | author_email='max7255@yandex-team.ru', 41 | maintainer_email='max7255@yandex-team.ru', 42 | license='GNU LGPL v3 License', 43 | packages=['porto'], 44 | package_data={'porto': ['rpc.proto']}, 45 | install_requires=['protobuf'], 46 | zip_safe=False) 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Porto 2 | ===== 3 | 4 | [![Build Status](https://travis-ci.org/yandex/porto.svg?branch=master)](https://travis-ci.org/yandex/porto) 5 | 6 | # OVERVIEW # 7 | 8 | Porto is a yet another Linux container management system, developed by Yandex. 9 | 10 | The main goal is providing single entry point for several Linux subsystems 11 | such as cgroups, namespaces, mounts, networking, etc. 12 | Porto is intended to be a base for large infrastructure projects. 13 | 14 | ## Key Features 15 | * **Nested containers** - containers could be put into containers 16 | * **Nested virtualizaion** - containers could use porto service too 17 | * **Flexible configuration** - all container parameters are optional 18 | * **Reliable service** - porto upgrades without restarting containers 19 | 20 | Container management software build on top of porto could be transparently 21 | enclosed inside porto container. 22 | 23 | Porto provides a protobuf interface via an unix socket /run/portod.socket. 24 | 25 | Command line tool **portoctl** and C++, Python and Go APIs are included. 26 | 27 | Porto requires Linux kernel 3.18 and optionally some offstream patches. 28 | 29 | # BUILDING # 30 | 31 | ``` 32 | $ dpkg-buildpackage -b 33 | $ sudo dpkg -i ../yandex-porto_*.deb 34 | ``` 35 | or 36 | ``` 37 | $ sudo apt-get install g++ cmake protobuf-compiler libprotobuf-dev libnl-3-dev libnl-route-3-dev libnl-idiag-3-dev libncurses5-dev pandoc 38 | $ cmake . 39 | $ make 40 | $ make install DESTDIR=/usr/local 41 | ``` 42 | 43 | # RUNNING # 44 | 45 | ``` 46 | $ sudo groupadd porto 47 | $ sudo sudo adduser $USER porto 48 | $ sudo portod start 49 | $ portoctl exec hello command='echo "Hello, world!"' 50 | ``` 51 | 52 | # DOCUMENTATION # 53 | * [Porto manpage](porto.md) 54 | -------------------------------------------------------------------------------- /src/fmt/time.h: -------------------------------------------------------------------------------- 1 | /* 2 | Formatting library for C++ - time formatting 3 | 4 | Copyright (c) 2012 - 2016, Victor Zverovich 5 | All rights reserved. 6 | 7 | For the license information refer to format.h. 8 | */ 9 | 10 | #ifndef FMT_TIME_H_ 11 | #define FMT_TIME_H_ 12 | 13 | #include "format.h" 14 | #include 15 | 16 | namespace fmt { 17 | template 18 | void format(BasicFormatter &f, 19 | const char *&format_str, const std::tm &tm) { 20 | if (*format_str == ':') 21 | ++format_str; 22 | const char *end = format_str; 23 | while (*end && *end != '}') 24 | ++end; 25 | if (*end != '}') 26 | FMT_THROW(FormatError("missing '}' in format string")); 27 | internal::MemoryBuffer format; 28 | format.append(format_str, end + 1); 29 | format[format.size() - 1] = '\0'; 30 | Buffer &buffer = f.writer().buffer(); 31 | std::size_t start = buffer.size(); 32 | for (;;) { 33 | std::size_t size = buffer.capacity() - start; 34 | std::size_t count = std::strftime(&buffer[start], size, &format[0], &tm); 35 | if (count != 0) { 36 | buffer.resize(start + count); 37 | break; 38 | } 39 | if (size >= format.size() * 256) { 40 | // If the buffer is 256 times larger than the format string, assume 41 | // that `strftime` gives an empty result. There doesn't seem to be a 42 | // better way to distinguish the two cases: 43 | // https://github.com/fmtlib/fmt/issues/367 44 | break; 45 | } 46 | const std::size_t MIN_GROWTH = 10; 47 | buffer.reserve(buffer.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); 48 | } 49 | format_str = end + 1; 50 | } 51 | } 52 | 53 | #endif // FMT_TIME_H_ 54 | -------------------------------------------------------------------------------- /src/env.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "util/error.hpp" 6 | 7 | struct TEnv { 8 | struct TEnvVar { 9 | std::string Name; 10 | std::string Value; 11 | bool Set; 12 | bool Locked; 13 | bool Overwritten; 14 | bool Secret; 15 | std::string Data; 16 | }; 17 | std::vector Vars; 18 | std::vector Environ; 19 | 20 | void ClearEnv(); 21 | TError GetEnv(const std::string &name, std::string &value) const; 22 | TError SetEnv(const std::string &name, const std::string &value, 23 | bool overwrite = true, bool lock = false, 24 | bool secret = false); 25 | TError UnsetEnv(const std::string &name, bool overwrite = true); 26 | char **Envp(); 27 | 28 | TError Parse(const std::string &cfg, bool overwrite, bool secret = false); 29 | void Format(std::string &cfg, bool show_secret = false) const; 30 | TError Apply() const; 31 | }; 32 | 33 | struct TUlimitResource { 34 | int Type; 35 | uint64_t Soft; 36 | uint64_t Hard; 37 | bool Overwritten; 38 | 39 | TError Parse(const std::string &str); 40 | std::string Format() const; 41 | }; 42 | 43 | struct TUlimit { 44 | std::vector Resources; 45 | 46 | static int GetType(const std::string &name); 47 | static std::string GetName(int type); 48 | TError Parse(const std::string &str); 49 | std::string Format() const; 50 | TError Load(pid_t pid = 0); 51 | TError Apply(pid_t pid = 0) const; 52 | void Clear() { Resources.clear(); } 53 | void Set(int type, uint64_t soft, uint64_t hard, bool overwrite = true); 54 | void Merge(const TUlimit &ulimit, bool owerwrite = true); 55 | }; 56 | -------------------------------------------------------------------------------- /portodshim/registry.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | type RegistryInfo struct { 9 | Host string 10 | AuthToken string 11 | AuthPath string 12 | AuthService string 13 | } 14 | 15 | const ( 16 | DefaultDockerRegistry = "registry-1.docker.io" 17 | ) 18 | 19 | var ( 20 | KnownRegistries = map[string]RegistryInfo{ 21 | DefaultDockerRegistry: RegistryInfo{ 22 | Host: DefaultDockerRegistry, 23 | }, 24 | "registry.yandex.net": RegistryInfo{ 25 | Host: "registry.yandex.net", 26 | AuthToken: "file:/var/run/portodshim/auth_tokens/registry.yandex.net", 27 | }, 28 | "quay.io": RegistryInfo{ 29 | Host: "quay.io", 30 | AuthPath: "https://quay.io/v2/auth", 31 | }, 32 | } 33 | ) 34 | 35 | func InitKnownRegistries() error { 36 | for host, registry := range KnownRegistries { 37 | if strings.HasPrefix(registry.AuthToken, "file:") { 38 | authTokenPath := registry.AuthToken[5:] 39 | // if file doesn't exist then auth token is empty 40 | _, err := os.Stat(authTokenPath) 41 | if err != nil { 42 | if os.IsNotExist(err) { 43 | registry.AuthToken = "" 44 | } else { 45 | return err 46 | } 47 | } else { 48 | content, err := os.ReadFile(authTokenPath) 49 | if err != nil { 50 | return err 51 | } 52 | registry.AuthToken = strings.TrimSpace(string(content)) 53 | } 54 | KnownRegistries[host] = registry 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | func GetImageRegistry(name string) RegistryInfo { 61 | host := DefaultDockerRegistry 62 | 63 | slashPos := strings.Index(name, "/") 64 | if slashPos > -1 { 65 | host = name[:slashPos] 66 | } 67 | 68 | if registry, ok := KnownRegistries[host]; ok { 69 | return registry 70 | } 71 | 72 | return RegistryInfo{} 73 | } 74 | -------------------------------------------------------------------------------- /test/test-userns.py: -------------------------------------------------------------------------------- 1 | import porto 2 | from test_common import * 3 | 4 | conn = porto.Connection() 5 | 6 | v = conn.CreateVolume(layers=['ubuntu-xenial']) 7 | 8 | def CheckUserNs(userns=True, **kwargs): 9 | virt_mode = kwargs.get('virt_mode', 'app') 10 | os_mode = virt_mode == 'os' 11 | devices = '/dev/fuse rw' if virt_mode != 'job' else '' 12 | 13 | a = conn.Run('a', userns=userns, user='1044', wait=0, net='L3 veth', devices=devices) 14 | b = conn.Run('a/b', user='1044', root=v.path) 15 | 16 | # check NET_ADMIN 17 | c = conn.Run('a/b/c', wait=5, unshare_on_exec=userns, user='1044', devices=devices, command="bash -c 'ip netns add test && ip netns list'", **kwargs) 18 | ExpectEq('0' if userns else '1', c['exit_code']) 19 | if not os_mode: 20 | ExpectEq('test' if userns else '', c['stdout'].strip()) 21 | c.Destroy() 22 | 23 | # check SYS_ADMIN 24 | c = conn.Run('a/b/c', wait=5, unshare_on_exec=userns, user='1044', devices=devices, command="bash -c 'mount -t tmpfs tmpfs /tmp && df /tmp | grep -o tmpfs'", **kwargs) 25 | ExpectEq('0' if userns else '1', c['exit_code']) 26 | if not os_mode: 27 | ExpectEq('tmpfs' if userns else '', c['stdout'].strip()) 28 | c.Destroy() 29 | 30 | # check devices ownership 31 | if devices: 32 | c = conn.Run('a/b/c', wait=5, unshare_on_exec=userns, user='1044', devices=devices, command="stat -c '%U %G' /dev/fuse", **kwargs) 33 | ExpectEq('0', c['exit_code']) 34 | if not os_mode: 35 | ExpectEq('root root', c['stdout'].strip()) 36 | c.Destroy() 37 | 38 | b.Destroy() 39 | a.Destroy() 40 | 41 | CheckUserNs(userns=False) 42 | CheckUserNs(virt_mode='app') 43 | CheckUserNs(virt_mode='job') 44 | CheckUserNs(virt_mode='os') 45 | 46 | conn.UnlinkVolume(v.path) 47 | -------------------------------------------------------------------------------- /src/util/namespace.cpp: -------------------------------------------------------------------------------- 1 | #include "namespace.hpp" 2 | #include "log.hpp" 3 | 4 | extern "C" { 5 | #include 6 | #include 7 | #include 8 | #include 9 | } 10 | 11 | TError TNamespaceFd::Open(TPath path) { 12 | Close(); 13 | Fd = open(path.c_str(), O_RDONLY | O_NOCTTY | O_NONBLOCK | O_CLOEXEC); 14 | if (Fd < 0) 15 | return TError::System("Cannot open " + path.ToString()); 16 | PORTO_ASSERT(Fd > 2); 17 | return OK; 18 | } 19 | 20 | TError TNamespaceFd::Open(pid_t pid, std::string type) { 21 | return Open("/proc/" + std::to_string(pid) + "/" + type); 22 | } 23 | 24 | void TNamespaceFd::Close() { 25 | if (Fd >= 0) { 26 | PORTO_ASSERT(Fd > 2); 27 | 28 | int ret = close(Fd); 29 | PORTO_ASSERT(!ret); 30 | 31 | Fd = -1; 32 | } 33 | } 34 | 35 | TError TNamespaceFd::SetNs(int type) const { 36 | if (Fd >= 0 && setns(Fd, type)) 37 | return TError::System("Cannot set namespace"); 38 | return OK; 39 | } 40 | 41 | TError TNamespaceFd::Chdir() const { 42 | if (Fd >= 0 && fchdir(Fd)) 43 | return TError::System("Cannot change cwd"); 44 | return OK; 45 | } 46 | 47 | TError TNamespaceFd::Chroot() const { 48 | if (Fd >= 0) { 49 | int ret = fchdir(Fd); 50 | if (!ret) 51 | ret = chroot("."); 52 | 53 | if (ret) 54 | return TError::System("Cannot change root"); 55 | } 56 | 57 | return OK; 58 | } 59 | 60 | ino_t TNamespaceFd::Inode() const { 61 | struct stat st; 62 | if (Fd > 0 && fstat(Fd, &st) == 0) 63 | return st.st_ino; 64 | return -1; 65 | } 66 | 67 | ino_t TNamespaceFd::PidInode(pid_t pid, std::string type) { 68 | struct stat st; 69 | if (TPath("/proc/" + std::to_string(pid) + "/" + type).StatFollow(st)) 70 | return 0; 71 | return st.st_ino; 72 | } 73 | -------------------------------------------------------------------------------- /test/test-jobs.py: -------------------------------------------------------------------------------- 1 | import porto 2 | from test_common import * 3 | 4 | c = porto.Connection(timeout=30) 5 | 6 | a = c.Run('test-a', weak=False, command='sleep 1000') 7 | ExpectEq(a['state'], 'running') 8 | 9 | ExpectEq(Catch(c.Run, 'test-a/b', virt_mode='job', memory_limit="1G"), porto.exceptions.InvalidValue) 10 | 11 | b = c.Run('test-a/b', weak=False, virt_mode='job', command='sleep 1000') 12 | ExpectEq(b['state'], 'running') 13 | 14 | ExpectEq(a['cgroups'], b['cgroups']) 15 | 16 | ReloadPortod() 17 | 18 | ExpectEq(b['state'], 'running') 19 | 20 | b.Kill(9) 21 | b.Wait() 22 | ExpectEq(b['state'], 'dead') 23 | ExpectEq(b['exit_code'], '-9') 24 | 25 | b.Destroy() 26 | 27 | ExpectEq(a['state'], 'running') 28 | 29 | a.Destroy() 30 | 31 | 32 | # host/job 33 | 34 | a = c.Run('test-a', virt_mode='host', memory_limit="1G", weak=False) 35 | ExpectEq(a['state'], 'meta') 36 | 37 | b = c.Run('test-a/b', virt_mode='job', command='sleep 1000', weak=False) 38 | ExpectEq(b['state'], 'running') 39 | 40 | ReloadPortod() 41 | 42 | ExpectEq(a['state'], 'meta') 43 | ExpectEq(b['state'], 'running') 44 | 45 | b.Destroy() 46 | 47 | b = c.Run('test-a/b', virt_mode='job', command='sleep 1000') 48 | ExpectEq(b['state'], 'running') 49 | b.Destroy() 50 | 51 | ExpectEq(a['state'], 'meta') 52 | a.Destroy() 53 | 54 | # check controllers after reload. 55 | a = c.Run('test-a', weak=False, command='sleep 1000', memory_limit='256M') 56 | 57 | b = c.Run('test-a/b', virt_mode='job', command='sleep 1000', weak=False) 58 | ExpectEq(b['state'], 'running') 59 | 60 | oom = c.Run('test-a/c', wait=0, weak=False, command="bash -c 'while true; do stress -m 1 ; done'") 61 | oom.Wait(5) 62 | time.sleep(5) 63 | 64 | ExpectEq(a['state'], 'dead') 65 | 66 | ExpectEq(len(b['controllers']), 0) 67 | 68 | ReloadPortod() 69 | 70 | ExpectEq(len(b['controllers']), 0) 71 | ExpectEq(a['state'], 'dead') 72 | 73 | a.Destroy() 74 | -------------------------------------------------------------------------------- /src/util/mutex.cpp: -------------------------------------------------------------------------------- 1 | #include "mutex.hpp" 2 | #include "unix.hpp" 3 | #include "log.hpp" 4 | 5 | #include 6 | 7 | class MutexTimer { 8 | const std::string &Name; 9 | uint64_t StartTime; 10 | 11 | public: 12 | MutexTimer(const std::string &name) : Name(name) { 13 | Statistics->LockOperationsCount++; 14 | StartTime = GetCurrentTimeMs(); 15 | } 16 | 17 | ~MutexTimer() { 18 | uint64_t requestTime = GetCurrentTimeMs() - StartTime; 19 | if (requestTime > 1000) { 20 | L("Long lock {} operation time={} ms", Name, requestTime); 21 | Statistics->LockOperationsLonger1s++; 22 | } 23 | if (requestTime > 3000) 24 | Statistics->LockOperationsLonger3s++; 25 | if (requestTime > 30000) 26 | Statistics->LockOperationsLonger30s++; 27 | if (requestTime > 300000) 28 | Statistics->LockOperationsLonger5m++; 29 | } 30 | }; 31 | 32 | 33 | MeasuredMutex::MeasuredMutex(const std::string &name) : Name(name) {} 34 | 35 | void MeasuredMutex::lock() { 36 | MutexTimer timer(Name); 37 | std::mutex::lock(); 38 | } 39 | 40 | std::unique_lock MeasuredMutex::UniqueLock() { 41 | MutexTimer timer(Name); 42 | return std::unique_lock(*this); 43 | } 44 | 45 | 46 | TFileMutex::TFileMutex(const TPath &path, int flags) { 47 | TError error = File.Open(path, flags); 48 | if (!error) { 49 | int ret = flock(File.Fd, LOCK_EX); 50 | if (ret) 51 | L_WRN("cannot flock lock {} {}", File.RealPath(), ret); 52 | } else { 53 | L_WRN("cannot open {} {}", path, error); 54 | } 55 | } 56 | 57 | TFileMutex::~TFileMutex() { 58 | if (!File) 59 | return; 60 | 61 | int ret = flock(File.Fd, LOCK_UN); 62 | if (ret) 63 | L_WRN("cannot flock unlock {} {}", File.RealPath(), ret); 64 | } 65 | -------------------------------------------------------------------------------- /scripts/standalone_test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # set -x 4 | 5 | PORTO_BIN=$(pwd) 6 | PORTO_SRC=$(dirname $(dirname $0)) 7 | 8 | die() { 9 | echo FAIL: $@ 10 | exit 2 11 | } 12 | 13 | stopper() { 14 | if [ -n "$PORTOD_PID" ] ; then 15 | kill -INT $PORTOD_PID 16 | wait $PORTOD_PID 17 | unset PORTOD_PID 18 | fi 19 | exit 0 20 | } 21 | 22 | starter() { 23 | trap stopper TERM INT QUIT EXIT 24 | while test -x $PORTO_BIN/portod ; do 25 | $PORTO_BIN/portod --verbose & 26 | PORTOD_PID=$! 27 | wait $PORTOD_PID 28 | echo "portod exit code:" $? 29 | done 30 | } 31 | 32 | poke_porto() { 33 | $PORTO_BIN/portoctl get / state >/dev/null 2>&1 34 | } 35 | 36 | start_porto() { 37 | starter & 38 | STARTER_PID=$! 39 | while ! poke_porto ; do 40 | kill -0 $STARTER_PID || die "cannot start porto" 41 | sleep 1 42 | done 43 | } 44 | 45 | stop_porto() { 46 | if [ -n "$STARTER_PID" ] ; then 47 | kill $STARTER_PID 48 | wait $STARTER_PID 49 | unset STARTER_PID 50 | fi 51 | poke_porto && die "cannot stop porto" 52 | } 53 | 54 | kill_porto() { 55 | if poke_porto ; then 56 | service yandex-porto stop || true 57 | killall -9 portod || true 58 | sleep 1 59 | poke_porto && die "cannot kill porto" 60 | fi 61 | } 62 | 63 | test $(id -u) = "0" || die "current user not root" 64 | 65 | trap stop_porto TERM INT QUIT EXIT 66 | kill_porto 67 | 68 | rm -f /var/log/portod.log 69 | 70 | start_porto 71 | 72 | $PORTO_BIN/portotest $@ || die "portotest failed" 73 | 74 | for pytest in $PORTO_SRC/test/test-*.py; do 75 | PYTHONPATH=$PORTO_SRC/src/api/python python $pytest || die "$pytest failed" 76 | done 77 | 78 | PYTHONPATH=$PORTO_SRC/src/api/python python -u $PORTO_SRC/test/porto_fuzzer/porto_fuzzer.py --timeout 300 --progress 32 2000 || die "fuzzer failed" 79 | -------------------------------------------------------------------------------- /src/filesystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "util/error.hpp" 7 | #include "util/path.hpp" 8 | #include "util/cred.hpp" 9 | #include "util/namespace.hpp" 10 | 11 | class TContainer; 12 | 13 | struct TBindMount { 14 | TPath Source; 15 | TPath Target; 16 | uint64_t MntFlags = 0; 17 | bool ControlSource = false; 18 | bool ControlTarget = false; 19 | 20 | TError Mount(const TCred &cred, const TPath &target_root) const; 21 | 22 | static TError Parse(const std::string &str, std::vector &binds); 23 | static std::string Format(const std::vector &binds); 24 | 25 | TError Load(const rpc::TContainerBindMount &spec); 26 | void Dump(rpc::TContainerBindMount &spec); 27 | }; 28 | 29 | struct TMountNamespace { 30 | public: 31 | std::string Container; /* for logging and errors */ 32 | TCred BindCred; 33 | TPath Cwd; 34 | TPath Root; /* path in ParentNs.Mnt */ 35 | TFile RootFd; 36 | TFile ProcFd; 37 | TFile ProcSysFd; 38 | bool RootRo; 39 | TPath HostRoot; 40 | std::vector BindMounts; 41 | std::map Symlink; 42 | bool BindPortoSock; 43 | bool IsolateRun; 44 | uint64_t RunSize; 45 | std::string Systemd; 46 | 47 | TNamespaceFd HostNs; 48 | TNamespaceFd ContainerNs; 49 | 50 | TError SetupRoot(const TContainer &ct); 51 | TError MountRun(const TContainer &ct); 52 | TError RemountRun(const TContainer &ct); 53 | TError MountBinds(); 54 | TError ProtectProc(); 55 | TError MountTraceFs(); 56 | TError MountSystemd(); 57 | TError MountCgroups(const TContainer &ct); 58 | 59 | TError Setup(const TContainer &ct); 60 | 61 | TError Enter(pid_t pid); 62 | TError Leave(); 63 | TError CreateSymlink(const TPath &symlink, const TPath &target); 64 | }; 65 | 66 | bool IsSystemPath(const TPath &path); 67 | -------------------------------------------------------------------------------- /test/test-io-limit.py: -------------------------------------------------------------------------------- 1 | from test_common import * 2 | 3 | import porto 4 | 5 | conn = porto.Connection() 6 | 7 | w = conn.Create("w", weak=True) 8 | 9 | root_volume = conn.CreateVolume(backend='overlay', layers=['ubuntu-precise'], containers='w') 10 | volume = conn.CreateVolume(backend='plain', containers='w') 11 | 12 | a = conn.Run('a', root=root_volume.path, bind='{} /bin/portoctl ro'.format(portoctl)) 13 | volume.Link(a, target='/storage') 14 | 15 | def run_ct(name, command='true', **porto_kwargs): 16 | return conn.Run(name, command=command, wait=5, enable_porto='isolate', **porto_kwargs) 17 | 18 | # /place exists only on host 19 | b = run_ct('a/b', io_limit='/place: 104857600') 20 | ExpectEq('0', b['exit_code']) 21 | b.Destroy() 22 | 23 | b = run_ct('a/b', 'portoctl exec c command="true" io_limit="/place: 104857600"') 24 | ExpectEq('0', b['exit_code']) 25 | b.Destroy() 26 | 27 | # /storage exists only in container 28 | ExpectException(run_ct, porto.exceptions.InvalidValue, 'a/b', io_limit='/storage: 104857600') 29 | 30 | b = run_ct('a/b', 'portoctl exec c command="true" io_limit="/storage: 104857600"') 31 | ExpectEq('1', b['exit_code']) 32 | ExpectEq('Cannot start container: InvalidValue:(Disk not found: /storage)\n', b['stderr']) 33 | b.Destroy() 34 | 35 | # with dot resolve occurs from container chroot 36 | ExpectException(run_ct, porto.exceptions.InvalidValue, 'a/b', io_limit='.place: 104857600') 37 | 38 | b = run_ct('a/b', 'portoctl exec c command="true" io_limit=".place: 104857600"') 39 | ExpectEq('1', b['exit_code']) 40 | ExpectEq('Cannot start container: InvalidValue:(Disk not found: .place)\n', b['stderr']) 41 | b.Destroy() 42 | 43 | b = run_ct('a/b', io_limit='.storage: 104857600') 44 | ExpectEq('0', b['exit_code']) 45 | b.Destroy() 46 | 47 | b = run_ct('a/b', 'portoctl exec c command="true" io_limit=".storage: 104857600"') 48 | ExpectEq('0', b['exit_code']) 49 | b.Destroy() 50 | 51 | volume.Unlink(a) 52 | 53 | a.Destroy() 54 | w.Destroy() 55 | -------------------------------------------------------------------------------- /test/test-symlink.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import porto 5 | from test_common import * 6 | 7 | AsAlice() 8 | 9 | c = porto.Connection(timeout=10) 10 | 11 | a = c.Run("a", symlink="tmp: /tmp; a/b: c/d") 12 | ExpectEq(a['symlink'], "a/b: c/d; tmp: /tmp; ") 13 | ExpectEq(a['symlink[tmp]'], "/tmp") 14 | ExpectEq(os.readlink(a['cwd'] + "/tmp"), "../../../tmp") 15 | ExpectEq(os.readlink(a['cwd'] + "/a/b"), "../c/d") 16 | ExpectEq(os.stat(a['cwd'] + "/a").st_uid, alice_uid) 17 | ExpectEq(os.stat(a['cwd'] + "/a").st_gid, alice_gid) 18 | ExpectEq(os.stat(a['cwd'] + "/a").st_mode & 0777, 0775) 19 | ExpectEq(os.lstat(a['cwd'] + "/a/b").st_uid, alice_uid) 20 | 21 | a["symlink[tmp]"] = "foo" 22 | ExpectEq(os.readlink(a['cwd'] + "/tmp"), "foo") 23 | ExpectEq(a['symlink'], "a/b: c/d; tmp: foo; ") 24 | 25 | a["symlink[tmp]"] = "" 26 | ExpectEq(os.path.lexists(a['cwd'] + "/tmp"), False) 27 | ExpectEq(a['symlink'], "a/b: c/d; ") 28 | 29 | a.SetSymlink("tmp", "/foo") 30 | ExpectEq(a['symlink'], "a/b: c/d; tmp: /foo; ") 31 | 32 | ExpectException(a.SetProperty, porto.exceptions.Permission, "symlink[/test]", "foo") 33 | 34 | v = c.CreateVolume(layers=["ubuntu-precise"], containers="a") 35 | a.Stop() 36 | a['symlink'] = "/foo/bar//../bar/baz: /xxx" 37 | a['symlink[a]'] = 'b' 38 | a['root'] = v.path 39 | a.Start() 40 | ExpectEq(a['symlink'], "/foo/bar/baz: /xxx; a: b; ") 41 | ExpectEq(os.readlink(a['root'] + "/a"), "b") 42 | ExpectEq(os.readlink(a['root'] + "/foo/bar/baz"), "../../xxx") 43 | 44 | a["symlink[/test/test]"] = "foo" 45 | ExpectEq(os.readlink(a['root'] + "/test/test"), "../foo") 46 | 47 | a["symlink"] = "" 48 | ExpectEq(a['symlink'], "") 49 | ExpectEq(os.path.lexists(a['root'] + "/test/test"), False) 50 | ExpectEq(os.path.lexists(a['root'] + "/a"), False) 51 | ExpectEq(os.path.lexists(a['root'] + "/foo/bar/baz"), False) 52 | 53 | a.Destroy() 54 | 55 | m = c.Run("m") 56 | a = c.Run("m/a", root_volume={"layers": ["ubuntu-precise"]}) 57 | a["symlink"] = "/a: /b" 58 | ExpectEq(os.path.lexists(a['root'] + "/a"), True) 59 | a.Destroy() 60 | m.Destroy() 61 | -------------------------------------------------------------------------------- /test/mem_touch.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | char *fname = "./test.mapped"; 13 | 14 | int main(int argc, char **argv) { 15 | unsigned long anon = 0lu, file = 0lu; 16 | unsigned char *anon_ptr = NULL; 17 | unsigned char *file_ptr = NULL; 18 | int to_wait = 0; 19 | 20 | if (argc < 3) 21 | return 1; 22 | 23 | if (sscanf(argv[1], "%lu", &anon) != 1 || 24 | sscanf(argv[2], "%lu", &file) != 1) 25 | return 1; 26 | 27 | if (argc > 3) 28 | fname = argv[3]; 29 | 30 | if (argc > 4) 31 | to_wait = !strcmp("wait", argv[4]); 32 | 33 | anon = anon % 4096 ? anon + (4096 - anon % 4096) : anon; 34 | file = file % 4096 ? file + (4096 - file % 4096) : file; 35 | 36 | if (anon) { 37 | anon_ptr = (unsigned char *)mmap(NULL, anon, PROT_READ | PROT_WRITE, 38 | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 39 | 40 | if (anon_ptr == NULL) 41 | return 1; 42 | 43 | for (unsigned long i = 0lu; i < anon / 4096; i++) 44 | anon_ptr[i * 4096 + i % 4096] = i % 256; 45 | } 46 | 47 | if (file) { 48 | int fd = open(fname, O_CREAT | O_TRUNC | O_RDWR, S_IWUSR | S_IRUSR); 49 | if (fd < 0) 50 | return 1; 51 | 52 | if (unlink(fname)) 53 | return 1; 54 | 55 | if (ftruncate(fd, file)) 56 | return 1; 57 | 58 | file_ptr = (unsigned char *)mmap(NULL, file, PROT_READ | PROT_WRITE, 59 | MAP_SHARED | MAP_LOCKED, fd, 0); 60 | if (file_ptr == NULL) 61 | return 1; 62 | 63 | if (mlock(file_ptr, file) < 0) { 64 | return errno == EAGAIN ? 2 : 1; 65 | } 66 | } 67 | 68 | if (to_wait) 69 | pause(); 70 | 71 | return (anon ? munmap(anon_ptr, anon) : 0) + (file ? munmap(file_ptr, file) : 0); 72 | } 73 | -------------------------------------------------------------------------------- /src/util/quota.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util/error.hpp" 4 | #include "util/path.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class TProjectQuota { 11 | static constexpr const char * PROJECT_QUOTA_FILE = "quota.project"; 12 | static const uint32_t PROJECT_QUOTA_MAGIC = 0xd9c03f14; 13 | 14 | TPath Device; 15 | TPath RootPath; 16 | 17 | bool RemoveUnusedProjects = false; 18 | 19 | std::unordered_set Inodes; 20 | std::unordered_map> Quotas; 21 | 22 | std::string Type; 23 | TError FindProject(); 24 | TError FindDevice(); 25 | 26 | static TError InitProjectQuotaFile(const TPath &path); 27 | static TError GetProjectId(const TPath &path, uint32_t &id); 28 | static TError SetProjectIdOne(const TPath &path, uint32_t id, bool isDir); 29 | static TError SetProjectIdAll(const TPath &path, uint32_t id); 30 | static TError InventProjectId(const TPath &path, uint32_t &id); 31 | 32 | bool SeenInode(const struct stat *st); 33 | dqblk* FindQuota(uint32_t id); 34 | dqblk* SearchQuota(uint32_t id); 35 | TError WalkQuotaFile(int fd, unsigned id, int index, int depth); 36 | TError ScanQuotaFile(const TPath "aPath); 37 | TError WalkInodes(const TPathWalk &walk); 38 | TError UpdateQuota(uint32_t id, const dqblk *quota, std::string &message); 39 | TError WalkUnlinked(); 40 | TError RecalcUsage(); 41 | 42 | public: 43 | TPath Path; 44 | 45 | uint32_t ProjectId = 0; 46 | uint64_t SpaceLimit = 0; 47 | uint64_t SpaceUsage = 0; 48 | uint64_t InodeLimit = 0; 49 | uint64_t InodeUsage = 0; 50 | 51 | TProjectQuota(const TPath &path) { Path = path; } 52 | TError Enable(); 53 | 54 | bool Exists(); 55 | 56 | TError Load(); 57 | TError Create(); 58 | TError Resize(); 59 | TError Destroy(); 60 | TError Check(std::string &message); 61 | 62 | TError StatFS(TStatFS &result); 63 | 64 | static TError Toggle(const TFile &dir, bool enabled); 65 | }; 66 | -------------------------------------------------------------------------------- /test/test-clear.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | 3 | import os 4 | import porto 5 | import tarfile 6 | import shutil 7 | import time 8 | 9 | from test_common import * 10 | 11 | AsRoot() 12 | 13 | TMPDIR="/tmp/test_clear" 14 | LAYERS="/place/porto_layers" 15 | NAME="test-layer" 16 | 17 | WIDTHS = [1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 36, 1, 1, 1, 1, 1, 4, 1, 1, 4, 4, 0] 18 | 19 | def CreateRecursive(path, k): 20 | if k >= len(WIDTHS): 21 | return 22 | 23 | width = WIDTHS[k] 24 | 25 | if width == 0: 26 | open(path + "/file.txt", "w").write(str(k)) 27 | else: 28 | for i in range(0, WIDTHS[k]): 29 | new_path = path + "/" + str(i) 30 | os.mkdir(new_path) 31 | CreateRecursive(new_path, k + 1) 32 | 33 | c = porto.Connection(timeout=10) 34 | 35 | try: 36 | os.mkdir(TMPDIR) 37 | except OSError: 38 | shutil.rmtree(TMPDIR) 39 | os.mkdir(TMPDIR) 40 | 41 | open("{}/{}".format(TMPDIR, "file.txt"), "w").write("1234567890") 42 | 43 | t = tarfile.open(name="{}/{}".format(TMPDIR, "layer.tar"), mode="w") 44 | t.add("{}/{}".format(TMPDIR, "file.txt"), arcname = "file.txt") 45 | t.close() 46 | 47 | os.unlink("{}/{}".format(TMPDIR, "file.txt")) 48 | 49 | BASE = "{}/{}".format(LAYERS, NAME) 50 | 51 | if NAME in [layer.name for layer in c.ListLayers()]: 52 | print "Removing existing layer at: {}".format(BASE) 53 | shutil.rmtree(BASE) 54 | 55 | c.ImportLayer(NAME, "{}/{}".format(TMPDIR, "layer.tar")) 56 | 57 | assert os.stat(BASE) != None 58 | assert os.stat("{}/file.txt".format(BASE)) != None 59 | 60 | print "Preparing special directory tree of depth: {} ... ".format(len(WIDTHS)) 61 | start_ts = time.time() 62 | CreateRecursive(BASE, 0) 63 | stop_ts = time.time() 64 | print "Prepare took {} s".format(stop_ts - start_ts) 65 | 66 | creation = stop_ts - start_ts 67 | 68 | print "Removing layer... " 69 | start_ts = time.time() 70 | c.RemoveLayer(NAME) 71 | stop_ts = time.time() 72 | print "Removal took {} s".format(stop_ts - start_ts) 73 | 74 | removal = stop_ts - start_ts 75 | 76 | os.unlink("{}/{}".format(TMPDIR, "layer.tar")) 77 | os.rmdir(TMPDIR) 78 | -------------------------------------------------------------------------------- /src/storage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "util/path.hpp" 5 | 6 | enum EStorageType { 7 | Place, 8 | Layer, 9 | DockerLayer, 10 | Storage, 11 | Meta, 12 | Volume, 13 | }; 14 | 15 | class TStorage { 16 | public: 17 | EStorageType Type; 18 | TPath Place; 19 | std::string Name; 20 | 21 | TPath Path; 22 | std::string Meta; 23 | std::string FirstName; 24 | 25 | TCred Owner; 26 | std::string Private; 27 | time_t LastChange = 0; 28 | uint64_t Size = 0; 29 | 30 | TError Resolve(EStorageType type, const TPath &place, const std::string &name = "", bool strict = false); 31 | void Open(EStorageType type, const TPath &place, const std::string &name = ""); 32 | 33 | static TError CheckName(const std::string &name, bool meta=false); 34 | static TError CheckPlace(const TPath &place); 35 | static TError SanitizeLayer(const TPath &layer); 36 | TError List(enum EStorageType type, std::list &list); 37 | TError ImportArchive(const TPath &archive, const std::string &cgroup, const std::string &compress = "", 38 | bool merge = false, bool verboseError = false); 39 | TError ExportArchive(const TPath &archive, const std::string &compress = ""); 40 | bool Exists() const; 41 | bool Weak() const; 42 | uint64_t LastUsage() const; 43 | TError Load(); 44 | TError RemoveAsync(); 45 | TError Remove(bool weak = false, bool async=false); 46 | TError Touch(); 47 | TError SaveOwner(const TCred &owner); 48 | TError SetPrivate(const std::string &text); 49 | TError SavePrivate(const std::string &text); 50 | TError SaveChecksums(); 51 | 52 | TError CreateMeta(uint64_t space_limit, uint64_t inode_limit); 53 | TError ResizeMeta(uint64_t space_limit, uint64_t inode_limit); 54 | TError StatMeta(TStatFS &stat); 55 | 56 | static void Init(); 57 | static void StopAsyncRemover(); 58 | static void IncPlaceLoad(const TPath &place); 59 | static void DecPlaceLoad(const TPath &place); 60 | 61 | private: 62 | static TError Cleanup(const TPath &place, EStorageType type, unsigned perms); 63 | TPath TempPath(const std::string &kind); 64 | TError CheckUsage(); 65 | }; 66 | -------------------------------------------------------------------------------- /src/util/signal.cpp: -------------------------------------------------------------------------------- 1 | #include "signal.hpp" 2 | #include "util/log.hpp" 3 | 4 | extern "C" { 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | } 11 | 12 | void Crash() { 13 | L_ERR("Crashed"); 14 | Stacktrace(); 15 | 16 | /* that's all */ 17 | Signal(SIGABRT, SIG_DFL); 18 | raise(SIGABRT); 19 | _exit(128); 20 | } 21 | 22 | void FatalSignal(int sig) { 23 | Statistics->Fatals++; 24 | 25 | /* don't hang */ 26 | alarm(5); 27 | 28 | L_ERR("Fatal signal: {}", std::string(strsignal(sig))); 29 | Stacktrace(); 30 | 31 | /* ok, die */ 32 | Signal(sig, SIG_DFL); 33 | raise(sig); 34 | _exit(128 + sig); 35 | } 36 | 37 | void CatchFatalSignals() { 38 | Signal(SIGILL, FatalSignal); 39 | Signal(SIGABRT, FatalSignal); 40 | Signal(SIGFPE, FatalSignal); 41 | Signal(SIGSEGV, FatalSignal); 42 | Signal(SIGBUS, FatalSignal); 43 | 44 | Signal(SIGPIPE, SIG_IGN); 45 | } 46 | 47 | void ResetBlockedSignals() { 48 | sigset_t sigMask; 49 | 50 | sigemptyset(&sigMask); 51 | if (sigprocmask(SIG_SETMASK, &sigMask, NULL)) { 52 | L_ERR("Cannot unblock signals"); 53 | Crash(); 54 | } 55 | } 56 | 57 | void ResetIgnoredSignals() { 58 | Signal(SIGPIPE, SIG_DFL); 59 | } 60 | 61 | void Signal(int signum, void (*handler)(int)) { 62 | struct sigaction sa; 63 | 64 | memset(&sa, 0, sizeof(sa)); 65 | sa.sa_handler = handler; 66 | if (sigaction(signum, &sa, NULL)) { 67 | L_ERR("Cannot set signal action"); 68 | Crash(); 69 | } 70 | } 71 | 72 | int SignalFd() { 73 | sigset_t sigMask; 74 | 75 | sigemptyset(&sigMask); 76 | sigaddset(&sigMask, SIGHUP); 77 | sigaddset(&sigMask, SIGINT); 78 | sigaddset(&sigMask, SIGTERM); 79 | sigaddset(&sigMask, SIGUSR1); 80 | sigaddset(&sigMask, SIGUSR2); 81 | sigaddset(&sigMask, SIGCHLD); 82 | 83 | if (sigprocmask(SIG_BLOCK, &sigMask, NULL)) { 84 | L_ERR("Cannot block signals"); 85 | Crash(); 86 | } 87 | 88 | int sigFd = signalfd(-1, &sigMask, SFD_NONBLOCK | SFD_CLOEXEC); 89 | if (sigFd < 0) { 90 | L_ERR("Cannot create signalfd"); 91 | Crash(); 92 | } 93 | 94 | return sigFd; 95 | } 96 | -------------------------------------------------------------------------------- /test/test-retriability.py: -------------------------------------------------------------------------------- 1 | import porto 2 | import random 3 | import subprocess 4 | import time 5 | 6 | from test_common import * 7 | from threading import Thread 8 | 9 | conn = porto.Connection() 10 | 11 | ConfigurePortod('test-retriability', """ 12 | daemon { 13 | portod_shutdown_timeout: 1, 14 | request_handling_delay_ms: 4000, 15 | }""") 16 | 17 | def TestCmd(command, restart_portod=False, fail=False): 18 | failed = False 19 | 20 | def RunCmd(): 21 | conn2 = porto.Connection(auto_reconnect=True) 22 | nonlocal failed 23 | try: 24 | eval('conn2.{}'.format(command)) 25 | if fail: 26 | failed = True 27 | except: 28 | if not fail: 29 | failed = True 30 | else: 31 | failed = (sys.exc_info()[0] != porto.exceptions.SocketError) 32 | 33 | cmd = Thread(target=RunCmd) 34 | cmd.start() 35 | 36 | time.sleep(1) 37 | if restart_portod: 38 | porto_cmd = random.choice(['upgrade', 'reload']) 39 | print('Make ' + porto_cmd) 40 | subprocess.call([portod, porto_cmd]) 41 | 42 | cmd.join() 43 | assert not failed 44 | 45 | 46 | TestCmd('ListVolumes()') 47 | 48 | TestCmd('ListVolumes()', True) 49 | 50 | TestCmd("Create('abc')", True) 51 | 52 | assert len(conn.ListContainers()) == 1 53 | 54 | TestCmd("Destroy('abc')", True) 55 | assert len(conn.ListContainers()) == 0 56 | 57 | TestCmd('CreateVolume()') 58 | TestCmd('CreateVolume()', True) 59 | 60 | volumes = conn.ListVolumes() 61 | assert len(volumes) == 2 62 | 63 | TestCmd("UnlinkVolume('{}')".format(volumes[0]), True) 64 | 65 | TestCmd("UnlinkVolume('{}')".format(volumes[1])) 66 | 67 | v = conn.CreateVolume() 68 | subprocess.call(['dd', 'if=/dev/urandom', 'of=' + str(v) + '/foo', 'bs=1M', 'count=1024']) 69 | 70 | subprocess.call(['rm', '-f', '/tmp/layer.tar.gz']) 71 | 72 | # export layer and upgrade portod. It must finished success 73 | TestCmd("ExportLayer('{}', place='{}', tarball='/tmp/layer.tar.gz')".format(v.path, v.place), True) 74 | 75 | # import layer and upgrade portod. Helper must be killed after portod_shutdown_timeout 76 | TestCmd("ImportLayer('layer', '/tmp/layer.tar.gz')", True, True) 77 | assert Catch(conn.FindLayer, 'layer') == porto.exceptions.LayerNotFound 78 | 79 | ConfigurePortod('test-retriability', "") 80 | -------------------------------------------------------------------------------- /portodshim/README.md: -------------------------------------------------------------------------------- 1 | # Portodshim 2 | 3 | Portodshim is a CRI ([Container Runtime Interface](https://kubernetes.io/docs/concepts/architecture/cri)) plugin 4 | for [Porto](https://github.com/yandex/porto) container management system. 5 | 6 | Portodshim allows Porto daemon to communicate with kubelet, so Porto can be used as Kubernetes container runtime. 7 | Portodshim is written on Go programming language. 8 | 9 | ![Porto and other container runtimes](./doc/images/container_runtimes.svg "Porto and other container runtimes") 10 | 11 | 12 | ## Quick start 13 | 14 | ### Dependencies 15 | 16 | Install [Porto container runtime](../README.md) and [Go programming language](https://go.dev/doc/install) (at least v1.17). 17 | Run Porto after installation. 18 | 19 | ### Build 20 | 21 | Download Portodshim project from github.com: 22 | ```bash 23 | git clone https://github.com/ 24 | cd portodshim 25 | ``` 26 | 27 | Build Porto and Portodshim using CMake: 28 | ```bash 29 | go build -o portodshim . 30 | ``` 31 | 32 | 33 | ### Run 34 | 35 | Execute Portodshim binary file (in background optionaly): 36 | ```bash 37 | sudo ./portodshim & 38 | ``` 39 | or 40 | ```bash 41 | sudo ./portodshim --debug & # add debug logs 42 | ``` 43 | 44 | The following socket has to appear after all actions ```/run/portodshim.sock```. 45 | 46 | You can use [crictl](https://github.com/kubernetes-sigs/cri-tools) to check portodshim is running: 47 | ```bash 48 | crictl --runtime-endpoint="unix:///run/portodshim.sock" ps 49 | crictl --image-endpoint="unix:///run/portodshim.sock" images 50 | ``` 51 | 52 | Also you can write the following config to ```/etc/crictl.yaml``` and do not specify endpoint flags: 53 | ```yaml 54 | runtime-endpoint: unix:///run/portodshim.sock 55 | image-endpoint: unix:///run/portodshim.sock 56 | ``` 57 | 58 | 59 | ## Kubernetes over Porto 60 | 61 | You should specify three kubelet flags to use Kubernetes with Porto: 62 | ```bash 63 | --container-runtime="remote" 64 | --container-runtime-endpoint="unix:///run/portodshim.sock" 65 | --container-image-endpoint="unix:///run/portodshim.sock" 66 | ``` 67 | 68 | Kubelet uses Portodshim as a CRI service and sends CRI gRPC request to it. 69 | In turn Portodshim converts СRI request from kubelet to Porto request and forward it to Porto. 70 | Porto performs request. So Portodshim works as proxy between kubelet and Porto. 71 | -------------------------------------------------------------------------------- /portodshim/logshim/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "io" 8 | "log" 9 | "os" 10 | "os/exec" 11 | "runtime" 12 | "strings" 13 | "sync" 14 | "syscall" 15 | "time" 16 | ) 17 | 18 | type logEnt struct { 19 | Log string `json:"log"` 20 | Stream string `json:"stream"` 21 | Time time.Time `json:"time"` 22 | } 23 | 24 | func syncWrite(mtx *sync.Mutex, w io.Writer, buf []byte) (int64, error) { 25 | mtx.Lock() 26 | defer mtx.Unlock() 27 | r1 := bytes.NewReader(buf) 28 | r2 := bytes.NewReader([]byte{'\n'}) 29 | return io.Copy(w, io.MultiReader(r1, r2)) 30 | } 31 | 32 | func streamLoop(mtx *sync.Mutex, w io.Writer, stream io.Reader, streamName string) { 33 | scanner := bufio.NewScanner(stream) 34 | logLine := &logEnt{Stream: streamName} 35 | for scanner.Scan() { 36 | logLine.Log = scanner.Text() + "\n" 37 | logLine.Time = time.Now() 38 | buf, err := json.Marshal(&logLine) 39 | if err != nil { 40 | log.Fatalf("failed to marshal json: %v", err) 41 | } 42 | _, err = syncWrite(mtx, w, buf) 43 | if err != nil { 44 | log.Fatalf("failed to write stream %s log: %v", streamName, err) 45 | } 46 | } 47 | } 48 | 49 | func main() { 50 | // Don't need more threads, so set limit explicitly. 51 | runtime.GOMAXPROCS(2) 52 | 53 | // At least we need a command here 54 | if len(os.Args) < 2 { 55 | os.Exit(-1) 56 | } 57 | 58 | realCmd := os.Args[1] 59 | realArgs := os.Args[2:] 60 | cmd := exec.Command(realCmd, realArgs...) 61 | cmd.SysProcAttr = &syscall.SysProcAttr{ 62 | Pdeathsig: syscall.SIGKILL, 63 | } 64 | 65 | stderr, err := cmd.StderrPipe() 66 | if err != nil { 67 | log.Fatalf("failed to create stderr pipe: %v", err) 68 | } 69 | stdout, err := cmd.StdoutPipe() 70 | if err != nil { 71 | log.Fatalf("failed to create stdout pipe: %v", err) 72 | } 73 | 74 | logMtx := sync.Mutex{} 75 | if err := cmd.Start(); err != nil { 76 | log.Fatalf("failed to start command '%s' with args '%s': %v", realCmd, strings.Join(realArgs, " "), err) 77 | } 78 | 79 | // Let's write real logs to stdout 80 | go streamLoop(&logMtx, os.Stdout, stderr, "stderr") 81 | go streamLoop(&logMtx, os.Stdout, stdout, "stdout") 82 | err = cmd.Wait() 83 | if err != nil { 84 | if err, ok := err.(*exec.ExitError); ok { 85 | os.Exit(err.ExitCode()) 86 | } 87 | log.Fatalf("failed to wait command '%s' with args '%s': %v", realCmd, realArgs, err) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/util/worker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util/log.hpp" 8 | #include "util/unix.hpp" 9 | #include "util/locks.hpp" 10 | #include "util/thread.hpp" 11 | 12 | template> 14 | class TWorker : public TLockable { 15 | protected: 16 | volatile bool Valid = true; 17 | Q Queue; 18 | std::condition_variable Cv; 19 | std::vector> Threads; 20 | size_t Seq = 0; 21 | const std::string Name; 22 | const size_t Nr; 23 | public: 24 | TWorker(const std::string &name, size_t nr) : Name(name), Nr(nr) {} 25 | 26 | void Start() { 27 | for (size_t i = 0; i < Nr; i++) 28 | Threads.push_back(std::shared_ptr(NewThread(&TWorker::WorkerFn, this, Name + std::to_string(i)))); 29 | } 30 | 31 | void Stop() { 32 | if (Valid) { 33 | { 34 | auto lock = ScopedLock(); 35 | Valid = false; 36 | Cv.notify_all(); 37 | } 38 | for (auto thread : Threads) 39 | thread->join(); 40 | Threads.clear(); 41 | } 42 | } 43 | 44 | void Push(const T &elem) { 45 | auto lock = ScopedLock(); 46 | Queue.push(elem); 47 | Seq++; 48 | Cv.notify_one(); 49 | } 50 | 51 | virtual void Wait(TScopedLock &lock) { 52 | if (!Valid) 53 | return; 54 | 55 | Cv.wait(lock); 56 | } 57 | 58 | void WorkerFn(const std::string &name) { 59 | SetProcessName(name); 60 | auto lock = ScopedLock(); 61 | while (Valid) { 62 | if (Queue.empty()) 63 | Wait(lock); 64 | 65 | while (Valid && !Queue.empty()) { 66 | T request = Top(); 67 | Queue.pop(); 68 | 69 | size_t seq = Seq; 70 | lock.unlock(); 71 | bool handled = Handle(request); 72 | lock.lock(); 73 | bool haveNewData = seq != Seq; 74 | 75 | if (!handled) { 76 | Queue.push(request); 77 | if (!haveNewData) 78 | Wait(lock); 79 | } 80 | } 81 | } 82 | } 83 | 84 | virtual const T &Top() =0; 85 | virtual bool Handle(const T &elem) =0; 86 | }; 87 | -------------------------------------------------------------------------------- /test/test-ulimit.py: -------------------------------------------------------------------------------- 1 | from test_common import * 2 | 3 | import sys 4 | import os 5 | import porto 6 | import subprocess 7 | 8 | def ExpectUlimit(a, key, val): 9 | line = subprocess.check_output(['grep', key, "/proc/{}/limits".format(a['root_pid'])]) 10 | ExpectEq(' '.join(line.split()), key + ' ' + val) 11 | 12 | c = porto.Connection() 13 | 14 | a = c.Run("a") 15 | ExpectUlimit(a, 'Max cpu time', 'unlimited unlimited seconds') 16 | ExpectUlimit(a, 'Max file size', 'unlimited unlimited bytes') 17 | ExpectUlimit(a, 'Max data size', 'unlimited unlimited bytes') 18 | ExpectUlimit(a, 'Max stack size', '8388608 unlimited bytes') 19 | ExpectUlimit(a, 'Max core file size', '0 unlimited bytes') # our 20 | ExpectUlimit(a, 'Max resident set', 'unlimited unlimited bytes') 21 | # ExpectUlimit(a, 'Max processes', '') vary 22 | ExpectUlimit(a, 'Max open files', '8192 1048576 files') # our 23 | ExpectUlimit(a, 'Max locked memory', '8388608 unlimited bytes') # our 24 | ExpectUlimit(a, 'Max address space', 'unlimited unlimited bytes') 25 | ExpectUlimit(a, 'Max file locks', 'unlimited unlimited locks') 26 | # ExpectUlimit(a, 'Max pending signals', '') vary 27 | ExpectUlimit(a, 'Max msgqueue size', '819200 819200 bytes') 28 | ExpectUlimit(a, 'Max nice priority', '0 0') 29 | ExpectUlimit(a, 'Max realtime priority', '0 0') 30 | ExpectUlimit(a, 'Max realtime timeout', 'unlimited unlimited us') 31 | a.Destroy() 32 | 33 | a = c.Run("a", memory_limit="1G") 34 | ExpectUlimit(a, 'Max locked memory', '1056964608 unlimited bytes') 35 | a.Destroy() 36 | 37 | a = c.Run("a", memory_limit="1G", ulimit="memlock: 12345678") 38 | ExpectUlimit(a, 'Max locked memory', '12345678 12345678 bytes') 39 | a.Destroy() 40 | 41 | b = c.Run("b", memory_limit="1G") 42 | 43 | a = c.Run("b/a", memory_limit="1G") 44 | ExpectUlimit(a, 'Max locked memory', '1056964608 unlimited bytes') 45 | a.Destroy() 46 | 47 | b.Destroy() 48 | 49 | 50 | def check_core_ulimit(pid, unlimited_count): 51 | pr = subprocess.check_output(['prlimit', '-p', pid, '--core']) 52 | assert unlimited_count == pr.count("unlimited") 53 | 54 | try: 55 | a = c.Run("a", weak=False, command='sleep 20', ulimit='core: unlimited') 56 | pid = a.GetProperty("root_pid") 57 | 58 | check_core_ulimit(pid, 2) 59 | 60 | subprocess.check_call(['prlimit', '-p', pid, '--core=0']) 61 | check_core_ulimit(pid, 0) 62 | 63 | ReloadPortod() 64 | 65 | check_core_ulimit(pid, 0) 66 | 67 | finally: 68 | a.Destroy() 69 | -------------------------------------------------------------------------------- /test/test-import-layer-cgroup.py: -------------------------------------------------------------------------------- 1 | from test_common import * 2 | import porto 3 | import os 4 | import subprocess 5 | import shutil 6 | import tarfile 7 | 8 | test_path = "/tmp/test" 9 | script_path = test_path + "/_check.sh" 10 | 11 | 12 | def create_tar(): 13 | with open(test_path + "/file.txt", "w") as f: 14 | f.write("Oh shit, here we go again") 15 | 16 | with tarfile.open(name=test_path + "/file_layer.tar", mode="w") as t: 17 | t.add(test_path + "/file.txt", arcname="file.txt") 18 | 19 | 20 | def invoke(type, mem_cgroup): 21 | # /portod-helpers is a default memory cgroup 22 | container = "" 23 | if mem_cgroup == "/porto%w": 24 | container = "w" 25 | if type == "python": 26 | conn.ImportLayer("file_layer", test_path + "/file_layer.tar", place=v.place, container=container) 27 | elif type == "portoctl": 28 | subprocess.check_call( 29 | [portoctl, "layer", "-P", v.place, "-I", "file_layer", test_path + "/file_layer.tar", container]) 30 | else: 31 | raise Exception("Unknown type") 32 | 33 | try_remove("file_layer") 34 | 35 | 36 | def try_remove(layer): 37 | try: 38 | conn.RemoveLayer(layer) 39 | except: 40 | pass 41 | 42 | 43 | def test(mem_cgroup): 44 | # This script checks memory cgroup of his process 45 | open(script_path, 'w').write(""" 46 | #!/bin/bash 47 | cgroup=$(cat /proc/self/cgroup | grep "memory" | awk -F ':' '{print $3}') 48 | if [ "$cgroup" = "%s" ]; then 49 | tar $@ 50 | else 51 | exit 1 52 | fi 53 | """ % (mem_cgroup)) 54 | os.chmod(script_path, 0o755) 55 | 56 | invoke("python", mem_cgroup) 57 | invoke("portoctl", mem_cgroup) 58 | 59 | 60 | # We replace default tar to our custom script via porto configs 61 | ConfigurePortod("test-import-layer-cgroup", """ 62 | daemon { 63 | tar_path: "%s" 64 | } 65 | """ % (script_path)) 66 | 67 | AsRoot() 68 | 69 | conn = porto.Connection(timeout=30) 70 | w = conn.Run('w', weak=True) 71 | 72 | try: 73 | shutil.rmtree(test_path) 74 | except: 75 | pass 76 | os.mkdir(test_path) 77 | 78 | v = conn.CreateVolume(path=test_path, backend="tmpfs", space_limit="1Mb", containers="w") 79 | create_tar() 80 | 81 | try: 82 | test('/portod-helpers') 83 | test('/porto%w') 84 | 85 | except Exception as ex: 86 | raise ex 87 | 88 | finally: 89 | v.Destroy() 90 | try_remove("file_layer") 91 | shutil.rmtree(test_path) 92 | ConfigurePortod("test-import-layer-cgroup", "") 93 | -------------------------------------------------------------------------------- /src/util/http.cpp: -------------------------------------------------------------------------------- 1 | #include "http.hpp" 2 | 3 | #define CPPHTTPLIB_NO_EXCEPTIONS 4 | #define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 5 5 | #include "cpp-httplib/httplib.h" 6 | 7 | struct THttpClient::TImpl { 8 | TImpl(const std::string &host) 9 | : Host(host) 10 | , Client(host) 11 | { 12 | #ifdef CPPHTTPLIB_OPENSSL_SUPPORT 13 | Client.enable_server_certificate_verification(false); 14 | #endif 15 | } 16 | 17 | TError HandleResult(const httplib::Result &res, const std::string &path, std::string &response, const THeaders &headers = {}, const TRequest *request = nullptr) { 18 | if (!res) 19 | return TError::System("HTTP request to {} failed: {}", Host + path, res.error()); 20 | 21 | if (res->status != 200) { 22 | if (res->status >= 300 && res->status < 400) { 23 | auto location = res->get_header_value("Location"); 24 | if (!location.empty()) { 25 | if (location[0] == '/') 26 | location = Host + location; 27 | return SingleRequest(location, response, headers, request); 28 | } 29 | } 30 | 31 | auto error = TError::System("HTTP request to {} failed: status {}", Host + path, res->status); 32 | error.Errno = res->status; 33 | return error; 34 | } 35 | 36 | response = res->body; 37 | 38 | return OK; 39 | } 40 | 41 | std::string Host; 42 | httplib::Client Client; 43 | }; 44 | 45 | THttpClient::THttpClient(const std::string &host) : Impl(new TImpl(host)) {} 46 | THttpClient::~THttpClient() = default; 47 | 48 | TError THttpClient::MakeRequest(const std::string &path, std::string &response, const THeaders &headers, const TRequest *request) const { 49 | httplib::Headers hdrs(headers.cbegin(), headers.cend()); 50 | 51 | if (request) 52 | return Impl->HandleResult(Impl->Client.Post(path.c_str(), hdrs, request->Body, request->ContentType), path, response); 53 | 54 | return Impl->HandleResult(Impl->Client.Get(path.c_str(), hdrs), path, response); 55 | } 56 | 57 | TError THttpClient::SingleRequest(const std::string &url, std::string &response, const THeaders &headers, const TRequest *request) { 58 | auto hostPos = url.find("://"); 59 | if (hostPos == std::string::npos) 60 | hostPos = 0; 61 | else 62 | hostPos += 3; 63 | 64 | auto pathPos = url.find('/', hostPos); 65 | 66 | auto host = url.substr(0, pathPos); 67 | auto path = pathPos == std::string::npos ? "/" : url.substr(pathPos); 68 | 69 | return THttpClient(host).MakeRequest(path, response, headers, request); 70 | } 71 | -------------------------------------------------------------------------------- /test/test-locate-process.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | 3 | import os 4 | import porto 5 | import types 6 | 7 | from test_common import * 8 | 9 | NAME = os.path.basename(__file__) 10 | 11 | (f1, f2) = (0, 0) 12 | 13 | def CT_NAME(suffix): 14 | global NAME 15 | return NAME + "-" + suffix 16 | 17 | def SetPipe(ct): 18 | (ct.fd1, ct.fd2) = os.pipe() 19 | ct.SetProperty("stdin_path", "/dev/fd/{}".format(ct.fd1)) 20 | 21 | def Check(ct, value): 22 | os.close(ct.fd1) 23 | os.close(ct.fd2) 24 | ct.Wait() 25 | ExpectEq(ct.GetProperty("stdout"), value) 26 | ct.Stop() 27 | 28 | 29 | CMD = "bash -c \'read; python -c \"\n"\ 30 | "import porto; import sys;\n"\ 31 | "c = porto.Connection();\n"\ 32 | "try:\n"\ 33 | "\tname = c.LocateProcess({}).name;\n"\ 34 | "except:\n"\ 35 | "\tname=\\\"failed\\\";\n"\ 36 | "sys.stdout.write(name);\"\'" 37 | 38 | conn = porto.Connection() 39 | 40 | Catch(conn.Destroy, CT_NAME("a")) 41 | Catch(conn.Destroy, CT_NAME("c")) 42 | 43 | ct = conn.Create(CT_NAME("a")) 44 | ct.Prepare = types.MethodType(SetPipe, ct) 45 | ct.Check = types.MethodType(Check, ct) 46 | ct.SetProperty("env", "PYTHONPATH={}".format(os.environ['PYTHONPATH'])) 47 | 48 | ct.Prepare() 49 | ct.SetProperty("command", CMD.format(2)) 50 | ct.SetProperty("porto_namespace", CT_NAME("a")) 51 | ct.Start() 52 | ct.Check("self") 53 | 54 | ct.Prepare() 55 | ct.SetProperty("porto_namespace", CT_NAME("a/")) 56 | ct.Start() 57 | ct.Check("self") 58 | 59 | ct.Prepare() 60 | ct.SetProperty("porto_namespace", "") 61 | ct.SetProperty("command", CMD.format(2)) 62 | ct.Start() 63 | ct.Check("self") 64 | 65 | ct.Prepare() 66 | ct2 = conn.Create(CT_NAME("a/b")) 67 | ct2.SetProperty("command", "sleep 1000") 68 | ct.SetProperty("command", CMD.format(4)) 69 | ct2.Start() 70 | ct.Check(CT_NAME("a/b")) 71 | 72 | ct.Prepare() 73 | ct2.SetProperty("isolate", False) 74 | ct2.Start() 75 | ct.Check(CT_NAME("a/b")) 76 | 77 | ct.Prepare() 78 | ct2.SetProperty("isolate", True) 79 | ct.SetProperty("enable_porto", "isolate") 80 | ct2.Start() 81 | ct.Check("b") 82 | 83 | ct.Prepare() 84 | ct2.Destroy() 85 | ct2 = conn.Create(CT_NAME("c")) 86 | ct2.SetProperty("command", "sleep 1000") 87 | ct2.Start() 88 | pid = ct2.GetProperty("root_pid") 89 | ct.SetProperty("command", CMD.format(pid)) 90 | ct.Start() 91 | ct.Check("failed") 92 | 93 | ct.Prepare() 94 | ct3 = conn.Create(CT_NAME("c/c")) 95 | ct3.SetProperty("command", "sleep 1000") 96 | ct3.Start() 97 | ct.SetProperty("command", CMD.format(4)) 98 | ct.Start() 99 | ct.Check("failed") 100 | 101 | ct.Destroy() 102 | ct2.Destroy() 103 | -------------------------------------------------------------------------------- /test/test-docker.py: -------------------------------------------------------------------------------- 1 | import porto 2 | import subprocess 3 | from test_common import * 4 | 5 | conn = porto.Connection() 6 | 7 | try: 8 | ConfigurePortod('test-docker', """ 9 | container { 10 | use_os_mode_cgroupns : true, 11 | enable_docker_mode: true 12 | enable_rw_net_cgroups: true 13 | }""") 14 | 15 | a = conn.Run('a', virt_mode='os', net='inherited', root_volume={'layers': ['docker-xenial', 'ubuntu-xenial']}) 16 | 17 | b = conn.Run('a/b', wait=3, virt_mode='docker', user='porto-alice', group='porto-alice', command='grep Cap /proc/self/status') 18 | 19 | ExpectEq(b['exit_code'], '0') 20 | ExpectNe(b['stdout'].count('0000003fffffffff'), 0) 21 | ExpectEq(len(b['stderr']), 0) 22 | 23 | b.Destroy() 24 | 25 | b = conn.Run('a/b', wait=3, virt_mode='docker', user='porto-alice', group='porto-alice', command='bash -c "echo lala > /proc/sys/kernel/core_pattern"') 26 | ExpectNe(b['exit_code'], '0') 27 | ExpectEq(b['stderr'].count('Permission denied'), 1) 28 | b.Destroy() 29 | 30 | uid = subprocess.check_output(['id', 'porto-alice', '--user']).strip() 31 | gid = subprocess.check_output(['id', 'porto-alice', '--group']).strip() 32 | 33 | # change owner 34 | for dir in ['/run']: 35 | b = conn.Run('a/b', wait=5, command='chown {}:{} {}'.format(uid, gid, dir)) 36 | ExpectEq(b['exit_code'], '0') 37 | b.Destroy() 38 | 39 | # change owner recursive 40 | for dir in ['/var/lib/docker', '/var/lib/containerd', '/etc/docker', '/etc/containerd']: 41 | b = conn.Run('a/b', wait=10, command='chown -R {}:{} {}'.format(uid, gid, dir)) 42 | if dir != '/sys/fs/cgroup': 43 | ExpectEq(b['exit_code'], '0') 44 | b.Destroy() 45 | 46 | # load modules for docker 47 | subprocess.check_call(['modprobe', 'ip_tables']) 48 | subprocess.check_call(['modprobe', 'iptable_nat']) 49 | 50 | # start dockerd/containerd in user namespace 51 | c = conn.Run('a/c', wait=0, virt_mode='docker', user='porto-alice', group='porto-alice') 52 | time.sleep(5) 53 | print(c['stderr']) 54 | 55 | ExpectEq(c['state'], 'running') 56 | 57 | # run docker containers in dockerd with user namespace 58 | b = conn.Run('a/c/b', wait=30, command='docker run hello-world', user='porto-alice', group='porto-alice') 59 | ExpectEq(b['exit_code'], '0') 60 | b.Destroy() 61 | 62 | b = conn.Run('a/c/b', wait=30, command='docker run --privileged hello-world', user='porto-alice', group='porto-alice') 63 | ExpectEq(b['exit_code'], '0') 64 | b.Destroy() 65 | 66 | 67 | c.Destroy() 68 | a.Destroy() 69 | 70 | finally: 71 | ConfigurePortod('test-docker', '') 72 | -------------------------------------------------------------------------------- /test/test-htb-restore.py: -------------------------------------------------------------------------------- 1 | import porto 2 | from test_common import * 3 | import os 4 | import re 5 | import subprocess 6 | 7 | # disabled because tc do not used 8 | sys.exit(0) 9 | 10 | def has_class(link, class_id): 11 | (major, minor) = class_id.split(':') 12 | expr = 'class (htb|hfsc) %s\:%s' % (major, minor) 13 | out = subprocess.check_output(['tc', 'class', 'show', 'dev', link]) 14 | return re.search(expr, out) 15 | 16 | 17 | def get_parent_id(link, class_id): 18 | m = re.search( 19 | 'class (htb|hfsc) %s parent ([0-9a-f]+:[0-9a-f]+)' % class_id, 20 | subprocess.check_output([ 21 | 'tc', 'class', 'show', 'dev', link 22 | ]) 23 | ) 24 | 25 | assert m, "Cannot get valid tclass parent" 26 | return m.groups()[1] 27 | 28 | 29 | def get_cs_ids(conn, ct): 30 | cs_ids = [""] * 8 31 | cs_leaf_ids = [""] * 8 32 | 33 | for i in xrange(0, 8): 34 | cs_ids[i] = conn.GetProperty(ct, "net_class_id[CS%s]" % i) 35 | cs_leaf_ids[i] = conn.GetProperty(ct, "net_class_id[Leaf CS%s]" % i) 36 | 37 | return cs_ids, cs_leaf_ids 38 | 39 | 40 | conn = porto.Connection(timeout=5) 41 | 42 | r = conn.Create("a") 43 | 44 | try: 45 | ConfigurePortod("htb-restore", """ 46 | network { 47 | device_qdisc: "default: hfsc", 48 | enable_host_net_classes: true, 49 | managed_ip6tnl: true, 50 | enforce_unmanaged_defaults: true 51 | } 52 | """) 53 | 54 | r2 = conn.Create("a/b") 55 | rr = conn.Create("a/b/c") 56 | rr.SetProperty("command", "/bin/sleep infinity") 57 | rr.SetProperty("net_limit", "default: 0") 58 | rr.SetProperty("net_guarantee", "default: 0") 59 | rr.Start() 60 | 61 | _, a_leaf_ids = get_cs_ids(conn, "a/b/c") 62 | root_ids, _ = get_cs_ids(conn, "/") 63 | 64 | managed_links = [link for link in os.listdir('/sys/class/net') if has_class(link, root_ids[0])] 65 | assert managed_links 66 | 67 | for link in managed_links: 68 | parent_id = get_parent_id(link, a_leaf_ids[0]) 69 | assert parent_id 70 | assert parent_id != "root" 71 | 72 | ConfigurePortod("htb-restore", """ 73 | network { 74 | device_qdisc: "default: htb", 75 | enable_host_net_classes: true, 76 | managed_ip6tnl: true, 77 | enforce_unmanaged_defaults: true 78 | } 79 | """) 80 | 81 | assert conn.GetProperty("a/b/c", "state") == "running" 82 | 83 | for link in managed_links: 84 | for i in xrange(0, 8): 85 | parent_id = get_parent_id(link, a_leaf_ids[i]) 86 | assert parent_id 87 | assert parent_id != "root" 88 | 89 | finally: 90 | try: 91 | r.Destroy() 92 | ConfigurePortod("htb-restore", "") 93 | except: 94 | pass 95 | -------------------------------------------------------------------------------- /test/test-cpu_policy.py: -------------------------------------------------------------------------------- 1 | import porto 2 | from test_common import ExpectEq, ExpectNe 3 | import multiprocessing 4 | 5 | CPUNR = multiprocessing.cpu_count() 6 | 7 | def parse_task_affinity(ct): 8 | pid = ct.GetProperty('root_pid') 9 | return [x for x in open('/proc/%s/status' % pid).readlines() 10 | if 'Cpus_allowed_list' in x][0].split()[1].strip() 11 | 12 | c = porto.Connection() 13 | ct = c.CreateWeakContainer('test-cpu_policy') 14 | 15 | smt_enabled = open("/sys/devices/system/cpu/cpu0/topology/thread_siblings_list").read().strip() != "0" 16 | 17 | # Testcase 1: just set and start 18 | ct.SetProperty('cpu_policy', 'nosmt') 19 | ct.SetProperty('command', 'sleep 1234') 20 | ct.Start() 21 | 22 | if smt_enabled: 23 | ExpectNe(parse_task_affinity(ct), "0-%d" % (CPUNR - 1)) 24 | else: 25 | ExpectEq(parse_task_affinity(ct), "0-%d" % (CPUNR - 1)) 26 | 27 | 28 | # Testcase 2: apply dynamically 29 | ct.SetProperty('cpu_policy', 'normal') 30 | ExpectEq(parse_task_affinity(ct), "0-%d" % (CPUNR - 1)) 31 | ct.SetProperty('cpu_policy', 'nosmt') 32 | if smt_enabled: 33 | ExpectNe(parse_task_affinity(ct), "0-%d" % (CPUNR - 1)) 34 | else: 35 | ExpectEq(parse_task_affinity(ct), "0-%d" % (CPUNR - 1)) 36 | 37 | 38 | # Testcase 3: test inheritance on start 39 | ct2 = c.CreateWeakContainer('test-cpu_policy/sub') 40 | ct2.SetProperty('command', "sleep 4321") 41 | ct2.Start() 42 | if smt_enabled: 43 | ExpectNe(parse_task_affinity(ct2), "0-%d" % (CPUNR - 1)) 44 | else: 45 | ExpectEq(parse_task_affinity(ct2), "0-%d" % (CPUNR - 1)) 46 | ct.Stop() 47 | 48 | 49 | # Testcase 4: ensure non-inheritance in dynamic 50 | if smt_enabled: 51 | ct.SetProperty('cpu_policy', 'normal') 52 | ct.Start() 53 | ct2.Start() 54 | 55 | ExpectEq(parse_task_affinity(ct), "0-%d" % (CPUNR - 1)) 56 | ExpectEq(parse_task_affinity(ct2), "0-%d" % (CPUNR - 1)) 57 | 58 | ct.SetProperty('cpu_policy', 'nosmt') 59 | ExpectNe(parse_task_affinity(ct), "0-%d" % (CPUNR - 1)) 60 | ExpectEq(parse_task_affinity(ct2), "0-%d" % (CPUNR - 1)) 61 | 62 | ct.Stop() 63 | 64 | # Testcase 5: check against cpu_sets / jails 65 | if smt_enabled: 66 | ct.SetProperty('cpu_set', '0,%d' % (CPUNR / 2)) 67 | ct.Start() 68 | ExpectEq(parse_task_affinity(ct), "0") 69 | 70 | ct.Stop() 71 | ct.SetProperty('cpu_set', 'jail 2') 72 | ct.Start() 73 | ExpectEq(parse_task_affinity(ct), "0") 74 | 75 | ct2.Start() 76 | ExpectEq(parse_task_affinity(ct2), "0") 77 | 78 | ct.Stop() 79 | 80 | if CPUNR > 2: 81 | ct.SetProperty('cpu_set', 'jail 4') 82 | ct.Start() 83 | ExpectEq(parse_task_affinity(ct), "0-1") 84 | ct2.Start() 85 | ExpectEq(parse_task_affinity(ct2), "0-1") 86 | 87 | ct.Destroy() 88 | -------------------------------------------------------------------------------- /src/util/error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "fmt/ostream.h" 6 | 7 | #include "rpc.pb.h" 8 | 9 | using ::rpc::EError; 10 | 11 | class TError { 12 | public: 13 | EError Error; 14 | int Errno = 0; 15 | std::string Text; 16 | 17 | static constexpr unsigned MAX_LENGTH = 65536; 18 | 19 | TError(): Error(EError::Success) {} 20 | 21 | TError(EError err): Error(err) {} 22 | 23 | TError(const std::string &text): Error(EError::Unknown), Text(text) {} 24 | 25 | TError(EError err, const std::string &text): Error(err), Text(text) {} 26 | 27 | TError(EError err, int eno, const std::string &text): Error(err), Errno(eno), Text(text) {} 28 | 29 | template TError(EError err, int eno, const char* fmt, const Args&... args): 30 | Error(err), Errno(eno), Text(fmt::format(fmt, args...)) {} 31 | 32 | template TError(EError err, const char* fmt, const Args&... args): 33 | Error(err), Text(fmt::format(fmt, args...)) {} 34 | 35 | template TError(const char* fmt, const Args&... args): 36 | Error(EError::Unknown), Text(fmt::format(fmt, args...)) {} 37 | 38 | template TError(const TError &other, const char* fmt, const Args&... args) : 39 | Error(other.Error), Errno(other.Errno), 40 | Text(fmt::format(fmt, args...) + ": " + other.Text) {} 41 | 42 | TError(const TError &) = default; 43 | TError(TError &&) = default; 44 | TError &operator=(TError &&) = default; 45 | TError &operator=(const TError &) = default; 46 | 47 | explicit operator bool() const { 48 | return Error != EError::Success; 49 | } 50 | 51 | inline bool operator==(const EError error) const { 52 | return Error == error; 53 | } 54 | 55 | inline bool operator!=(const EError error) const { 56 | return Error != error; 57 | } 58 | 59 | static std::string ErrorName(EError error); 60 | 61 | std::string Message() const; 62 | std::string ToString() const; 63 | 64 | static TError System(const std::string &text) { 65 | return TError(EError::Unknown, errno, text); 66 | } 67 | 68 | static TError Queued() { 69 | return TError(EError::Queued); 70 | } 71 | 72 | template static TError System(const char* fmt, const Args&... args) { 73 | return TError(EError::Unknown, errno, fmt::format(fmt, args...)); 74 | } 75 | 76 | friend std::ostream& operator<<(std::ostream& os, const TError& err); 77 | 78 | TError Serialize(int fd) const; 79 | static bool Deserialize(int fd, TError &error); 80 | 81 | void Dump(rpc::TError &error) const; 82 | }; 83 | 84 | extern const TError OK; 85 | -------------------------------------------------------------------------------- /src/util/error.cpp: -------------------------------------------------------------------------------- 1 | #include "util/error.hpp" 2 | 3 | extern "C" { 4 | #include 5 | } 6 | 7 | std::string TError::ErrorName(EError error) { 8 | return rpc::EError_Name(error); 9 | } 10 | 11 | std::string TError::Message() const { 12 | if (Errno) 13 | return fmt::format("{}: {}", strerror(Errno), Text); 14 | return Text; 15 | } 16 | 17 | std::string TError::ToString() const { 18 | if (Errno) 19 | return fmt::format("{}:({}: {})", ErrorName(Error), strerror(Errno), Text); 20 | if (Text.length()) 21 | return fmt::format("{}:({})", ErrorName(Error), Text); 22 | return ErrorName(Error); 23 | } 24 | 25 | TError TError::Serialize(int fd) const { 26 | int ret; 27 | unsigned len = Text.length(); 28 | 29 | ret = write(fd, &Error, sizeof(Error)); 30 | if (ret != sizeof(Error)) 31 | return System("Can't serialize error"); 32 | ret = write(fd, &Errno, sizeof(Errno)); 33 | if (ret != sizeof(Errno)) 34 | return System("Can't serialize errno"); 35 | ret = write(fd, &len, sizeof(len)); 36 | if (ret != sizeof(len)) 37 | return System("Can't serialize length"); 38 | ret = write(fd, Text.data(), len); 39 | if (ret != (int)len) 40 | return System("Can't serialize description"); 41 | 42 | return OK; 43 | } 44 | 45 | bool TError::Deserialize(int fd, TError &error) { 46 | EError err; 47 | int errno_; 48 | int ret; 49 | unsigned len; 50 | 51 | ret = read(fd, &err, sizeof(err)); 52 | if (ret == 0) 53 | return false; 54 | if (ret != sizeof(Error) || !EError_IsValid(err)) { 55 | error = System("Can't deserialize error"); 56 | return true; 57 | } 58 | ret = read(fd, &errno_, sizeof(Errno)); 59 | if (ret != sizeof(Errno)) { 60 | error = System("Can't deserialize errno"); 61 | return true; 62 | } 63 | ret = read(fd, &len, sizeof(len)); 64 | if (ret != sizeof(len)) { 65 | error = System("Can't deserialize length"); 66 | return true; 67 | } 68 | 69 | if (len > TError::MAX_LENGTH) { 70 | error = TError("Invalid error description length: {}", len); 71 | return true; 72 | } 73 | 74 | std::string desc(len, '\0'); 75 | ret = read(fd, &desc[0], len); 76 | if (ret != (int)len) { 77 | error = System("Can't deserialize description"); 78 | return true; 79 | } 80 | 81 | error = TError(err, errno_, std::move(desc)); 82 | return true; 83 | } 84 | 85 | void TError::Dump(rpc::TError &error) const { 86 | error.set_error(Error); 87 | error.set_msg(Message()); 88 | } 89 | 90 | std::ostream &operator<<(std::ostream &os, const TError &err) { 91 | os << err.ToString(); 92 | return os; 93 | } 94 | 95 | const TError OK; 96 | -------------------------------------------------------------------------------- /portodshim/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/containerd/containerd v1.6.7 7 | github.com/containerd/go-cni v1.1.7 8 | github.com/creack/pty v1.1.9 9 | github.com/yandex/porto/src/api/go/porto v0.0.0-20221121185031-c3318d6030a7 10 | go.uber.org/zap v1.21.0 11 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f 12 | google.golang.org/grpc v1.47.0 13 | k8s.io/apimachinery v0.25.3 14 | k8s.io/client-go v0.25.3 15 | k8s.io/cri-api v0.23.5 16 | k8s.io/kubernetes v1.25.3 17 | ) 18 | 19 | require google.golang.org/protobuf v1.28.1 // indirect 20 | 21 | require ( 22 | github.com/Microsoft/go-winio v0.5.1 // indirect 23 | github.com/Microsoft/hcsshim v0.9.4 // indirect 24 | github.com/containerd/cgroups v1.0.3 // indirect 25 | github.com/containernetworking/cni v1.1.1 // indirect 26 | github.com/containernetworking/plugins v1.1.1 // indirect 27 | github.com/davecgh/go-spew v1.1.1 // indirect 28 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 29 | github.com/go-logr/logr v1.2.3 // indirect 30 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 31 | github.com/google/gofuzz v1.2.0 // indirect 32 | github.com/json-iterator/go v1.1.12 // indirect 33 | github.com/moby/spdystream v0.2.0 // indirect 34 | github.com/moby/sys/mountinfo v0.6.0 // indirect 35 | github.com/moby/sys/symlink v0.2.0 // indirect 36 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 37 | github.com/modern-go/reflect2 v1.0.2 // indirect 38 | github.com/pkg/errors v0.9.1 // indirect 39 | github.com/sirupsen/logrus v1.8.1 // indirect 40 | go.opencensus.io v0.23.0 // indirect 41 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 42 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 43 | google.golang.org/appengine v1.6.7 // indirect 44 | gopkg.in/inf.v0 v0.9.1 // indirect 45 | gopkg.in/yaml.v2 v2.4.0 // indirect 46 | k8s.io/api v0.25.3 // indirect 47 | k8s.io/apiserver v0.25.3 // indirect 48 | k8s.io/klog/v2 v2.70.1 // indirect 49 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 50 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 51 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 52 | sigs.k8s.io/yaml v1.2.0 // indirect 53 | ) 54 | 55 | require ( 56 | github.com/gogo/protobuf v1.3.2 // indirect 57 | github.com/golang/protobuf v1.5.2 // indirect 58 | go.uber.org/atomic v1.9.0 // indirect 59 | go.uber.org/multierr v1.8.0 // indirect 60 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b 61 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // direct 62 | golang.org/x/text v0.3.7 // indirect 63 | google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect 64 | ) 65 | 66 | replace github.com/yandex/porto/src/api/go/porto v0.0.0-20221121185031-c3318d6030a7 => ../src/api/go/porto 67 | -------------------------------------------------------------------------------- /test/test-net-ifup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import porto 5 | import subprocess 6 | import time 7 | 8 | from test_common import ConfigurePortod,Expect,ExpectException,ExpectLe 9 | 10 | def veth_link_count(): 11 | return len(subprocess.check_output(["ip", "-o", "link", "show", "type", "veth"]).split()) 12 | 13 | # Prepare 14 | 15 | script_path = os.tmpnam() 16 | 17 | open(script_path,'w') 18 | os.chmod(script_path, 0755) 19 | 20 | cwd = os.getcwd() 21 | portoctl_path = cwd + '/portoctl' 22 | 23 | ConfigurePortod('net-ifup',""" 24 | network { 25 | network_ifup_script: \"%s\" 26 | } 27 | """ % script_path) 28 | 29 | c = porto.Connection() 30 | ct = c.CreateWeakContainer('test-ifup-script') 31 | ct.SetProperty('net', "L3 veth") 32 | ct.SetProperty('labels', 'AA.aaa: test') 33 | ct.SetProperty('net_limit', 'default: 7255') 34 | ct.SetProperty('net_rx_limit', 'default: 7255') 35 | 36 | 37 | # 1. Check invocation & env 38 | 39 | stdout_path = portoctl_path + '_log' 40 | 41 | open(script_path, 'w').write(""" 42 | #!/bin/bash 43 | env > %s 44 | """ % (stdout_path)) 45 | 46 | ct.Start() 47 | ct.Stop() 48 | 49 | env = open(stdout_path, 'r').read() 50 | 51 | Expect('PORTO_CONTAINER=test-ifup-script' in env) 52 | Expect('PORTO_LABELS=AA.aaa: test' in env) 53 | Expect('PORTO_NET=L3 veth' in env) 54 | Expect('PORTO_IP=' in env) 55 | Expect('PORTO_NET_LIMIT=default: 7255' in env) 56 | Expect('PORTO_NET_RX_LIMIT=default: 7255' in env) 57 | Expect('PORTO_L3_IFACE=L3-' in env) 58 | Expect('PORTO_NETNS_FD=/proc' in env) 59 | 60 | open(script_path, 'w').write(""" 61 | #!/bin/bash 62 | exit 1 63 | """) 64 | 65 | stale_count = veth_link_count() 66 | 67 | for i in xrange(0, 10): 68 | ExpectException(ct.Start, porto.exceptions.Unknown) 69 | 70 | # Nets are cleared asynchronously 71 | deadline = time.time() + 30.0 72 | 73 | while time.time() < deadline and veth_link_count() > stale_count: 74 | time.sleep(1) 75 | 76 | ExpectLe(veth_link_count(), stale_count) 77 | 78 | 79 | # 2. Check interaction with portod 80 | 81 | open(script_path, 'w').write(""" 82 | #!/bin/bash 83 | %s get / memory_usage > %s 84 | """ % (portoctl_path, stdout_path)) 85 | 86 | ct.Start() 87 | ct.Stop() 88 | 89 | Expect(int(open(stdout_path, 'r').read()) > 0) 90 | 91 | 92 | # 3. Check portod write permission 93 | 94 | open(script_path, 'w').write(""" 95 | #!/bin/bash 96 | %s create ifup-test 97 | """ % (portoctl_path)) 98 | 99 | ExpectException(ct.Start, porto.exceptions.Unknown) 100 | 101 | open(script_path, 'w').write(""" 102 | #!/bin/bash 103 | %s set test-ifup-script cpu_policy idle 104 | """ % (portoctl_path)) 105 | 106 | ExpectException(ct.Start, porto.exceptions.Unknown) 107 | 108 | os.unlink(script_path) 109 | os.unlink(stdout_path) 110 | 111 | ConfigurePortod('net-ifup','') 112 | -------------------------------------------------------------------------------- /test/test-aufs.py: -------------------------------------------------------------------------------- 1 | import porto 2 | from test_common import * 3 | import os 4 | import stat 5 | import time 6 | import subprocess 7 | 8 | os.umask(0022) 9 | 10 | aufs_tar = '/tmp/test-aufs.tar' 11 | layer_name = 'test-aufs' 12 | layer_path = '/place/porto_layers/' + layer_name 13 | 14 | conn = porto.Connection() 15 | 16 | aufs = conn.CreateVolume(backend='plain') 17 | 18 | file(aufs.path + '/file', 'w') 19 | 20 | file(aufs.path + '/removed-file', 'w') 21 | file(aufs.path + '/.wh.removed-file', 'w') 22 | 23 | os.symlink('foo', aufs.path + '/symlink') 24 | 25 | os.symlink('foo', aufs.path + '/removed-symlink') 26 | file(aufs.path + '/.wh.removed-symlink', 'w') 27 | 28 | os.mkdir(aufs.path + '/dir') 29 | 30 | os.mkdir(aufs.path + '/removed-dir') 31 | os.mkdir(aufs.path + '/removed-dir/removed-subdir') 32 | file(aufs.path + '/.wh.removed-dir', 'w') 33 | 34 | os.mknod(aufs.path + '/device', stat.S_IFCHR|0444, os.makedev(1, 1)) 35 | 36 | os.mknod(aufs.path + '/removed-device', stat.S_IFCHR|0444, os.makedev(1, 1)) 37 | file(aufs.path + '/.wh.removed-device', 'w') 38 | 39 | os.mkdir(aufs.path + '/opaque-dir') 40 | file(aufs.path + '/opaque-dir/file', 'w') 41 | file(aufs.path + '/opaque-dir/.wh..wh..opq', 'w') 42 | 43 | if os.path.exists(aufs_tar): 44 | os.unlink(aufs_tar) 45 | 46 | aufs.Export(aufs_tar) 47 | 48 | aufs.Unlink() 49 | 50 | try: 51 | conn.RemoveLayer(layer_name) 52 | except: 53 | pass 54 | 55 | conn.ImportLayer(layer_name, aufs_tar) 56 | 57 | for dirpath, dir_names, file_names in os.walk(layer_path): 58 | for name in dir_names + file_names: 59 | assert not name.startswith('.wh.'), "Unhandled AUFS whiteout {}".format(name) 60 | 61 | ExpectFile(layer_path + '/file', mode=stat.S_IFREG|0644) 62 | 63 | ExpectFile(layer_path + '/removed-file', mode=stat.S_IFCHR, dev=0) 64 | ExpectFile(layer_path + '/.wh.removed-file', mode=None) 65 | 66 | ExpectFile(layer_path + '/symlink', mode=stat.S_IFLNK|0777) 67 | 68 | ExpectFile(layer_path + '/removed-symlink', mode=stat.S_IFCHR, dev=0) 69 | ExpectFile(layer_path + '/.wh.removed-symlink', mode=None) 70 | 71 | ExpectFile(layer_path + '/dir', mode=stat.S_IFDIR|0755) 72 | 73 | ExpectFile(layer_path + '/removed-dir', mode=stat.S_IFCHR, dev=0) 74 | ExpectFile(layer_path + '/.wh.removed-dir', mode=None) 75 | 76 | ExpectFile(layer_path + '/device', mode=stat.S_IFCHR|0444, dev=os.makedev(1, 1)) 77 | 78 | ExpectFile(layer_path + '/removed-device', mode=stat.S_IFCHR, dev=0) 79 | ExpectFile(layer_path + '/.wh.removed-device', mode=None) 80 | 81 | ExpectFile(layer_path + '/opaque-dir', mode=stat.S_IFDIR|0755) 82 | ExpectFile(layer_path + '/opaque-dir/file', mode=stat.S_IFREG|0644) 83 | ExpectFile(layer_path + '/opaque-dir/.wh..wh..opq', mode=None) 84 | 85 | ExpectEq(subprocess.check_output(['getfattr', '--only-values', '-n', 'trusted.overlay.opaque', layer_path + '/opaque-dir' ]), 'y') 86 | 87 | conn.RemoveLayer(layer_name) 88 | -------------------------------------------------------------------------------- /src/event.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "event.hpp" 3 | #include "util/log.hpp" 4 | #include "util/unix.hpp" 5 | #include "util/worker.hpp" 6 | #include "container.hpp" 7 | #include "client.hpp" 8 | 9 | class TEventWorker : public TWorker> { 10 | public: 11 | TEventWorker(const size_t nr) : TWorker("portod-EV", nr), client("") {} 12 | 13 | TClient client; 14 | 15 | const TEvent &Top() override { 16 | return Queue.top(); 17 | } 18 | 19 | void Wait(TScopedLock &lock) override { 20 | if (!Valid) 21 | return; 22 | 23 | Statistics->QueuedEvents = Queue.size(); 24 | 25 | if (Queue.size()) { 26 | auto now = GetCurrentTimeMs(); 27 | if (Top().DueMs <= now) 28 | return; 29 | auto timeout = Top().DueMs - now; 30 | Cv.wait_for(lock, std::chrono::milliseconds(timeout)); 31 | } else { 32 | TWorker::Wait(lock); 33 | } 34 | } 35 | 36 | bool Handle(const TEvent &event) override { 37 | if (event.DueMs <= GetCurrentTimeMs()) { 38 | client.ClientContainer = RootContainer; 39 | client.StartRequest(); 40 | TContainer::Event(event); 41 | client.FinishRequest(); 42 | return true; 43 | } 44 | 45 | return false; 46 | } 47 | }; 48 | 49 | std::string TEvent::GetMsg() const { 50 | switch (Type) { 51 | case EEventType::ChildExit: 52 | return "exit status " + std::to_string(Exit.Status) 53 | + " for child pid " + std::to_string(Exit.Pid); 54 | case EEventType::Exit: 55 | return "exit status " + std::to_string(Exit.Status) 56 | + " for pid " + std::to_string(Exit.Pid); 57 | case EEventType::RotateLogs: 58 | return "rotate logs"; 59 | case EEventType::Respawn: 60 | return "respawn"; 61 | case EEventType::OOM: 62 | return "OOM"; 63 | case EEventType::WaitTimeout: 64 | return "wait timeout"; 65 | case EEventType::DestroyAgedContainer: 66 | return "destroy aged container"; 67 | case EEventType::DestroyWeakContainer: 68 | return "destroy weak container"; 69 | default: 70 | return "unknown event"; 71 | } 72 | } 73 | 74 | bool TEvent::operator<(const TEvent& rhs) const { 75 | return DueMs >= rhs.DueMs; 76 | } 77 | 78 | void TEventQueue::Add(uint64_t timeoutMs, const TEvent &e) { 79 | TEvent copy = e; 80 | copy.DueMs = GetCurrentTimeMs() + timeoutMs; 81 | Worker->Push(copy); 82 | } 83 | 84 | TEventQueue::TEventQueue() { 85 | Worker = std::make_shared(1); 86 | } 87 | 88 | void TEventQueue::Start() { 89 | Worker->Start(); 90 | } 91 | 92 | void TEventQueue::Stop() { 93 | Worker->Stop(); 94 | } 95 | -------------------------------------------------------------------------------- /test/portotest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.hpp" 5 | #include "version.hpp" 6 | #include "signal.hpp" 7 | #include "unix.hpp" 8 | #include "string.hpp" 9 | #include "test.hpp" 10 | #include "netlink.hpp" 11 | 12 | using std::string; 13 | 14 | extern "C" { 15 | #include 16 | #include 17 | } 18 | 19 | static int Selftest(int argc, char *argv[]) { 20 | std::vector args; 21 | 22 | for (int i = 0; i < argc; i++) 23 | args.push_back(argv[i]); 24 | 25 | return test::SelfTest(args); 26 | } 27 | 28 | static int Stresstest(int argc, char *argv[]) { 29 | int threads = -1, iter = 50; 30 | bool killPorto = true; 31 | if (argc >= 1) 32 | StringToInt(argv[0], threads); 33 | if (argc >= 2) 34 | StringToInt(argv[1], iter); 35 | if (argc >= 3 && strcmp(argv[2], "off") == 0) 36 | killPorto = false; 37 | std::cout << "Threads: " << threads << " Iterations: " << iter << " Kill: " << killPorto << std::endl; 38 | return test::StressTest(threads, iter, killPorto); 39 | } 40 | 41 | static void Usage() { 42 | std::cout << "usage: " << program_invocation_short_name << " [--except] ..." << std::endl; 43 | std::cout << " " << program_invocation_short_name << " stress [threads] [iterations] [kill=on/off]" << std::endl; 44 | } 45 | 46 | static int TestConnectivity() { 47 | using namespace test; 48 | 49 | Porto::Connection api; 50 | 51 | std::vector containers; 52 | ExpectApiSuccess(api.List(containers)); 53 | 54 | std::string name = "a"; 55 | ExpectApiSuccess(api.Create(name)); 56 | ExpectApiSuccess(api.Destroy(name)); 57 | 58 | return 0; 59 | } 60 | 61 | int main(int argc, char *argv[]) { 62 | if (argc == 2 && !strcmp(argv[1], "connectivity")) 63 | return TestConnectivity(); 64 | 65 | // in case client closes pipe we are writing to in the protobuf code 66 | Signal(SIGPIPE, SIG_IGN); 67 | 68 | umask(0); 69 | 70 | if (argc >= 2) { 71 | string name(argv[1]); 72 | if (name == "-h" || name == "--help") { 73 | Usage(); 74 | return EXIT_FAILURE; 75 | } 76 | 77 | if (name == "-v" || name == "--version") { 78 | std::cout << PORTO_VERSION << " " << PORTO_REVISION << std::endl; 79 | return EXIT_FAILURE; 80 | } 81 | } 82 | 83 | ReadConfigs(); 84 | 85 | test::InitUsersAndGroups(); 86 | test::InitKernelFeatures(); 87 | 88 | std::vector v; 89 | ExpectOk(test::Popen("./portod restart", v)); 90 | 91 | string what = ""; 92 | if (argc >= 2) 93 | what = argv[1]; 94 | 95 | if (what == "stress") 96 | return Stresstest(argc - 2, argv + 2); 97 | 98 | return Selftest(argc - 1, argv + 1); 99 | } 100 | -------------------------------------------------------------------------------- /src/cli.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "libporto.hpp" 10 | #include "util/error.hpp" 11 | 12 | class TCommandEnviroment; 13 | 14 | class ICmd { 15 | protected: 16 | Porto::Connection *Api; 17 | std::string Name, Usage, Desc, Help; 18 | sig_atomic_t Interrupted = 0; 19 | public: 20 | int NeedArgs; 21 | 22 | ICmd(Porto::Connection *api, const std::string &name, int args, 23 | const std::string &usage, const std::string &desc, const std::string &help = ""); 24 | virtual ~ICmd() {} 25 | const std::string &GetName() const; 26 | const std::string &GetUsage() const; 27 | const std::string &GetDescription() const; 28 | const std::string &GetHelp() const; 29 | 30 | void PrintError(const std::string &prefix); 31 | void PrintError(const std::string &prefix, const TError &error); 32 | void PrintUsage(); 33 | bool ValidArgs(const std::vector &args); 34 | virtual int Execute(TCommandEnviroment *env) = 0; 35 | }; 36 | 37 | struct Option { 38 | char key; 39 | bool hasArg; 40 | std::function handler; 41 | }; 42 | 43 | class TCommandHandler { 44 | void operator=(const TCommandHandler&) = delete; 45 | TCommandHandler(const TCommandHandler&) = delete; 46 | 47 | public: 48 | using RegisteredCommands = std::map>; 49 | 50 | explicit TCommandHandler(Porto::Connection &api); 51 | ~TCommandHandler(); 52 | 53 | void RegisterCommand(std::unique_ptr cmd); 54 | int HandleCommand(int argc, char *argv[]); 55 | void Usage(const char *command); 56 | 57 | Porto::Connection &GetPortoApi() { return PortoApi; } 58 | const RegisteredCommands &GetCommands() const { return Commands; } 59 | 60 | template 61 | void RegisterCommand() { 62 | RegisterCommand(std::unique_ptr(new TCommand(&PortoApi))); 63 | } 64 | 65 | private: 66 | RegisteredCommands Commands; 67 | Porto::Connection &PortoApi; 68 | }; 69 | 70 | class TCommandEnviroment { 71 | TCommandHandler &Handler; 72 | const std::vector &Arguments; 73 | 74 | TCommandEnviroment() = delete; 75 | TCommandEnviroment(const TCommandEnviroment &) = delete; 76 | 77 | public: 78 | int NeedArgs = 0; 79 | TCommandEnviroment(TCommandHandler &handler, 80 | const std::vector &arguments) 81 | : Handler(handler), 82 | Arguments(arguments) {} 83 | 84 | TCommandEnviroment(TCommandEnviroment *env, 85 | const std::vector &arguments) 86 | : Handler(env->Handler), 87 | Arguments(arguments) {} 88 | 89 | std::vector GetOpts(const std::vector