├── .gitignore ├── .gitmodules ├── .travis.yml ├── AUTHORS ├── CONTRIBUTING.md ├── Dockerfile.build ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.aarch64.md ├── README.md ├── go.mod ├── go.sum ├── hack └── update_binaries.sh ├── libcontainer ├── README.md ├── configs │ ├── config.go │ ├── defaults.go │ └── spec.go ├── console.go ├── console_nabla.go ├── container.go ├── container_nabla.go ├── errors.go ├── factory.go ├── factory_nabla.go ├── generic_error.go ├── hooks.go ├── init_nabla.go ├── process.go ├── process_nabla.go └── stats.go ├── llcli ├── create.go ├── delete.go ├── exec.go ├── init.go ├── kill.go ├── llcli.go ├── start.go ├── state.go ├── util.go ├── util_nabla.go ├── util_runner.go ├── util_signal.go └── util_tty.go ├── llif ├── exec.go ├── fs.go ├── llif.go └── network.go ├── llmodules ├── fs │ ├── iso_storage.go │ └── noop_storage.go └── network │ ├── noop_network.go │ └── tap_bridge.go ├── llruntimes └── nabla │ ├── exec_handler.go │ └── runnc-cont │ ├── config.go │ ├── rumprun.go │ └── runnc_cont.go ├── nabla-lib ├── network │ └── network_linux.go └── storage │ └── storage_linux.go ├── runnc.go ├── tests ├── README.md ├── bats-core │ ├── LICENSE.md │ ├── bats │ ├── bats-exec-suite │ ├── bats-exec-test │ ├── bats-format-tap-stream │ └── bats-preprocess └── integration │ ├── Dockerfile.curl │ ├── Dockerfile.hello │ ├── Dockerfile.node │ ├── Makefile │ ├── helpers.bash │ ├── run.bats │ └── testdata │ ├── config.json │ ├── hello │ ├── app.js │ ├── cwd.js │ └── env.js │ └── test-http-server.py └── utils ├── copy.go └── slice.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | bin/ 3 | build/ 4 | *.nabla 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "solo5"] 2 | path = solo5 3 | url = https://github.com/solo5/solo5.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, IBM 2 | # Copyright 2012-2015 Docker, Inc. 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | # Based on the opencontainers/runc .travis.yml file. 7 | 8 | dist: xenial 9 | language: go 10 | go: 11 | - 1.11.x 12 | - tip 13 | 14 | matrix: 15 | include: 16 | - go: "1.12" 17 | env: GO111MODULE=on 18 | - go: tip 19 | env: GO111MODULE=on 20 | allow_failures: 21 | - go: tip 22 | - env: GO111MODULE=on 23 | 24 | go_import_path: github.com/nabla-containers/runnc 25 | 26 | sudo: required 27 | services: 28 | - docker 29 | 30 | before_install: 31 | - sudo apt-get -qq update 32 | - sudo apt-get install -y libseccomp-dev genisoimage jq 33 | - go get -u golang.org/x/lint/golint 34 | - go get -u github.com/vbatts/git-validation 35 | - sh -c "if [ \"${GO111MODULE}\" != 'on' ]; then go get -u github.com/golang/dep/cmd/dep; fi" 36 | - env | grep TRAVIS_ 37 | 38 | script: 39 | - git-validation -run DCO,short-subject -v 40 | - make build/runnc build/nabla-run 41 | - git submodule update --init --recursive 42 | - make shellcheck 43 | - make build 44 | - make install 45 | - echo '{"runtimes":{"runnc":{"path":"/usr/local/bin/runnc"}}}' | sudo tee /etc/docker/daemon.json 46 | - sudo service docker restart 47 | - make local-integration-test 48 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Corporate Contributors 2 | ====================== 3 | 4 | Copyright (c) 2018 IBM 5 | 6 | Individual Contributors 7 | ======================= 8 | 9 | Brandon Lum (IBM) 10 | Dan Williams (IBM) 11 | Ricardo Koller (IBM) 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We require all contributions to be signed off on, indicating that the 2 | contributor agrees to the Developer's Certificate of Origin 1.1 3 | [used by Linux][1] and reproduced below: 4 | 5 | > Developer's Certificate of Origin 1.1 6 | > 7 | > By making a contribution to this project, I certify that: 8 | > 9 | > 1. The contribution was created in whole or in part by me and I have 10 | > the right to submit it under the open source license indicated in 11 | > the file; or 12 | > 13 | > 2. The contribution is based upon previous work that, to the best of 14 | > my knowledge, is covered under an appropriate open source license 15 | > and I have the right under that license to submit that work with 16 | > modifications, whether created in whole or in part by me, under 17 | > the same open source license (unless I am permitted to submit 18 | > under a different license), as indicated in the file; or 19 | > 20 | > 3. The contribution was provided directly to me by some other person 21 | > who certified (a), (b) or (c) and I have not modified it. 22 | > 23 | > 4. I understand and agree that this project and the contribution are 24 | > public and that a record of the contribution (including all 25 | > personal information I submit with it, including my sign-off) is 26 | > maintained indefinitely and may be redistributed consistent with 27 | > this project or the open source license(s) involved. 28 | 29 | 30 | [1]: https://github.com/torvalds/linux/blob/master/Documentation/process/submitting-patches.rst 31 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM golang:1.11 2 | RUN go get -u github.com/golang/dep/cmd/dep 3 | RUN apt update 4 | RUN apt install -y genisoimage 5 | RUN apt install -y libseccomp-dev 6 | RUN apt install -y sudo 7 | RUN apt install -y jq 8 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:2e4b6024ac18cdcbbf298ea74cce50269e11964eec3668e984299df544f06004" 6 | name = "github.com/docker/docker" 7 | packages = [ 8 | "pkg/mount", 9 | "pkg/term", 10 | ] 11 | pruneopts = "UT" 12 | revision = "a8a31eff10544860d2188dddabdee4d727545796" 13 | version = "v1.5.0" 14 | 15 | [[projects]] 16 | digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8" 17 | name = "github.com/konsorten/go-windows-terminal-sequences" 18 | packages = ["."] 19 | pruneopts = "UT" 20 | revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" 21 | version = "v1.0.1" 22 | 23 | [[projects]] 24 | digest = "1:807b37892d64f054664a178955adbf09e57fd4dcd71f17a6b138a7079ea7ae4f" 25 | name = "github.com/opencontainers/runc" 26 | packages = [ 27 | "libcontainer/label", 28 | "libcontainer/selinux", 29 | "libcontainer/stacktrace", 30 | "libcontainer/system", 31 | "libcontainer/utils", 32 | ] 33 | pruneopts = "UT" 34 | revision = "baf6536d6259209c3edfa2b22237af82942d3dfa" 35 | version = "v0.1.1" 36 | 37 | [[projects]] 38 | digest = "1:57234a321bf1f8f98a8a9a5122a2404cc60d0800516f4ab7a7b2375e4b2d19ea" 39 | name = "github.com/opencontainers/runtime-spec" 40 | packages = ["specs-go"] 41 | pruneopts = "UT" 42 | revision = "4e3b9264a330d094b0386c3703c5f379119711e8" 43 | version = "v1.0.1" 44 | 45 | [[projects]] 46 | digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" 47 | name = "github.com/pkg/errors" 48 | packages = ["."] 49 | pruneopts = "UT" 50 | revision = "645ef00459ed84a119197bfb8d8205042c6df63d" 51 | version = "v0.8.0" 52 | 53 | [[projects]] 54 | branch = "master" 55 | digest = "1:b17bd7b89f445e9c4b82f6144a8fe41e60d921fbe4279f669f9464b277927254" 56 | name = "github.com/sirupsen/logrus" 57 | packages = ["."] 58 | pruneopts = "UT" 59 | revision = "680f584d621da87ee04ea659130e149ba9d23cae" 60 | 61 | [[projects]] 62 | digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679" 63 | name = "github.com/urfave/cli" 64 | packages = ["."] 65 | pruneopts = "UT" 66 | revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" 67 | version = "v1.20.0" 68 | 69 | [[projects]] 70 | digest = "1:2d9d06cb9d46dacfdbb45f8575b39fc0126d083841a29d4fbf8d97708f43107e" 71 | name = "github.com/vishvananda/netlink" 72 | packages = [ 73 | ".", 74 | "nl", 75 | ] 76 | pruneopts = "UT" 77 | revision = "a2ad57a690f3caf3015351d2d6e1c0b95c349752" 78 | version = "v1.0.0" 79 | 80 | [[projects]] 81 | branch = "master" 82 | digest = "1:e4e30678fb2560b5c62f6308c5023d6c294fc7713216fa379411cc74465e866f" 83 | name = "github.com/vishvananda/netns" 84 | packages = ["."] 85 | pruneopts = "UT" 86 | revision = "13995c7128ccc8e51e9a6bd2b551020a27180abd" 87 | 88 | [[projects]] 89 | branch = "master" 90 | digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8" 91 | name = "golang.org/x/crypto" 92 | packages = ["ssh/terminal"] 93 | pruneopts = "UT" 94 | revision = "0c41d7ab0a0ee717d4590a44bcb987dfd9e183eb" 95 | 96 | [[projects]] 97 | branch = "master" 98 | digest = "1:f5aa274a0377f85735edc7fedfb0811d3cbc20af91633797cb359e29c3272271" 99 | name = "golang.org/x/sys" 100 | packages = [ 101 | "unix", 102 | "windows", 103 | ] 104 | pruneopts = "UT" 105 | revision = "fa43e7bc11baaae89f3f902b2b4d832b68234844" 106 | 107 | [solve-meta] 108 | analyzer-name = "dep" 109 | analyzer-version = 1 110 | input-imports = [ 111 | "github.com/docker/docker/pkg/term", 112 | "github.com/opencontainers/runc/libcontainer/label", 113 | "github.com/opencontainers/runc/libcontainer/stacktrace", 114 | "github.com/opencontainers/runc/libcontainer/system", 115 | "github.com/opencontainers/runc/libcontainer/utils", 116 | "github.com/opencontainers/runtime-spec/specs-go", 117 | "github.com/pkg/errors", 118 | "github.com/sirupsen/logrus", 119 | "github.com/urfave/cli", 120 | "github.com/vishvananda/netlink", 121 | "github.com/vishvananda/netns", 122 | "golang.org/x/sys/unix", 123 | ] 124 | solver-name = "gps-cdcl" 125 | solver-version = 1 126 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/opencontainers/runtime-spec" 30 | version = "1.0.1" 31 | 32 | [[constraint]] 33 | name = "github.com/vishvananda/netlink" 34 | version = "1.0.0" 35 | 36 | [[constraint]] 37 | branch = "master" 38 | name = "golang.org/x/sys" 39 | 40 | [prune] 41 | go-tests = true 42 | unused-packages = true 43 | 44 | [[constraint]] 45 | branch = "master" 46 | name = "github.com/sirupsen/logrus" 47 | 48 | [[constraint]] 49 | name = "github.com/urfave/cli" 50 | version = "v1.20.0" 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2018 Contributors as noted in the AUTHORS file 4 | 5 | Permission to use, copy, modify, and/or distribute this software 6 | for any purpose with or without fee is hereby granted, provided 7 | that the above copyright notice and this permission notice appear 8 | in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 11 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 12 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 13 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 14 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 15 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 16 | NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 17 | CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, IBM 2 | # Author(s): Brandon Lum, Ricardo Koller, Dan Williams 3 | # 4 | # Permission to use, copy, modify, and/or distribute this software for 5 | # any purpose with or without fee is hereby granted, provided that the 6 | # above copyright notice and this permission notice appear in all 7 | # copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 | # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 | # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 | # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 | # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA 14 | # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | # PERFORMANCE OF THIS SOFTWARE. 17 | GO_BIN ?= go 18 | 19 | ARCH=$(shell uname --m) 20 | ifeq ($(ARCH), aarch64) 21 | GOARCH="arm64" 22 | else ifeq ($(ARCH), x86_64) 23 | GOARCH="amd64" 24 | endif 25 | 26 | default: build 27 | 28 | SUBMOD_NEEDS_UPDATE=$(shell [ -z "`git submodule | grep -v "^ "`" ] && echo 0 || echo 1) 29 | 30 | ifeq ($(SUBMOD_NEEDS_UPDATE), 1) 31 | submodule_warning: 32 | $(info #####################################################) 33 | $(info # Warning: git submodule out of date!!!! #) 34 | $(info # Please run `git submodule update --init` #) 35 | $(info #####################################################) 36 | $(info ) 37 | $(info Continuing in 5 seconds...) 38 | $(shell sleep 5) 39 | else 40 | submodule_warning: 41 | 42 | endif 43 | 44 | # Synced release version to download from 45 | RELEASE_VER=v0.3 46 | 47 | RELEASE_SERVER=https://github.com/nabla-containers/nabla-base-build/releases/download/${RELEASE_VER}/ 48 | 49 | build: submodule_warning godep build/runnc build/nabla-run test_images 50 | 51 | container-build: 52 | sudo docker build . -f Dockerfile.build -t runnc-build 53 | sudo docker run --rm -v ${PWD}:/go/src/github.com/nabla-containers/runnc -w /go/src/github.com/nabla-containers/runnc runnc-build make 54 | 55 | container-install: 56 | sudo docker build . -f Dockerfile.build -t runnc-build 57 | sudo docker run --rm -v /opt/runnc/:/opt/runnc/ -v /usr/local/bin:/usr/local/bin -v ${PWD}:/go/src/github.com/nabla-containers/runnc -w /go/src/github.com/nabla-containers/runnc runnc-build make install 58 | 59 | container-uninstall: 60 | sudo docker rmi -f runnc-build 61 | make clean 62 | sudo hack/update_binaries.sh delete 63 | 64 | .PHONY: godep 65 | ifeq ($(GO111MODULE),on) 66 | godep: 67 | $(GO_BIN) mod tidy 68 | else 69 | godep: 70 | dep ensure 71 | endif 72 | 73 | update: 74 | $(GO_BIN) get -u 75 | 76 | build/runnc: godep runnc.go 77 | GOOS=linux GOARCH=${GOARCH} $(GO_BIN) build -o $@ . 78 | 79 | solo5/tenders/spt/solo5-spt: FORCE 80 | make -C solo5 81 | 82 | solo5/tests/test_hello/test_hello.spt: FORCE 83 | make -C solo5 84 | 85 | .PHONY: FORCE 86 | 87 | build/nabla-run: solo5/tenders/spt/solo5-spt 88 | install -m 775 -D $< $@ 89 | 90 | tests/integration/node.nabla: 91 | wget -nc ${RELEASE_SERVER}/node-${ARCH}.nabla -O $@ && chmod +x $@ 92 | 93 | tests/integration/test_hello.nabla: solo5/tests/test_hello/test_hello.spt 94 | install -m 664 -D $< $@ 95 | 96 | tests/integration/test_curl.nabla: 97 | wget -nc ${RELEASE_SERVER}/test_curl-${ARCH}.nabla -O $@ && chmod +x $@ 98 | 99 | install: build/runnc build/nabla-run 100 | sudo hack/update_binaries.sh 101 | 102 | .PHONY: test,container-integration-test,local-integration-test,integration,integration-make 103 | test: integration 104 | 105 | test_images: \ 106 | tests/integration/node.nabla \ 107 | tests/integration/test_hello.nabla \ 108 | tests/integration/test_curl.nabla 109 | 110 | integration: local-integration-test 111 | 112 | integration-make: 113 | make -C tests/integration 114 | 115 | local-integration-test: integration-make 116 | sudo tests/bats-core/bats -p tests/integration 117 | 118 | #container-integration-test: test/integration/node_tests.iso 119 | # sudo docker run -it --rm \ 120 | # -v $(CURDIR)/build:/build \ 121 | # -v $(CURDIR)/tests:/tests \ 122 | # --cap-add=NET_ADMIN \ 123 | # -e INCONTAINER=1 \ 124 | # ubuntu:16.04 /tests/bats-core/bats -p /tests/integration 125 | 126 | clean: 127 | sudo rm -rf build/ 128 | sudo rm -f tests/integration/node.nabla \ 129 | tests/integration/test_hello.nabla \ 130 | tests/integration/test_curl.nabla \ 131 | tests/integration/node_tests.iso 132 | sudo make -C solo5 clean 133 | 134 | SHELLCHECK=docker run --rm -v "$(CURDIR)":/v -w /v koalaman/shellcheck 135 | 136 | .PHONY: shellcheck 137 | shellcheck: 138 | $(SHELLCHECK) tests/integration/*.bats tests/integration/*.bash 139 | -------------------------------------------------------------------------------- /README.aarch64.md: -------------------------------------------------------------------------------- 1 | # Runnc on aarch64 2 | 3 | Currently nodeJS is not supported as we are having trouble building it on 4 | aarch64 for NetBSD. As a result, the relevant test fails. 5 | 6 | Apart from the above limitation, runnc on aarch64 works the same way as on 7 | x86_64 (as expected) 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/nabla-containers/runnc.svg?branch=master)](https://travis-ci.org/nabla-containers/runnc) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/nabla-containers/runnc)](https://goreportcard.com/report/github.com/nabla-containers/runnc) 3 | 4 | # Runnc 5 | 6 | `runnc` is the nabla-container runtime which interfaces with the container OCI runtime spec to create a nabla-container runtime. The runtime currently re-uses functionality from `runc` for some setup steps, but will eventually be self-sufficient in providing nabla-container equivalent setups. 7 | 8 | There is initial aarch64 support. For more information please check the [README.aarch64](README.aarch64.md) file 9 | 10 | ## Getting started with the go repo! 11 | 12 | 1. Ensure that your `GOPATH` is set. (https://github.com/golang/go/wiki/SettingGOPATH) 13 | 2. Go get the repo `go get github.com/nabla-containers/runnc` 14 | 3. Install genisoimage on host `sudo apt install genisoimage` 15 | 4. Install jq on host `sudo apt install jq` 16 | 5. Ensure that docker is installed (docker-ce recent versions, i.e. v15 onwards) 17 | 18 | Docker major versions tested with: 19 | 20 | - docker-ce 17 21 | 22 | ## Build and Install Runnc 23 | 24 | We have created two ways to build and install `runnc`. You may build inside a container, or perform a local build. 25 | 26 | 27 | ### Build with a container 28 | ``` 29 | # Go to the repo 30 | cd $GOPATH/src/github.com/nabla-containers/runnc 31 | 32 | # make container-build to build runnc. 33 | make container-build 34 | 35 | # make container-install to install runnc 36 | make container-install 37 | ``` 38 | 39 | ### Build locally 40 | ``` 41 | # Go to the repo 42 | cd $GOPATH/src/github.com/nabla-containers/runnc 43 | 44 | # Get the necessary binaries for the runtime 45 | make build 46 | 47 | # Install libseccomp on the host 48 | sudo apt install libseccomp-dev 49 | 50 | # Install the appropriate binaries/libraries 51 | make install 52 | ``` 53 | 54 | ## Configure Docker to use new Runtime 55 | 56 | 0. Install genisoimage and libseccomp on host 57 | ``` 58 | sudo apt install genisoimage 59 | sudo apt install libseccomp-dev 60 | ``` 61 | 62 | 1. Modify to add runtime to `/etc/docker/daemon.json`, for example: 63 | ``` 64 | { 65 | "runtimes": { 66 | "runnc": { 67 | "path": "/usr/local/bin/runnc" 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | 2. Restart docker 74 | 75 | ``` 76 | systemctl restart docker 77 | ``` 78 | 79 | 3. Run with runtime: 80 | 81 | ``` 82 | sudo docker run --rm --runtime=runnc nablact/nabla-node-base:v0.3 83 | ``` 84 | 85 | ## Limitations 86 | 87 | There are many. Some are fixable and being worked on, some are fixable but harder and will take some time, and some others are ones that we don't really know how to fix (or possibly not worth fixing). 88 | 89 | Container runtime limitations: 90 | - Unable to properly handle /32 IP address assignments. Current hack converts cidr from 32 to 1 91 | 92 | Here are some missing features that we are currently working on: 93 | - ~~a golang base image~~ 94 | - MirageOS and IncludeOS base images 95 | - base images for all the known apps that can run on rumprun (from rumprun-packages), like openjdk. 96 | - a writable file system. Currently only `/tmp` is writable. 97 | - support for committing the image 98 | - volumes (as in `docker -v /a:/a`) 99 | - not ignoring cgroups (start with the memory ones) 100 | - multiple network interfaces 101 | - ~~not using `runc` as an intermediate step. Right now, `runnc` calls `runc` which then calls `nabla-run`~~ 102 | - `runnc` use of interactive console/tty (i.e. `docker run -it`) 103 | 104 | These are some harder features (sorted from more to less important): 105 | - allow dynamic loading of libraries. The nabla runtime can only start static binaries and that seems to be OK for most things, but one big limitation is that python can't load modules with `.so`'s in them. 106 | - use something other than the rumprun netbsd libc: we could use LKL, or IncludeOS recent support for musl libc, or wrap the netbsd libc on something that looks like glibc 107 | - `mmap()` for sharing memory to/from another process (nabla and not nabla) 108 | - GPU support 109 | - support for custom/host namespaces 110 | - `docker exec`. What exactly would it run? what do people do for microcontainers (like an image with just one statically built go binary) 111 | - "real" TLS (Thread Local Storage) support. Right now, pthread-key based thread specific data is supported (`pthread_key_create` / `pthread_setspecific`), but it does not use the real segment-based TLS. So you would get the correct behavior, but not the best-performing implementation. Also, `__thread` is not supported. 112 | 113 | Harder limitations that we don't know how to fix (nor we don't know if they should be fixed): 114 | - support for running vanilla images. Currently nabla can only run nabla based images. 115 | - `fork()`. Should a nabla process fork another nabla process (unikernel)? a single unikernel can't run multiple address spaces 116 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nabla-containers/runnc 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/docker/docker v1.5.0 7 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect 8 | github.com/opencontainers/runc v0.1.1 9 | github.com/opencontainers/runtime-spec v1.0.1 10 | github.com/pkg/errors v0.8.0 11 | github.com/sirupsen/logrus v0.0.0-20181016112244-680f584d621d 12 | github.com/urfave/cli v1.20.0 13 | github.com/vishvananda/netlink v1.0.0 14 | github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc 15 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e // indirect 16 | golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/docker/docker v1.5.0 h1:4EqMjFx2B8oEq3KCV3/OWNf5/LA90Z8P39wMYfmwu1c= 4 | github.com/docker/docker v1.5.0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 5 | github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 6 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 7 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 8 | github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= 9 | github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 10 | github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhzfSgKeC1yFvzQ= 11 | github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 12 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 13 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/sirupsen/logrus v0.0.0-20181016112244-680f584d621d h1:U7ScQ34xlilGaj60rNn/loN+3iCho0T9LGl4aXHGIfM= 17 | github.com/sirupsen/logrus v0.0.0-20181016112244-680f584d621d/go.mod h1:FlsNQqAcy9lUZ69lPvDK0ZUd28jGbCKMHKDLIBensDI= 18 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 20 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 21 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 22 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 23 | github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= 24 | github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= 25 | github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4= 26 | github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= 27 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 28 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= 29 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 30 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 31 | golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc= 32 | golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 33 | -------------------------------------------------------------------------------- /hack/update_binaries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2018, IBM 4 | # Author(s): Brandon Lum, Ricardo Koller, Dan Williams 5 | # 6 | # Permission to use, copy, modify, and/or distribute this software for 7 | # any purpose with or without fee is hereby granted, provided that the 8 | # above copyright notice and this permission notice appear in all 9 | # copies. 10 | # 11 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 12 | # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 13 | # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 14 | # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 15 | # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA 16 | # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 17 | # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 18 | # PERFORMANCE OF THIS SOFTWARE. 19 | 20 | BIN_PATH=/usr/local/bin/ 21 | 22 | # We add binaries like runnc and nabla-run to /opt/X since they are not 23 | # to be consumed directly by the user. 24 | BIN_PATH2=/opt/runnc/bin/ 25 | 26 | COPY_BINS=("runnc" "nabla-run") 27 | 28 | if [[ $1 == "delete" ]] 29 | then 30 | rm -rf ${BIN_PATH2} 31 | 32 | for i in ${COPY_BINS[@]}; do 33 | echo "Deleting " $i 34 | rm -f ${BIN_PATH}/$i 35 | done 36 | else 37 | mkdir -p ${BIN_PATH2} 38 | 39 | for i in ${COPY_BINS[@]}; do 40 | echo "Copying " $i 41 | cp build/$i ${BIN_PATH}/ 42 | cp build/$i ${BIN_PATH2}/ 43 | done 44 | fi 45 | -------------------------------------------------------------------------------- /libcontainer/README.md: -------------------------------------------------------------------------------- 1 | Currently take out `state_*.go` and perform transitions manually. 2 | -------------------------------------------------------------------------------- /libcontainer/configs/config.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | spec "github.com/opencontainers/runtime-spec/specs-go" 5 | ) 6 | 7 | type Config struct { 8 | Args []string `json:"args"` 9 | Rootfs string `json:"rootfs"` 10 | Env []string `json:"env"` 11 | Cwd string `json:"cwd"` 12 | 13 | // Version is the version of opencontainer specification that is supported. 14 | Version string `json:"version"` 15 | 16 | // Labels are user defined metadata that is stored in the config and populated on the state 17 | Labels []string `json:"labels"` 18 | 19 | // Network namespace 20 | NetnsPath string `json:"netnspath"` 21 | 22 | // Hooks configures callbacks for container lifecycle events. 23 | Hooks *spec.Hooks `json:"hooks,omitempty"` 24 | 25 | // Memory is passed from docker cli to runtime. 26 | Memory int64 `json:"memory,omitempty"` 27 | 28 | // Mounts specify source and destination paths that will be copied 29 | // inside the container's rootfs. 30 | Mounts []spec.Mount `json:"mounts,omitempty"` 31 | } 32 | 33 | // HostUID returns the UID to run the nabla container as. Default is root. 34 | func (c Config) HostUID() (int, error) { 35 | return 0, nil 36 | } 37 | 38 | // HostGID returns the GID to run the nabla container as. Default is root. 39 | func (c Config) HostGID() (int, error) { 40 | return 0, nil 41 | } 42 | -------------------------------------------------------------------------------- /libcontainer/configs/defaults.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | //ContainerMemoryMinimum is the size in MB if none is explicitly passed from docker cli. 4 | const ContainerMemoryMinimum = 512 5 | -------------------------------------------------------------------------------- /libcontainer/configs/spec.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/opencontainers/runtime-spec/specs-go" 7 | "github.com/pkg/errors" 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func ParseSpec(s *specs.Spec) (*Config, error) { 12 | if s == nil { 13 | return nil, errors.New("Spec is nil") 14 | } 15 | if s.Process == nil || s.Process.Args == nil { 16 | return nil, errors.New("Process Args is nil") 17 | } 18 | 19 | if s.Root == nil { 20 | return nil, errors.New("Root is nil") 21 | } 22 | 23 | labels := []string{} 24 | for k, v := range s.Annotations { 25 | labels = append(labels, fmt.Sprintf("%s=%s", k, v)) 26 | } 27 | 28 | var netnsPath string 29 | var memory int64 30 | if s.Linux != nil { 31 | for _, v := range s.Linux.Namespaces { 32 | if v.Type == specs.NetworkNamespace { 33 | netnsPath = v.Path 34 | } 35 | } 36 | } 37 | 38 | // Setting default memory to pass to runnc as an argument. 39 | // Docker passes it as bytes, nabla-run expects MB. 40 | if s.Linux != nil && s.Linux.Resources != nil && s.Linux.Resources.Memory != nil && s.Linux.Resources.Memory.Limit != nil { 41 | memory = (*s.Linux.Resources.Memory.Limit) / (1 << 20) 42 | } 43 | 44 | // Set a floor for container memory 45 | if memory < ContainerMemoryMinimum { 46 | memory = ContainerMemoryMinimum 47 | log.Warning("Memory was less than ContainerMemoryMinimum setting to 512mb") 48 | } 49 | 50 | cfg := Config{ 51 | Args: s.Process.Args, 52 | Rootfs: s.Root.Path, 53 | Env: s.Process.Env, 54 | Cwd: s.Process.Cwd, 55 | Version: s.Version, 56 | NetnsPath: netnsPath, 57 | Labels: labels, 58 | Hooks: s.Hooks, 59 | Memory: memory, 60 | Mounts: s.Mounts, 61 | } 62 | 63 | return &cfg, nil 64 | } 65 | -------------------------------------------------------------------------------- /libcontainer/console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import "io" 18 | 19 | // Console represents a pseudo TTY. 20 | type Console interface { 21 | io.ReadWriter 22 | io.Closer 23 | 24 | // Path returns the filesystem path to the slave side of the pty. 25 | Path() string 26 | 27 | // Fd returns the fd for the master of the pty. 28 | Fd() uintptr 29 | } 30 | -------------------------------------------------------------------------------- /libcontainer/console_nabla.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | "syscall" 22 | "unsafe" 23 | 24 | "github.com/opencontainers/runc/libcontainer/label" 25 | ) 26 | 27 | // NewConsole returns an initalized console that can be used within a container by copying bytes 28 | // from the master side to the slave that is attached as the tty for the container's init process. 29 | func NewConsole(uid, gid int) (Console, error) { 30 | master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) 31 | if err != nil { 32 | return nil, err 33 | } 34 | console, err := ptsname(master) 35 | if err != nil { 36 | return nil, err 37 | } 38 | if err := unlockpt(master); err != nil { 39 | return nil, err 40 | } 41 | if err := os.Chmod(console, 0600); err != nil { 42 | return nil, err 43 | } 44 | if err := os.Chown(console, uid, gid); err != nil { 45 | return nil, err 46 | } 47 | return &linuxConsole{ 48 | slavePath: console, 49 | master: master, 50 | }, nil 51 | } 52 | 53 | // newConsoleFromPath is an internal function returning an initialized console for use inside 54 | // a container's MNT namespace. 55 | func newConsoleFromPath(slavePath string) *linuxConsole { 56 | return &linuxConsole{ 57 | slavePath: slavePath, 58 | } 59 | } 60 | 61 | // linuxConsole is a linux psuedo TTY for use within a container. 62 | type linuxConsole struct { 63 | master *os.File 64 | slavePath string 65 | } 66 | 67 | func (c *linuxConsole) Fd() uintptr { 68 | return c.master.Fd() 69 | } 70 | 71 | func (c *linuxConsole) Path() string { 72 | return c.slavePath 73 | } 74 | 75 | func (c *linuxConsole) Read(b []byte) (int, error) { 76 | return c.master.Read(b) 77 | } 78 | 79 | func (c *linuxConsole) Write(b []byte) (int, error) { 80 | return c.master.Write(b) 81 | } 82 | 83 | func (c *linuxConsole) Close() error { 84 | if m := c.master; m != nil { 85 | return m.Close() 86 | } 87 | return nil 88 | } 89 | 90 | // mount initializes the console inside the rootfs mounting with the specified mount label 91 | // and applying the correct ownership of the console. 92 | func (c *linuxConsole) mount(rootfs, mountLabel string) error { 93 | oldMask := syscall.Umask(0000) 94 | defer syscall.Umask(oldMask) 95 | if err := label.SetFileLabel(c.slavePath, mountLabel); err != nil { 96 | return err 97 | } 98 | dest := filepath.Join(rootfs, "/dev/console") 99 | f, err := os.Create(dest) 100 | if err != nil && !os.IsExist(err) { 101 | return err 102 | } 103 | if f != nil { 104 | f.Close() 105 | } 106 | return syscall.Mount(c.slavePath, dest, "bind", syscall.MS_BIND, "") 107 | } 108 | 109 | // dupStdio opens the slavePath for the console and dups the fds to the current 110 | // processes stdio, fd 0,1,2. 111 | func (c *linuxConsole) dupStdio() error { 112 | slave, err := c.open(syscall.O_RDWR) 113 | if err != nil { 114 | return err 115 | } 116 | fd := int(slave.Fd()) 117 | for _, i := range []int{0, 1, 2} { 118 | if err := syscall.Dup3(fd, i, 0); err != nil { 119 | return err 120 | } 121 | } 122 | return nil 123 | } 124 | 125 | // open is a clone of os.OpenFile without the O_CLOEXEC used to open the pty slave. 126 | func (c *linuxConsole) open(flag int) (*os.File, error) { 127 | r, e := syscall.Open(c.slavePath, flag, 0) 128 | if e != nil { 129 | return nil, &os.PathError{ 130 | Op: "open", 131 | Path: c.slavePath, 132 | Err: e, 133 | } 134 | } 135 | return os.NewFile(uintptr(r), c.slavePath), nil 136 | } 137 | 138 | func ioctl(fd uintptr, flag, data uintptr) error { 139 | if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, flag, data); err != 0 { 140 | return err 141 | } 142 | return nil 143 | } 144 | 145 | // unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f. 146 | // unlockpt should be called before opening the slave side of a pty. 147 | func unlockpt(f *os.File) error { 148 | var u int32 149 | return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) 150 | } 151 | 152 | // ptsname retrieves the name of the first available pts for the given master. 153 | func ptsname(f *os.File) (string, error) { 154 | var n int32 155 | if err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil { 156 | return "", err 157 | } 158 | return fmt.Sprintf("/dev/pts/%d", n), nil 159 | } 160 | -------------------------------------------------------------------------------- /libcontainer/container.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import ( 18 | "os" 19 | "time" 20 | 21 | "github.com/nabla-containers/runnc/libcontainer/configs" 22 | ) 23 | 24 | // Status is the status of a container. 25 | type Status int 26 | 27 | const ( 28 | // Created is the status that denotes the container exists but has not been run yet. 29 | Created Status = iota 30 | // Running is the status that denotes the container exists and is running. 31 | Running 32 | // Pausing is the status that denotes the container exists, it is in the process of being paused. 33 | Pausing 34 | // Paused is the status that denotes the container exists, but all its processes are paused. 35 | Paused 36 | // Stopped is the status that denotes the container does not have a created or running process. 37 | Stopped 38 | ) 39 | 40 | func (s Status) String() string { 41 | switch s { 42 | case Created: 43 | return "created" 44 | case Running: 45 | return "running" 46 | case Pausing: 47 | return "pausing" 48 | case Paused: 49 | return "paused" 50 | case Stopped: 51 | return "stopped" 52 | default: 53 | return "unknown" 54 | } 55 | } 56 | 57 | // BaseState represents the platform agnostic pieces relating to a 58 | // running container's state 59 | type BaseState struct { 60 | // ID is the container ID. 61 | ID string `json:"id"` 62 | 63 | // InitProcessPid is the init process id in the parent namespace. 64 | InitProcessPid int `json:"init_process_pid"` 65 | 66 | // InitProcessStartTime is the init process start time in clock cycles since boot time. 67 | InitProcessStartTime string `json:"init_process_start"` 68 | 69 | // Created is the unix timestamp for the creation time of the container in UTC 70 | Created time.Time `json:"created"` 71 | 72 | // Config is the container's configuration. 73 | Config configs.Config `json:"config"` 74 | } 75 | 76 | // BaseContainer is a libcontainer container object. 77 | // 78 | // Each container is thread-safe within the same process. Since a container can 79 | // be destroyed by a separate process, any function may return that the container 80 | // was not found. BaseContainer includes methods that are platform agnostic. 81 | type BaseContainer interface { 82 | // Returns the ID of the container 83 | ID() string 84 | 85 | // Returns the current status of the container. 86 | // 87 | // errors: 88 | // ContainerNotExists - Container no longer exists, 89 | // Systemerror - System error. 90 | Status() (Status, error) 91 | 92 | // State returns the current container's state information. 93 | // 94 | // errors: 95 | // SystemError - System error. 96 | State() (*State, error) 97 | 98 | // Returns the current config of the container. 99 | Config() configs.Config 100 | 101 | // Returns the PIDs inside this container. The PIDs are in the namespace of the calling process. 102 | // 103 | // errors: 104 | // ContainerNotExists - Container no longer exists, 105 | // Systemerror - System error. 106 | // 107 | // Some of the returned PIDs may no longer refer to processes in the Container, unless 108 | // the Container state is PAUSED in which case every PID in the slice is valid. 109 | Processes() ([]int, error) 110 | 111 | // Returns statistics for the container. 112 | // 113 | // errors: 114 | // ContainerNotExists - Container no longer exists, 115 | // Systemerror - System error. 116 | Stats() (*Stats, error) 117 | 118 | // Set resources of container as configured 119 | // 120 | // We can use this to change resources when containers are running. 121 | // 122 | // errors: 123 | // SystemError - System error. 124 | Set(config configs.Config) error 125 | 126 | // Start a process inside the container. Returns error if process fails to 127 | // start. You can track process lifecycle with passed Process structure. 128 | // 129 | // errors: 130 | // ContainerNotExists - Container no longer exists, 131 | // ConfigInvalid - config is invalid, 132 | // ContainerPaused - Container is paused, 133 | // SystemError - System error. 134 | Start(process *Process) (err error) 135 | 136 | // Run immediatly starts the process inside the conatiner. Returns error if process 137 | // fails to start. It does not block waiting for the exec fifo after start returns but 138 | // opens the fifo after start returns. 139 | // 140 | // errors: 141 | // ContainerNotExists - Container no longer exists, 142 | // ConfigInvalid - config is invalid, 143 | // ContainerPaused - Container is paused, 144 | // SystemError - System error. 145 | Run(process *Process) (err error) 146 | 147 | // Destroys the container after killing all running processes. 148 | // 149 | // Any event registrations are removed before the container is destroyed. 150 | // No error is returned if the container is already destroyed. 151 | // 152 | // errors: 153 | // SystemError - System error. 154 | Destroy() error 155 | 156 | // Signal sends the provided signal code to the container's initial process. 157 | // 158 | // errors: 159 | // SystemError - System error. 160 | Signal(s os.Signal, all bool) error 161 | 162 | // Exec signals the container to exec the users process at the end of the init. 163 | // 164 | // errors: 165 | // SystemError - System error. 166 | Exec() error 167 | } 168 | -------------------------------------------------------------------------------- /libcontainer/container_nabla.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build linux 16 | 17 | package libcontainer 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "io/ioutil" 23 | "os" 24 | "os/exec" 25 | "path/filepath" 26 | "sync" 27 | "syscall" 28 | "time" 29 | 30 | "github.com/nabla-containers/runnc/libcontainer/configs" 31 | ll "github.com/nabla-containers/runnc/llif" 32 | "github.com/opencontainers/runc/libcontainer/system" 33 | "github.com/opencontainers/runc/libcontainer/utils" 34 | "github.com/pkg/errors" 35 | ) 36 | 37 | const stdioFdCount = 3 38 | 39 | // State represents a running container's state 40 | type State struct { 41 | BaseState 42 | 43 | FsState ll.LLState `json:"fsstate"` 44 | NetworkState ll.LLState `json:"netstate"` 45 | ExecState ll.LLState `json:"execstate"` 46 | 47 | // Platform specific fields below here 48 | Status Status `json:"status"` 49 | } 50 | 51 | // Container is a libcontainer container object. 52 | // 53 | // Each container is thread-safe within the same process. Since a container can 54 | // be destroyed by a separate process, any function may return that the container 55 | // was not found. 56 | type Container interface { 57 | BaseContainer 58 | 59 | // Methods below here are platform specific 60 | } 61 | 62 | type nablaContainer struct { 63 | id string 64 | root string 65 | config *configs.Config 66 | m sync.Mutex 67 | state *State 68 | created time.Time 69 | llcHandler ll.RunllcHandler 70 | } 71 | 72 | func (c *nablaContainer) Config() configs.Config { 73 | return *c.config 74 | } 75 | 76 | func (c *nablaContainer) Status() (Status, error) { 77 | c.m.Lock() 78 | defer c.m.Unlock() 79 | return c.currentStatus() 80 | } 81 | 82 | func (c *nablaContainer) State() (*State, error) { 83 | c.m.Lock() 84 | defer c.m.Unlock() 85 | return c.currentState() 86 | } 87 | 88 | func (c *nablaContainer) Destroy() error { 89 | c.m.Lock() 90 | defer c.m.Unlock() 91 | return c.destroy() 92 | } 93 | 94 | func (c *nablaContainer) ID() string { 95 | return c.id 96 | } 97 | 98 | // TODO(NABLA) 99 | func (c *nablaContainer) Processes() ([]int, error) { 100 | return nil, errors.New("NablaContainer.Processes not implemented") 101 | } 102 | 103 | // TODO(NABLA) 104 | func (c *nablaContainer) Stats() (*Stats, error) { 105 | return nil, errors.New("NablaContainer.Stats not implemented") 106 | } 107 | 108 | // TODO(NABLA) 109 | func (c *nablaContainer) Set(config configs.Config) error { 110 | c.m.Lock() 111 | defer c.m.Unlock() 112 | return errors.New("NablaContainer.Set not implemented") 113 | } 114 | 115 | func (c *nablaContainer) Start(process *Process) error { 116 | c.m.Lock() 117 | defer c.m.Unlock() 118 | return c.start(process) 119 | } 120 | 121 | // TODO(NABLA) 122 | func (c *nablaContainer) Run(process *Process) error { 123 | c.m.Lock() 124 | defer c.m.Unlock() 125 | return errors.New("NablaContainer.Run not implemented") 126 | } 127 | 128 | // TODO(NABLA) 129 | func (c *nablaContainer) Exec() error { 130 | c.m.Lock() 131 | defer c.m.Unlock() 132 | return c.exec() 133 | } 134 | 135 | func (c *nablaContainer) Signal(sig os.Signal, all bool) error { 136 | // For nabla container, we only have 1 process 137 | s, ok := sig.(syscall.Signal) 138 | if !ok { 139 | return errors.New("os: unsupported signal type") 140 | } 141 | pid := c.state.InitProcessPid 142 | if all { 143 | pid = -pid 144 | } 145 | return syscall.Kill(pid, s) 146 | } 147 | 148 | type nablaProcess struct { 149 | process *Process 150 | cmd *exec.Cmd 151 | } 152 | 153 | func (p *nablaProcess) wait() (*os.ProcessState, error) { 154 | err := p.cmd.Wait() 155 | return p.cmd.ProcessState, err 156 | } 157 | 158 | func (p *nablaProcess) pid() int { 159 | return p.cmd.Process.Pid 160 | } 161 | 162 | func (p *nablaProcess) signal(sig os.Signal) error { 163 | s, ok := sig.(syscall.Signal) 164 | if !ok { 165 | return errors.New("os: unsupported signal type") 166 | } 167 | return syscall.Kill(p.pid(), s) 168 | } 169 | 170 | // TODO: TEMP COPY OUTDATED VERSION OF RUNC 171 | // NewSockPair returns a new unix socket pair 172 | func NewSockPair(name string) (parent *os.File, child *os.File, err error) { 173 | fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) 174 | if err != nil { 175 | return nil, nil, err 176 | } 177 | return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil 178 | } 179 | 180 | func (c *nablaContainer) start(p *Process) error { 181 | parentPipe, childPipe, err := NewSockPair("init") 182 | if err != nil { 183 | return newSystemErrorWithCause(err, "creating new init pipe") 184 | } 185 | cmd, err := c.commandTemplate(p, childPipe) 186 | if err != nil { 187 | return newSystemErrorWithCause(err, "creating new command template") 188 | } 189 | 190 | // We only set up rootDir if we're not doing a `runc exec`. The reason for 191 | // this is to avoid cases where a racing, unprivileged process inside the 192 | // container can get access to the statedir file descriptor (which would 193 | // allow for container rootfs escape). 194 | rootDir, err := os.Open(c.root) 195 | if err != nil { 196 | return err 197 | } 198 | cmd.ExtraFiles = append(cmd.ExtraFiles, rootDir) 199 | cmd.Env = append(cmd.Env, 200 | fmt.Sprintf("_LIBCONTAINER_STATEDIR=%d", stdioFdCount+len(cmd.ExtraFiles)-1)) 201 | 202 | // newInitProcess 203 | p.ops = &nablaProcess{ 204 | process: p, 205 | cmd: cmd, 206 | } 207 | 208 | defer parentPipe.Close() 209 | config := initConfig{ 210 | Id: c.id, 211 | BundlePath: c.root, 212 | Root: c.config.Rootfs, 213 | Args: c.config.Args, 214 | Cwd: c.config.Cwd, 215 | Env: c.config.Env, 216 | NetnsPath: c.config.NetnsPath, 217 | Hooks: c.config.Hooks, 218 | Memory: c.config.Memory, 219 | Mounts: c.config.Mounts, 220 | Config: c.config, 221 | FsState: c.state.FsState, 222 | NetworkState: c.state.NetworkState, 223 | ExecState: c.state.ExecState, 224 | } 225 | 226 | enc := json.NewEncoder(parentPipe) 227 | if err := enc.Encode(config); err != nil { 228 | return err 229 | } 230 | 231 | if err := cmd.Start(); err != nil { 232 | return err 233 | } 234 | 235 | if cmd.Process == nil { 236 | return errors.New("Cmd.Process is nil after starting") 237 | } 238 | 239 | c.state.InitProcessPid = p.ops.pid() 240 | c.state.Created = time.Now().UTC() 241 | c.state.Status = Created 242 | c.state.InitProcessStartTime, err = system.GetProcessStartTime(c.state.BaseState.InitProcessPid) 243 | if err != nil { 244 | return err 245 | } 246 | 247 | c.saveState(c.state) 248 | 249 | return nil 250 | } 251 | 252 | func (c *nablaContainer) exec() error { 253 | path := filepath.Join(c.root, execFifoFilename) 254 | f, err := os.OpenFile(path, os.O_RDONLY, 0) 255 | if err != nil { 256 | return newSystemErrorWithCause(err, "open exec fifo for reading") 257 | } 258 | defer f.Close() 259 | data, err := ioutil.ReadAll(f) 260 | if err != nil { 261 | return err 262 | } 263 | if len(data) > 0 { 264 | c.state.Status = Running 265 | c.saveState(c.state) 266 | os.Remove(path) 267 | return nil 268 | } 269 | return fmt.Errorf("cannot start an already running container") 270 | } 271 | 272 | func (c *nablaContainer) destroy() error { 273 | c.state.InitProcessPid = 0 274 | c.state.Status = Stopped 275 | 276 | execInput := &ll.ExecDestroyInput{ 277 | ll.ExecGenericInput{ 278 | ContainerRoot: c.root, 279 | Config: c.config, 280 | ContainerId: c.id, 281 | FsState: &c.state.FsState, 282 | NetworkState: &c.state.NetworkState, 283 | ExecState: &c.state.ExecState, 284 | }, 285 | } 286 | 287 | execState, err := c.llcHandler.ExecH.ExecDestroyFunc(execInput) 288 | if err != nil { 289 | return err 290 | } 291 | if execState != nil { 292 | c.state.ExecState = *execState 293 | } else { 294 | c.state.ExecState = ll.LLState{} 295 | } 296 | 297 | fsInput := &ll.FsDestroyInput{ 298 | ll.FsGenericInput{ 299 | ContainerRoot: c.root, 300 | Config: c.config, 301 | ContainerId: c.id, 302 | FsState: &c.state.FsState, 303 | NetworkState: &c.state.NetworkState, 304 | ExecState: execState, 305 | }, 306 | } 307 | 308 | fsState, err := c.llcHandler.FsH.FsDestroyFunc(fsInput) 309 | if err != nil { 310 | return err 311 | } 312 | if fsState != nil { 313 | c.state.FsState = *fsState 314 | } else { 315 | c.state.FsState = ll.LLState{} 316 | } 317 | 318 | networkInput := &ll.NetworkDestroyInput{ 319 | ll.NetworkGenericInput{ 320 | ContainerRoot: c.root, 321 | Config: c.config, 322 | ContainerId: c.id, 323 | FsState: fsState, 324 | NetworkState: &c.state.NetworkState, 325 | ExecState: execState, 326 | }, 327 | } 328 | 329 | networkState, err := c.llcHandler.NetworkH.NetworkDestroyFunc(networkInput) 330 | if err != nil { 331 | return err 332 | } 333 | 334 | if networkState != nil { 335 | c.state.NetworkState = *networkState 336 | } else { 337 | c.state.NetworkState = ll.LLState{} 338 | } 339 | 340 | return nil 341 | } 342 | 343 | func (c *nablaContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) { 344 | cmd := exec.Command("/proc/self/exe", "init") 345 | cmd.Stdin = p.Stdin 346 | cmd.Stdout = p.Stdout 347 | cmd.Stderr = p.Stderr 348 | cmd.Dir = c.config.Rootfs 349 | cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 350 | 351 | cmd.ExtraFiles = append(cmd.ExtraFiles, p.ExtraFiles...) 352 | cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe) 353 | cmd.Env = append(cmd.Env, 354 | fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1), 355 | ) 356 | // NOTE: when running a container with no PID namespace and the parent process spawning the container is 357 | // PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason 358 | // even with the parent still running. 359 | /* TODO: Check if needed 360 | if c.config.ParentDeathSignal > 0 { 361 | cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal) 362 | } 363 | */ 364 | return cmd, nil 365 | } 366 | 367 | func (c *nablaContainer) currentState() (*State, error) { 368 | process, err := os.FindProcess(int(c.state.InitProcessPid)) 369 | if err != nil { 370 | return nil, err 371 | } 372 | 373 | // [kill(2)] If sig is 0, then no signal is sent, but error checking is still per‐ 374 | // formed; this can be used to check for the existence of a process ID or 375 | // process group ID. 376 | err = process.Signal(syscall.Signal(0)) 377 | if err != nil { 378 | c.state.Status = Stopped 379 | c.saveState(c.state) 380 | } 381 | 382 | return c.state, nil 383 | } 384 | 385 | func (c *nablaContainer) currentStatus() (Status, error) { 386 | var sts Status 387 | state, err := c.currentState() 388 | if err != nil { 389 | return sts, err 390 | } 391 | 392 | return state.Status, nil 393 | } 394 | 395 | func (c *nablaContainer) saveState(s *State) error { 396 | f, err := os.Create(filepath.Join(c.root, stateFilename)) 397 | if err != nil { 398 | return err 399 | } 400 | defer f.Close() 401 | return utils.WriteJSON(f, s) 402 | } 403 | -------------------------------------------------------------------------------- /libcontainer/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import ( 18 | "io" 19 | ) 20 | 21 | // ErrorCode is the API error code type. 22 | type ErrorCode int 23 | 24 | // API error codes. 25 | const ( 26 | // Factory errors 27 | IdInUse ErrorCode = iota 28 | InvalidIdFormat 29 | 30 | // Container errors 31 | ContainerNotExists 32 | ContainerPaused 33 | ContainerNotStopped 34 | ContainerNotRunning 35 | ContainerNotPaused 36 | 37 | // Process errors 38 | NoProcessOps 39 | 40 | // Common errors 41 | ConfigInvalid 42 | ConsoleExists 43 | SystemError 44 | ) 45 | 46 | func (c ErrorCode) String() string { 47 | switch c { 48 | case IdInUse: 49 | return "Id already in use" 50 | case InvalidIdFormat: 51 | return "Invalid format" 52 | case ContainerPaused: 53 | return "Container paused" 54 | case ConfigInvalid: 55 | return "Invalid configuration" 56 | case SystemError: 57 | return "System error" 58 | case ContainerNotExists: 59 | return "Container does not exist" 60 | case ContainerNotStopped: 61 | return "Container is not stopped" 62 | case ContainerNotRunning: 63 | return "Container is not running" 64 | case ConsoleExists: 65 | return "Console exists for process" 66 | case ContainerNotPaused: 67 | return "Container is not paused" 68 | case NoProcessOps: 69 | return "No process operations" 70 | default: 71 | return "Unknown error" 72 | } 73 | } 74 | 75 | // Error is the API error type. 76 | type Error interface { 77 | error 78 | 79 | // Returns a verbose string including the error message 80 | // and a representation of the stack trace suitable for 81 | // printing. 82 | Detail(w io.Writer) error 83 | 84 | // Returns the error code for this error. 85 | Code() ErrorCode 86 | } 87 | -------------------------------------------------------------------------------- /libcontainer/factory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import ( 18 | "github.com/nabla-containers/runnc/libcontainer/configs" 19 | ) 20 | 21 | type Factory interface { 22 | // Creates a new container with the given id and starts the initial process inside it. 23 | // id must be a string containing only letters, digits and underscores and must contain 24 | // between 1 and 1024 characters, inclusive. 25 | // 26 | // The id must not already be in use by an existing container. Containers created using 27 | // a factory with the same path (and file system) must have distinct ids. 28 | // 29 | // Returns the new container with a running process. 30 | // 31 | // errors: 32 | // IdInUse - id is already in use by a container 33 | // InvalidIdFormat - id has incorrect format 34 | // ConfigInvalid - config is invalid 35 | // Systemerror - System error 36 | // 37 | // On error, any partially created container parts are cleaned up (the operation is atomic). 38 | Create(id string, config *configs.Config) (Container, error) 39 | 40 | // Load takes an ID for an existing container and returns the container information 41 | // from the state. This presents a read only view of the container. 42 | // 43 | // errors: 44 | // Path does not exist 45 | // Container is stopped 46 | // System error 47 | Load(id string) (Container, error) 48 | 49 | // StartInitialization is an internal API to libcontainer used during the reexec of the 50 | // container. 51 | // 52 | // Errors: 53 | // Pipe connection error 54 | // System error 55 | StartInitialization() error 56 | 57 | // Type returns info string about factory type (e.g. lxc, libcontainer...) 58 | Type() string 59 | } 60 | -------------------------------------------------------------------------------- /libcontainer/factory_nabla.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build linux 16 | 17 | package libcontainer 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "os" 23 | "path/filepath" 24 | "regexp" 25 | "syscall" 26 | 27 | "github.com/nabla-containers/runnc/libcontainer/configs" 28 | ll "github.com/nabla-containers/runnc/llif" 29 | 30 | "github.com/pkg/errors" 31 | ) 32 | 33 | const ( 34 | stateFilename = "state.json" 35 | execFifoFilename = "exec.fifo" 36 | pauseNablaName = "pause.nabla" 37 | ) 38 | 39 | var ( 40 | idRegex = regexp.MustCompile(`^[\w+-\.]+$`) 41 | maxIdLen = 1024 42 | ) 43 | 44 | // New returns a linux based container factory based in the root directory and 45 | // configures the factory with the provided option funcs. 46 | func New(root string, llcHandler ll.RunllcHandler, options ...func(*NablaFactory) error) (Factory, error) { 47 | if root != "" { 48 | if err := os.MkdirAll(root, 0700); err != nil { 49 | return nil, err 50 | } 51 | } 52 | l := &NablaFactory{ 53 | Root: root, 54 | LLCHandler: llcHandler, 55 | } 56 | 57 | for _, opt := range options { 58 | if err := opt(l); err != nil { 59 | return nil, err 60 | } 61 | } 62 | return l, nil 63 | } 64 | 65 | // LinuxFactory implements the default factory interface for linux based systems. 66 | type NablaFactory struct { 67 | // Root directory for the factory to store state. 68 | Root string 69 | // LLCHandler is the set of low level container handlers 70 | LLCHandler ll.RunllcHandler 71 | } 72 | 73 | func isPauseContainer(config *configs.Config) bool { 74 | return len(config.Args) == 1 && config.Args[0] == "/pause" 75 | } 76 | 77 | func applyPauseHack(config *configs.Config, containerRoot string) (*configs.Config, error) { 78 | if !isPauseContainer(config) { 79 | return nil, errors.New("Trying to make pause changes on non-pause container") 80 | } 81 | 82 | config.Args = []string{pauseNablaName} 83 | return config, nil 84 | } 85 | 86 | // nablaTapName returns the tapname of a given container ID 87 | func nablaTapName(id string) string { 88 | if len(id) < 8 { 89 | panic("Insufficient uniqueness in ID") 90 | } 91 | return ("tap" + id)[:syscall.IFNAMSIZ-1] 92 | } 93 | 94 | func (l *NablaFactory) Create(id string, config *configs.Config) (Container, error) { 95 | if l.Root == "" { 96 | return nil, fmt.Errorf("invalid root") 97 | } 98 | if err := l.validateID(id); err != nil { 99 | return nil, err 100 | } 101 | 102 | //if err := l.Validator.Validate(config); err != nil { 103 | // return nil, err 104 | //} 105 | uid, err := config.HostUID() 106 | if err != nil { 107 | return nil, err 108 | } 109 | gid, err := config.HostGID() 110 | if err != nil { 111 | return nil, err 112 | } 113 | containerRoot := filepath.Join(l.Root, id) 114 | if _, err := os.Stat(containerRoot); err == nil { 115 | return nil, fmt.Errorf("container with id exists: %v", id) 116 | } else if !os.IsNotExist(err) { 117 | return nil, err 118 | } 119 | if err := os.MkdirAll(containerRoot, 0711); err != nil { 120 | return nil, err 121 | } 122 | 123 | if err := os.Chown(containerRoot, uid, gid); err != nil { 124 | return nil, err 125 | } 126 | fifoName := filepath.Join(containerRoot, execFifoFilename) 127 | oldMask := syscall.Umask(0000) 128 | if err := syscall.Mkfifo(fifoName, 0622); err != nil { 129 | syscall.Umask(oldMask) 130 | return nil, err 131 | } 132 | syscall.Umask(oldMask) 133 | if err := os.Chown(fifoName, uid, gid); err != nil { 134 | return nil, err 135 | } 136 | 137 | var fsState *ll.LLState 138 | // If it is a pause container for kubernetes, set config so that init 139 | // will just pause instead of executing a nabla 140 | if isPauseContainer(config) { 141 | config, err = applyPauseHack(config, containerRoot) 142 | if err != nil { 143 | return nil, err 144 | } 145 | } else { 146 | fsInput := &ll.FsCreateInput{ 147 | ll.FsGenericInput{ 148 | ContainerRoot: containerRoot, 149 | Config: config, 150 | ContainerId: id, 151 | FsState: &ll.LLState{}, 152 | NetworkState: &ll.LLState{}, 153 | ExecState: &ll.LLState{}, 154 | }, 155 | } 156 | 157 | fsState, err = l.LLCHandler.FsH.FsCreateFunc(fsInput) 158 | if err != nil { 159 | return nil, fmt.Errorf("Error running FsCreateFunc: %v", err) 160 | } 161 | } 162 | 163 | networkInput := &ll.NetworkCreateInput{ 164 | ll.NetworkGenericInput{ 165 | ContainerRoot: containerRoot, 166 | Config: config, 167 | ContainerId: id, 168 | FsState: fsState, 169 | NetworkState: &ll.LLState{}, 170 | ExecState: &ll.LLState{}, 171 | }, 172 | } 173 | 174 | networkState, err := l.LLCHandler.NetworkH.NetworkCreateFunc(networkInput) 175 | if err != nil { 176 | // TODO(runllc): Handle error case for Fs Handler - run FsDestroyFunc 177 | return nil, fmt.Errorf("Error running NetworkCreateFunc: %v", err) 178 | } 179 | 180 | execInput := &ll.ExecCreateInput{ 181 | ll.ExecGenericInput{ 182 | ContainerRoot: containerRoot, 183 | Config: config, 184 | ContainerId: id, 185 | FsState: fsState, 186 | NetworkState: networkState, 187 | ExecState: &ll.LLState{}, 188 | }, 189 | } 190 | 191 | execState, err := l.LLCHandler.ExecH.ExecCreateFunc(execInput) 192 | if err != nil { 193 | // TODO(runllc): Handle error case for Fs Handler - run FsDestroyFunc 194 | // TODO(runllc): Handle error case for Net Handler - run NetDestroyFunc 195 | return nil, fmt.Errorf("Error running ExecCreateFunc: %v", err) 196 | } 197 | 198 | if networkState == nil { 199 | networkState = &ll.LLState{} 200 | } 201 | if fsState == nil { 202 | fsState = &ll.LLState{} 203 | } 204 | if execState == nil { 205 | execState = &ll.LLState{} 206 | } 207 | 208 | c := &nablaContainer{ 209 | id: id, 210 | root: containerRoot, 211 | config: config, 212 | llcHandler: l.LLCHandler, 213 | state: &State{ 214 | BaseState: BaseState{ 215 | ID: id, 216 | Config: *config, 217 | }, 218 | FsState: *fsState, 219 | NetworkState: *networkState, 220 | ExecState: *execState, 221 | Status: Stopped, 222 | }, 223 | } 224 | return c, nil 225 | } 226 | 227 | func (l *NablaFactory) Load(id string) (Container, error) { 228 | if l.Root == "" { 229 | return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid) 230 | } 231 | containerRoot := filepath.Join(l.Root, id) 232 | state, err := l.loadState(containerRoot, id) 233 | if err != nil { 234 | return nil, err 235 | } 236 | 237 | c := &nablaContainer{ 238 | id: id, 239 | root: containerRoot, 240 | config: &state.Config, 241 | state: state, 242 | llcHandler: l.LLCHandler, 243 | } 244 | 245 | return c, nil 246 | } 247 | 248 | func (l *NablaFactory) StartInitialization() error { 249 | return initNabla(l.LLCHandler) 250 | } 251 | 252 | func (l *NablaFactory) Type() string { 253 | return "nabla" 254 | } 255 | 256 | func (l *NablaFactory) validateID(id string) error { 257 | if !idRegex.MatchString(id) { 258 | return fmt.Errorf("invalid id format: %v", id) 259 | } 260 | if len(id) > maxIdLen { 261 | return fmt.Errorf("id length: %v, greater than max length: %v", len(id), maxIdLen) 262 | } 263 | return nil 264 | } 265 | 266 | func (l *NablaFactory) loadState(root, id string) (*State, error) { 267 | f, err := os.Open(filepath.Join(root, stateFilename)) 268 | if err != nil { 269 | if os.IsNotExist(err) { 270 | return nil, newGenericError(fmt.Errorf("container %q does not exist", id), ContainerNotExists) 271 | } 272 | return nil, newGenericError(err, SystemError) 273 | } 274 | defer f.Close() 275 | var state *State 276 | if err := json.NewDecoder(f).Decode(&state); err != nil { 277 | return nil, newGenericError(err, SystemError) 278 | } 279 | return state, nil 280 | } 281 | -------------------------------------------------------------------------------- /libcontainer/generic_error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "text/template" 21 | "time" 22 | 23 | "github.com/opencontainers/runc/libcontainer/stacktrace" 24 | ) 25 | 26 | type syncType uint8 27 | 28 | const ( 29 | procReady syncType = iota 30 | procError 31 | procRun 32 | procHooks 33 | procResume 34 | ) 35 | 36 | type syncT struct { 37 | Type syncType `json:"type"` 38 | } 39 | 40 | var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} 41 | Code: {{.ECode}} 42 | {{if .Message }} 43 | Message: {{.Message}} 44 | {{end}} 45 | Frames:{{range $i, $frame := .Stack.Frames}} 46 | --- 47 | {{$i}}: {{$frame.Function}} 48 | Package: {{$frame.Package}} 49 | File: {{$frame.File}}@{{$frame.Line}}{{end}} 50 | `)) 51 | 52 | func newGenericError(err error, c ErrorCode) Error { 53 | if le, ok := err.(Error); ok { 54 | return le 55 | } 56 | gerr := &genericError{ 57 | Timestamp: time.Now(), 58 | Err: err, 59 | ECode: c, 60 | Stack: stacktrace.Capture(1), 61 | } 62 | if err != nil { 63 | gerr.Message = err.Error() 64 | } 65 | return gerr 66 | } 67 | 68 | func newSystemError(err error) Error { 69 | return createSystemError(err, "") 70 | } 71 | 72 | func newSystemErrorWithCausef(err error, cause string, v ...interface{}) Error { 73 | return createSystemError(err, fmt.Sprintf(cause, v...)) 74 | } 75 | 76 | func newSystemErrorWithCause(err error, cause string) Error { 77 | return createSystemError(err, cause) 78 | } 79 | 80 | // createSystemError creates the specified error with the correct number of 81 | // stack frames skipped. This is only to be called by the other functions for 82 | // formatting the error. 83 | func createSystemError(err error, cause string) Error { 84 | gerr := &genericError{ 85 | Timestamp: time.Now(), 86 | Err: err, 87 | ECode: SystemError, 88 | Cause: cause, 89 | Stack: stacktrace.Capture(2), 90 | } 91 | if err != nil { 92 | gerr.Message = err.Error() 93 | } 94 | return gerr 95 | } 96 | 97 | type genericError struct { 98 | Timestamp time.Time 99 | ECode ErrorCode 100 | Err error `json:"-"` 101 | Cause string 102 | Message string 103 | Stack stacktrace.Stacktrace 104 | } 105 | 106 | func (e *genericError) Error() string { 107 | if e.Cause == "" { 108 | return e.Message 109 | } 110 | frame := e.Stack.Frames[0] 111 | return fmt.Sprintf("%s:%d: %s caused %q", frame.File, frame.Line, e.Cause, e.Message) 112 | } 113 | 114 | func (e *genericError) Code() ErrorCode { 115 | return e.ECode 116 | } 117 | 118 | func (e *genericError) Detail(w io.Writer) error { 119 | return errorTemplate.Execute(w, e) 120 | } 121 | -------------------------------------------------------------------------------- /libcontainer/hooks.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | spec "github.com/opencontainers/runtime-spec/specs-go" 8 | "os" 9 | "os/exec" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | func runHook(hook spec.Hook, cid, bundlePath string) error { 15 | // Adapted from: 16 | // github.com/kata-containers/runtime/cli/hook.go 17 | state := spec.State{ 18 | Pid: os.Getpid(), 19 | Bundle: bundlePath, 20 | ID: cid, 21 | } 22 | 23 | stateJSON, err := json.Marshal(state) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | var stdout, stderr bytes.Buffer 29 | cmd := &exec.Cmd{ 30 | Path: hook.Path, 31 | Args: hook.Args, 32 | Env: hook.Env, 33 | Stdin: bytes.NewReader(stateJSON), 34 | Stdout: &stdout, 35 | Stderr: &stderr, 36 | } 37 | 38 | if err := cmd.Start(); err != nil { 39 | return err 40 | } 41 | 42 | if hook.Timeout == nil { 43 | if err := cmd.Wait(); err != nil { 44 | return fmt.Errorf("%s: stdout: %s, stderr: %s", err, stdout.String(), stderr.String()) 45 | } 46 | } else { 47 | done := make(chan error, 1) 48 | go func() { 49 | done <- cmd.Wait() 50 | close(done) 51 | }() 52 | 53 | select { 54 | case err := <-done: 55 | if err != nil { 56 | return fmt.Errorf("%s: stdout: %s, stderr: %s", err, stdout.String(), stderr.String()) 57 | } 58 | case <-time.After(time.Duration(*hook.Timeout) * time.Second): 59 | if err := syscall.Kill(cmd.Process.Pid, syscall.SIGKILL); err != nil { 60 | return err 61 | } 62 | 63 | return fmt.Errorf("Hook timeout") 64 | } 65 | } 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /libcontainer/init_nabla.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "os" 21 | "strconv" 22 | "syscall" 23 | 24 | "github.com/nabla-containers/runnc/libcontainer/configs" 25 | ll "github.com/nabla-containers/runnc/llif" 26 | spec "github.com/opencontainers/runtime-spec/specs-go" 27 | "github.com/vishvananda/netns" 28 | ) 29 | 30 | type initConfig struct { 31 | Id string `json:"id"` 32 | BundlePath string `json:"bundlepath"` 33 | Root string `json:"root"` 34 | Args []string `json:"args"` 35 | Cwd string `json:"cwd"` 36 | Env []string `json:"env"` 37 | TapName string `json:"tap"` 38 | NetnsPath string `json:"netnspath"` 39 | Hooks *spec.Hooks `json:"hooks"` 40 | Memory int64 `json:"mem"` 41 | Mounts []spec.Mount `json:"Mounts"` 42 | Config *configs.Config `json:"config"` 43 | 44 | FsState ll.LLState `json:"fsstate"` 45 | NetworkState ll.LLState `json:"netstate"` 46 | ExecState ll.LLState `json:execstate"` 47 | } 48 | 49 | func initNabla(llcHandler ll.RunllcHandler) error { 50 | var ( 51 | pipefd, rootfd int 52 | envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE") 53 | envStateDir = os.Getenv("_LIBCONTAINER_STATEDIR") 54 | ) 55 | 56 | // Get the INITPIPE. 57 | pipefd, err := strconv.Atoi(envInitPipe) 58 | if err != nil { 59 | return fmt.Errorf("unable to convert _LIBCONTAINER_INITPIPE=%s to int: %s", envInitPipe, err) 60 | } 61 | 62 | pipe := os.NewFile(uintptr(pipefd), "pipe") 63 | defer pipe.Close() 64 | 65 | var config *initConfig 66 | if err := json.NewDecoder(pipe).Decode(&config); err != nil { 67 | return err 68 | } 69 | 70 | // Only init processes have STATEDIR. 71 | if rootfd, err = strconv.Atoi(envStateDir); err != nil { 72 | return fmt.Errorf("unable to convert _LIBCONTAINER_STATEDIR=%s to int: %s", envStateDir, err) 73 | } 74 | 75 | // clear the current process's environment to clean any libcontainer 76 | // specific env vars. 77 | os.Clearenv() 78 | 79 | // LLC Fs Handle 80 | fsInput := &ll.FsRunInput{ 81 | ll.FsGenericInput{ 82 | ContainerRoot: config.Root, 83 | Config: config.Config, 84 | ContainerId: config.Id, 85 | FsState: &config.FsState, 86 | NetworkState: &config.NetworkState, 87 | ExecState: &config.ExecState, 88 | }, 89 | } 90 | 91 | // TODO(runllc): Propagate and store LLstates 92 | fsState, err := llcHandler.FsH.FsRunFunc(fsInput) 93 | if err != nil { 94 | return fmt.Errorf("Error running llc Fs handler: %v", err) 95 | } 96 | 97 | // Go into network namespace for temporary hack for CNI plugin using veth pairs 98 | // K8s case 99 | if config.NetnsPath != "" { 100 | nsh, err := netns.GetFromPath(config.NetnsPath) 101 | if err != nil { 102 | return newSystemErrorWithCause(err, "unable to get netns handle") 103 | } 104 | 105 | if err := netns.Set(nsh); err != nil { 106 | return newSystemErrorWithCause(err, "unable to get set netns") 107 | } 108 | } else { 109 | // Docker case for docker cli 110 | // TODO: case on specific --docker-cli flag 111 | nsh, err := netns.New() 112 | if err != nil { 113 | return newSystemErrorWithCause(err, "unable to create netns handle") 114 | } 115 | 116 | if err := netns.Set(nsh); err != nil { 117 | return newSystemErrorWithCause(err, "unable to get set netns") 118 | } 119 | } 120 | if config.Hooks != nil { 121 | for _, hook := range config.Hooks.Prestart { 122 | if err := runHook(hook, config.Id, config.BundlePath); err != nil { 123 | return newSystemErrorWithCause(err, "unable to run prestart hook") 124 | } 125 | } 126 | } 127 | 128 | networkInput := &ll.NetworkRunInput{ 129 | ll.NetworkGenericInput{ 130 | ContainerRoot: config.Root, 131 | Config: config.Config, 132 | ContainerId: config.Id, 133 | FsState: fsState, 134 | NetworkState: &config.NetworkState, 135 | ExecState: &config.ExecState, 136 | }, 137 | } 138 | 139 | // TODO(runllc): Propagate and store LLstates 140 | networkState, err := llcHandler.NetworkH.NetworkRunFunc(networkInput) 141 | if err != nil { 142 | fmt.Fprintf(os.Stderr, "Error running llc Network handler: %v", err) 143 | return fmt.Errorf("Error running llc Network handler: %v", err) 144 | } 145 | 146 | // wait for the fifo to be opened on the other side before 147 | // exec'ing the users process. 148 | fd, err := syscall.Openat(rootfd, execFifoFilename, os.O_WRONLY|syscall.O_CLOEXEC, 0) 149 | if err != nil { 150 | return newSystemErrorWithCause(err, "openat exec fifo") 151 | } 152 | if _, err := syscall.Write(fd, []byte("0")); err != nil { 153 | return newSystemErrorWithCause(err, "write 0 exec fifo") 154 | } 155 | syscall.Close(fd) 156 | syscall.Close(rootfd) 157 | 158 | // Check if it is a pause container, if it is, just pause 159 | if len(config.Args) == 1 && config.Args[0] == pauseNablaName { 160 | select {} 161 | } 162 | 163 | // LLC Exec Handle 164 | execInput := &ll.ExecRunInput{ 165 | ll.ExecGenericInput{ 166 | ContainerRoot: config.Root, 167 | Config: config.Config, 168 | ContainerId: config.Id, 169 | FsState: fsState, 170 | NetworkState: networkState, 171 | ExecState: &config.ExecState, 172 | }, 173 | } 174 | 175 | // Should not return if successful 176 | return llcHandler.ExecH.ExecRunFunc(execInput) 177 | } 178 | -------------------------------------------------------------------------------- /libcontainer/process.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "math" 21 | "os" 22 | ) 23 | 24 | type processOperations interface { 25 | wait() (*os.ProcessState, error) 26 | signal(sig os.Signal) error 27 | pid() int 28 | } 29 | 30 | // Process specifies the configuration and IO for a process inside 31 | // a container. 32 | type Process struct { 33 | // The command to be run followed by any arguments. 34 | Args []string 35 | 36 | // Env specifies the environment variables for the process. 37 | Env []string 38 | 39 | // User will set the uid and gid of the executing process running inside the container 40 | // local to the container's user and group configuration. 41 | User string 42 | 43 | // AdditionalGroups specifies the gids that should be added to supplementary groups 44 | // in addition to those that the user belongs to. 45 | AdditionalGroups []string 46 | 47 | // Cwd will change the processes current working directory inside the container's rootfs. 48 | Cwd string 49 | 50 | // Stdin is a pointer to a reader which provides the standard input stream. 51 | Stdin io.Reader 52 | 53 | // Stdout is a pointer to a writer which receives the standard output stream. 54 | Stdout io.Writer 55 | 56 | // Stderr is a pointer to a writer which receives the standard error stream. 57 | Stderr io.Writer 58 | 59 | // ExtraFiles specifies additional open files to be inherited by the container 60 | ExtraFiles []*os.File 61 | 62 | // consolePath is the path to the console allocated to the container. 63 | consolePath string 64 | 65 | // Capabilities specify the capabilities to keep when executing the process inside the container 66 | // All capabilities not specified will be dropped from the processes capability mask 67 | Capabilities []string 68 | 69 | // AppArmorProfile specifies the profile to apply to the process and is 70 | // changed at the time the process is execed 71 | AppArmorProfile string 72 | 73 | // Label specifies the label to apply to the process. It is commonly used by selinux 74 | Label string 75 | 76 | // NoNewPrivileges controls whether processes can gain additional privileges. 77 | NoNewPrivileges *bool 78 | 79 | // Rlimits specifies the resource limits, such as max open files, to set in the container 80 | // If Rlimits are not set, the container will inherit rlimits from the parent process 81 | //Rlimits []configs.Rlimit 82 | 83 | ops processOperations 84 | 85 | // OOMScoreAdj sets the value of the processes oom_score_adj. 86 | OOMScoreAdj *int 87 | } 88 | 89 | // Wait waits for the process to exit. 90 | // Wait releases any resources associated with the Process 91 | func (p Process) Wait() (*os.ProcessState, error) { 92 | if p.ops == nil { 93 | return nil, fmt.Errorf("invalid process") 94 | } 95 | return p.ops.wait() 96 | } 97 | 98 | // Pid returns the process ID 99 | func (p Process) Pid() (int, error) { 100 | // math.MinInt32 is returned here, because it's invalid value 101 | // for the kill() system call. 102 | if p.ops == nil { 103 | return math.MinInt32, fmt.Errorf("invalid process") 104 | } 105 | return p.ops.pid(), nil 106 | } 107 | 108 | // Signal sends a signal to the Process. 109 | func (p Process) Signal(sig os.Signal) error { 110 | if p.ops == nil { 111 | return fmt.Errorf("invalid process") 112 | } 113 | return p.ops.signal(sig) 114 | } 115 | 116 | // IO holds the process's STDIO 117 | type IO struct { 118 | Stdin io.WriteCloser 119 | Stdout io.ReadCloser 120 | Stderr io.ReadCloser 121 | } 122 | 123 | // NewConsole creates new console for process and returns it 124 | func (p *Process) NewConsole(rootuid, rootgid int) (Console, error) { 125 | console, err := NewConsole(rootuid, rootgid) 126 | if err != nil { 127 | return nil, err 128 | } 129 | p.consolePath = console.Path() 130 | return console, nil 131 | } 132 | 133 | // ConsoleFromPath sets the process's console with the path provided 134 | func (p *Process) ConsoleFromPath(path string) error { 135 | if p.consolePath != "" { 136 | return fmt.Errorf("console path already exists for process") 137 | } 138 | p.consolePath = path 139 | return nil 140 | } 141 | -------------------------------------------------------------------------------- /libcontainer/process_nabla.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | import ( 18 | "os" 19 | "syscall" 20 | ) 21 | 22 | // InitializeIO creates pipes for use with the process's STDIO 23 | // and returns the opposite side for each 24 | func (p *Process) InitializeIO(rootuid, rootgid int) (i *IO, err error) { 25 | var fds []uintptr 26 | i = &IO{} 27 | // cleanup in case of an error 28 | defer func() { 29 | if err != nil { 30 | for _, fd := range fds { 31 | syscall.Close(int(fd)) 32 | } 33 | } 34 | }() 35 | // STDIN 36 | r, w, err := os.Pipe() 37 | if err != nil { 38 | return nil, err 39 | } 40 | fds = append(fds, r.Fd(), w.Fd()) 41 | p.Stdin, i.Stdin = r, w 42 | // STDOUT 43 | if r, w, err = os.Pipe(); err != nil { 44 | return nil, err 45 | } 46 | fds = append(fds, r.Fd(), w.Fd()) 47 | p.Stdout, i.Stdout = w, r 48 | // STDERR 49 | if r, w, err = os.Pipe(); err != nil { 50 | return nil, err 51 | } 52 | fds = append(fds, r.Fd(), w.Fd()) 53 | p.Stderr, i.Stderr = w, r 54 | // change ownership of the pipes incase we are in a user namespace 55 | for _, fd := range fds { 56 | if err := syscall.Fchown(int(fd), rootuid, rootgid); err != nil { 57 | return nil, err 58 | } 59 | } 60 | return i, nil 61 | } 62 | -------------------------------------------------------------------------------- /libcontainer/stats.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package libcontainer 16 | 17 | // Stats contains statistics about the container 18 | type Stats struct { 19 | } 20 | -------------------------------------------------------------------------------- /llcli/create.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "os" 19 | 20 | ll "github.com/nabla-containers/runnc/llif" 21 | "github.com/urfave/cli" 22 | ) 23 | 24 | func newCreateCmd(llcHandler ll.RunllcHandler, sf stringSubFunc) cli.Command { 25 | return cli.Command{ 26 | Name: "create", 27 | Usage: "create a container", 28 | ArgsUsage: sf(` 29 | 30 | Where "" is your name for the instance of the container that you 31 | are starting. The name you provide for the container instance must be unique on 32 | your host.`), 33 | Description: sf(`The create command creates an instance of a container for a bundle. The bundle 34 | is a directory with a specification file named "` + specConfig + `" and a root 35 | filesystem. 36 | 37 | The specification file includes an args parameter. The args parameter is used 38 | to specify command(s) that get run when the container is started. 39 | `), 40 | Flags: []cli.Flag{ 41 | cli.StringFlag{ 42 | Name: "bundle, b", 43 | Value: "", 44 | Usage: `path to the root of the bundle directory, defaults to the current directory`, 45 | }, 46 | cli.StringFlag{ 47 | Name: "console-socket", 48 | Value: "", 49 | Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal", 50 | }, 51 | cli.StringFlag{ 52 | Name: "pid-file", 53 | Value: "", 54 | Usage: "specify the file to write the process id to", 55 | }, 56 | cli.BoolFlag{ 57 | Name: "no-pivot", 58 | Usage: "do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk", 59 | }, 60 | cli.BoolFlag{ 61 | Name: "no-new-keyring", 62 | Usage: "do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key", 63 | }, 64 | cli.IntFlag{ 65 | Name: "preserve-fds", 66 | Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)", 67 | }, 68 | }, 69 | Action: func(context *cli.Context) error { 70 | // TODO: Implement 71 | spec, err := setupSpec(context) 72 | if err != nil { 73 | fatal(err) 74 | } 75 | 76 | status, err := startContainer(context, llcHandler, spec, true) 77 | if err != nil { 78 | fatal(err) 79 | } 80 | 81 | // exit with the container's exit status so any external supervisor is 82 | // notified of the exit with the correct exit status. 83 | os.Exit(status) 84 | return nil 85 | }, 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /llcli/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | "syscall" 22 | "time" 23 | 24 | "github.com/nabla-containers/runnc/libcontainer" 25 | ll "github.com/nabla-containers/runnc/llif" 26 | "github.com/urfave/cli" 27 | ) 28 | 29 | func newDeleteCmd(llcHandler ll.RunllcHandler, sf stringSubFunc) cli.Command { 30 | return cli.Command{ 31 | Name: "delete", 32 | Usage: "delete any resources held by the container often used with detached container", 33 | ArgsUsage: sf(` 34 | 35 | Where "" is the name for the instance of the container. 36 | 37 | EXAMPLE: 38 | For example, if the container id is "ubuntu01" and {{name}} list currently shows the 39 | status of "ubuntu01" as "stopped" the following will delete resources held for 40 | "ubuntu01" removing "ubuntu01" from the {{name}} list of containers: 41 | 42 | # {{name}} delete ubuntu01`), 43 | Flags: []cli.Flag{ 44 | cli.BoolFlag{ 45 | Name: "force, f", 46 | Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)", 47 | }, 48 | }, 49 | Action: func(context *cli.Context) error { 50 | id := context.Args().First() 51 | container, err := getContainer(context, llcHandler) 52 | if err != nil { 53 | if lerr, ok := err.(libcontainer.Error); ok && lerr.Code() == libcontainer.ContainerNotExists { 54 | // if there was an aborted start or something of the sort then the container's directory could exist but 55 | // libcontainer does not see it because the state.json file inside that directory was never created. 56 | path := filepath.Join(context.GlobalString("root"), id) 57 | if e := os.RemoveAll(path); e != nil { 58 | // fmt.Fprintf(os.Stderr, "remove %s: %v\n", path, e) 59 | } 60 | } 61 | return err 62 | } 63 | s, err := container.Status() 64 | if err != nil { 65 | return err 66 | } 67 | switch s { 68 | case libcontainer.Stopped: 69 | destroy(container) 70 | case libcontainer.Created: 71 | return killContainer(container) 72 | default: 73 | if context.Bool("force") { 74 | return killContainer(container) 75 | } 76 | return fmt.Errorf("cannot delete container %s that is not stopped: %s\n", id, s) 77 | } 78 | 79 | return nil 80 | }, 81 | } 82 | } 83 | 84 | func killContainer(container libcontainer.Container) error { 85 | _ = container.Signal(syscall.SIGKILL, false) 86 | for i := 0; i < 100; i++ { 87 | time.Sleep(100 * time.Millisecond) 88 | if err := container.Signal(syscall.Signal(0), false); err != nil { 89 | destroy(container) 90 | return nil 91 | } 92 | } 93 | return fmt.Errorf("container init still running") 94 | } 95 | -------------------------------------------------------------------------------- /llcli/exec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/urfave/cli" 21 | ) 22 | 23 | func newExecCmd() cli.Command { 24 | return cli.Command{ 25 | Name: "exec", 26 | Usage: "execute new process inside the container", 27 | ArgsUsage: ` [command options] || -p process.json 28 | 29 | Where "" is the name for the instance of the container and 30 | "" is the command to be executed in the container. 31 | "" can't be empty unless a "-p" flag provided. 32 | 33 | EXAMPLE: 34 | For example, if the container is configured to run the linux ps command the 35 | following will output a list of processes running in the container: 36 | 37 | # {{name}} exec ps`, 38 | Flags: []cli.Flag{ 39 | cli.StringFlag{ 40 | Name: "console-socket", 41 | Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal", 42 | }, 43 | cli.StringFlag{ 44 | Name: "cwd", 45 | Usage: "current working directory in the container", 46 | }, 47 | cli.StringSliceFlag{ 48 | Name: "env, e", 49 | Usage: "set environment variables", 50 | }, 51 | cli.BoolFlag{ 52 | Name: "tty, t", 53 | Usage: "allocate a pseudo-TTY", 54 | }, 55 | cli.StringFlag{ 56 | Name: "user, u", 57 | Usage: "UID (format: [:])", 58 | }, 59 | cli.Int64SliceFlag{ 60 | Name: "additional-gids, g", 61 | Usage: "additional gids", 62 | }, 63 | cli.StringFlag{ 64 | Name: "process, p", 65 | Usage: "path to the process.json", 66 | }, 67 | cli.BoolFlag{ 68 | Name: "detach,d", 69 | Usage: "detach from the container's process", 70 | }, 71 | cli.StringFlag{ 72 | Name: "pid-file", 73 | Value: "", 74 | Usage: "specify the file to write the process id to", 75 | }, 76 | cli.StringFlag{ 77 | Name: "process-label", 78 | Usage: "set the asm process label for the process commonly used with selinux", 79 | }, 80 | cli.StringFlag{ 81 | Name: "apparmor", 82 | Usage: "set the apparmor profile for the process", 83 | }, 84 | cli.BoolFlag{ 85 | Name: "no-new-privs", 86 | Usage: "set the no new privileges value for the process", 87 | }, 88 | cli.StringSliceFlag{ 89 | Name: "cap, c", 90 | Value: &cli.StringSlice{}, 91 | Usage: "add a capability to the bounding set for the process", 92 | }, 93 | cli.BoolFlag{ 94 | Name: "no-subreaper", 95 | Usage: "disable the use of the subreaper used to reap reparented processes", 96 | Hidden: true, 97 | }, 98 | }, 99 | Action: func(context *cli.Context) error { 100 | // TODO: implement 101 | return fmt.Errorf("OCI Exec Not Implemented") 102 | }, 103 | SkipArgReorder: true, 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /llcli/init.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build linux 16 | 17 | package llcli 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "runtime" 23 | 24 | "github.com/nabla-containers/runnc/libcontainer" 25 | ll "github.com/nabla-containers/runnc/llif" 26 | "github.com/urfave/cli" 27 | ) 28 | 29 | func init() { 30 | if len(os.Args) > 1 && os.Args[1] == "init" { 31 | runtime.GOMAXPROCS(1) 32 | runtime.LockOSThread() 33 | } 34 | } 35 | 36 | func newInitCmd(llcHandler ll.RunllcHandler, sf stringSubFunc) cli.Command { 37 | return cli.Command{ 38 | Name: "init", 39 | Usage: sf(`initialize the namespaces and launch the process (do not call it outside of {{name}})`), 40 | Action: func(context *cli.Context) error { 41 | factory, _ := libcontainer.New("", llcHandler) 42 | if err := factory.StartInitialization(); err != nil { 43 | // as the error is sent back to the parent there is no need to log 44 | // or write it to stderr because the parent process will handle this 45 | fmt.Fprintf(os.Stderr, "ERR: %v", err) 46 | fmt.Fprintf(os.Stdout, "ERR: %v", err) 47 | os.Exit(1) 48 | } 49 | panic("libcontainer: container init failed to exec") 50 | }, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /llcli/kill.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build linux 16 | 17 | package llcli 18 | 19 | import ( 20 | "fmt" 21 | "github.com/urfave/cli" 22 | "strconv" 23 | "strings" 24 | "syscall" 25 | 26 | ll "github.com/nabla-containers/runnc/llif" 27 | ) 28 | 29 | var signalMap = map[string]syscall.Signal{ 30 | "ABRT": syscall.SIGABRT, 31 | "ALRM": syscall.SIGALRM, 32 | "BUS": syscall.SIGBUS, 33 | "CHLD": syscall.SIGCHLD, 34 | "CLD": syscall.SIGCLD, 35 | "CONT": syscall.SIGCONT, 36 | "FPE": syscall.SIGFPE, 37 | "HUP": syscall.SIGHUP, 38 | "ILL": syscall.SIGILL, 39 | "INT": syscall.SIGINT, 40 | "IO": syscall.SIGIO, 41 | "IOT": syscall.SIGIOT, 42 | "KILL": syscall.SIGKILL, 43 | "PIPE": syscall.SIGPIPE, 44 | "POLL": syscall.SIGPOLL, 45 | "PROF": syscall.SIGPROF, 46 | "PWR": syscall.SIGPWR, 47 | "QUIT": syscall.SIGQUIT, 48 | "SEGV": syscall.SIGSEGV, 49 | "STKFLT": syscall.SIGSTKFLT, 50 | "STOP": syscall.SIGSTOP, 51 | "SYS": syscall.SIGSYS, 52 | "TERM": syscall.SIGTERM, 53 | "TRAP": syscall.SIGTRAP, 54 | "TSTP": syscall.SIGTSTP, 55 | "TTIN": syscall.SIGTTIN, 56 | "TTOU": syscall.SIGTTOU, 57 | "UNUSED": syscall.SIGUNUSED, 58 | "URG": syscall.SIGURG, 59 | "USR1": syscall.SIGUSR1, 60 | "USR2": syscall.SIGUSR2, 61 | "VTALRM": syscall.SIGVTALRM, 62 | "WINCH": syscall.SIGWINCH, 63 | "XCPU": syscall.SIGXCPU, 64 | "XFSZ": syscall.SIGXFSZ, 65 | } 66 | 67 | func newKillCmd(llcHandler ll.RunllcHandler, sf stringSubFunc) cli.Command { 68 | return cli.Command{ 69 | Name: "kill", 70 | Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process", 71 | ArgsUsage: sf(` [signal] 72 | 73 | Where "" is the name for the instance of the container and 74 | "[signal]" is the signal to be sent to the init process. 75 | 76 | EXAMPLE: 77 | For example, if the container id is "ubuntu01" the following will send a "KILL" 78 | signal to the init process of the "ubuntu01" container: 79 | 80 | # {{name}} kill ubuntu01 KILL`), 81 | Flags: []cli.Flag{ 82 | cli.BoolFlag{ 83 | Name: "all, a", 84 | Usage: "send the specified signal to all processes inside the container", 85 | }, 86 | }, 87 | Action: func(context *cli.Context) error { 88 | container, err := getContainer(context, llcHandler) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | sigstr := context.Args().Get(1) 94 | if sigstr == "" { 95 | sigstr = "SIGTERM" 96 | } 97 | 98 | signal, err := parseSignal(sigstr) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | // We ignore the all flag since we are using an older version of 104 | // runc libcontainer libraries and we only have a single process 105 | if err := container.Signal(signal, context.Bool("all")); err != nil { 106 | return err 107 | } 108 | return nil 109 | }, 110 | } 111 | } 112 | 113 | func parseSignal(rawSignal string) (syscall.Signal, error) { 114 | s, err := strconv.Atoi(rawSignal) 115 | if err == nil { 116 | sig := syscall.Signal(s) 117 | for _, msig := range signalMap { 118 | if sig == msig { 119 | return sig, nil 120 | } 121 | } 122 | return -1, fmt.Errorf("unknown signal %q", rawSignal) 123 | } 124 | signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] 125 | if !ok { 126 | return -1, fmt.Errorf("unknown signal %q", rawSignal) 127 | } 128 | return signal, nil 129 | } 130 | -------------------------------------------------------------------------------- /llcli/llcli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "os" 21 | "strings" 22 | 23 | ll "github.com/nabla-containers/runnc/llif" 24 | "github.com/opencontainers/runtime-spec/specs-go" 25 | "github.com/sirupsen/logrus" 26 | "github.com/urfave/cli" 27 | ) 28 | 29 | // version will be populated by the Makefile, read from 30 | // VERSION file of the source code. 31 | var version = "" 32 | 33 | // gitCommit will be the hash that the binary was built from 34 | // and will be populated by the Makefile 35 | var gitCommit = "" 36 | 37 | const ( 38 | specConfig = "config.json" 39 | usage = `Nabla Containers runtime 40 | 41 | {{name}} is a command line client for running applications packaged according to 42 | the Open Container Initiative (OCI) format. 43 | 44 | Containers are configured using bundles. A bundle for a container is a directory 45 | that includes a specification file named "` + specConfig + `" and a root filesystem. 46 | The root filesystem contains the contents of the container. 47 | 48 | To start a new instance of a container: 49 | 50 | # {{name}} run [ -b bundle ] 51 | 52 | Where "" is your name for the instance of the container that you 53 | are starting. The name you provide for the container instance must be unique on 54 | your host. Providing the bundle directory using "-b" is optional. The default 55 | value for "bundle" is the current directory.` 56 | ) 57 | 58 | // Runllc takes in a set of low level handlers (llcHandler), the name of the 59 | // runtime (i.e. "runnc"), and the container root to use and runs the CLI 60 | // of an OCI container runtime. 61 | func Runllc(runtimeName string, runtimeRoot string, llcHandler ll.RunllcHandler) { 62 | app := cli.NewApp() 63 | app.Name = runtimeName 64 | 65 | strFn := createSubst(map[string]string{ 66 | "name": app.Name, 67 | }) 68 | 69 | app.Usage = strFn(usage) 70 | 71 | var v []string 72 | if version != "" { 73 | v = append(v, version) 74 | } 75 | if gitCommit != "" { 76 | v = append(v, fmt.Sprintf("commit: %s", gitCommit)) 77 | } 78 | v = append(v, fmt.Sprintf("spec: %s", specs.Version)) 79 | app.Version = strings.Join(v, "\n") 80 | 81 | root := runtimeRoot 82 | 83 | app.Flags = []cli.Flag{ 84 | cli.BoolFlag{ 85 | Name: "debug", 86 | Usage: "enable debug output for logging", 87 | }, 88 | cli.StringFlag{ 89 | Name: "log", 90 | Value: "/dev/null", 91 | Usage: "set the log file path where internal debug information is written", 92 | }, 93 | cli.StringFlag{ 94 | Name: "log-format", 95 | Value: "text", 96 | Usage: "set the format used by logs ('text' (default), or 'json')", 97 | }, 98 | cli.StringFlag{ 99 | Name: "root", 100 | Value: root, 101 | Usage: "root directory for storage of container state (this should be located in tmpfs)", 102 | }, 103 | } 104 | app.Commands = []cli.Command{ 105 | // Implement essentials first (for basic docker run to work) 106 | newCreateCmd(llcHandler, strFn), 107 | newDeleteCmd(llcHandler, strFn), 108 | newStateCmd(llcHandler, strFn), 109 | newStartCmd(llcHandler, strFn), 110 | newKillCmd(llcHandler, strFn), 111 | newInitCmd(llcHandler, strFn), 112 | // checkpointCommand, 113 | // eventsCommand, 114 | // execCommand, 115 | // listCommand, 116 | // pauseCommand, 117 | // psCommand, 118 | // restoreCommand, 119 | // resumeCommand, 120 | // runCommand, 121 | // specCommand, 122 | // updateCommand, 123 | } 124 | app.Before = func(context *cli.Context) error { 125 | if context.GlobalBool("debug") { 126 | logrus.SetLevel(logrus.DebugLevel) 127 | } 128 | if path := context.GlobalString("log"); path != "" { 129 | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666) 130 | if err != nil { 131 | fmt.Fprintln(os.Stdout, err.Error()) 132 | return err 133 | } 134 | logrus.SetOutput(f) 135 | } 136 | switch context.GlobalString("log-format") { 137 | case "text": 138 | // retain logrus's default. 139 | case "json": 140 | logrus.SetFormatter(new(logrus.JSONFormatter)) 141 | default: 142 | return fmt.Errorf("unknown log-format %q", context.GlobalString("log-format")) 143 | } 144 | return nil 145 | } 146 | 147 | // If the command returns an error, cli takes upon itself to print 148 | // the error on cli.ErrWriter and exit. 149 | // Use our own writer here to ensure the log gets sent to the right location. 150 | cli.ErrWriter = &FatalWriter{cli.ErrWriter} 151 | if err := app.Run(os.Args); err != nil { 152 | fatal(err) 153 | } 154 | } 155 | 156 | type FatalWriter struct { 157 | cliErrWriter io.Writer 158 | } 159 | 160 | func (f *FatalWriter) Write(p []byte) (n int, err error) { 161 | logrus.Error(string(p)) 162 | return f.cliErrWriter.Write(p) 163 | } 164 | -------------------------------------------------------------------------------- /llcli/start.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "fmt" 19 | "github.com/nabla-containers/runnc/libcontainer" 20 | "github.com/pkg/errors" 21 | "github.com/urfave/cli" 22 | 23 | ll "github.com/nabla-containers/runnc/llif" 24 | ) 25 | 26 | func newStartCmd(llcHandler ll.RunllcHandler, sf stringSubFunc) cli.Command { 27 | return cli.Command{ 28 | Name: "start", 29 | Usage: "executes the user defined process in a created container", 30 | ArgsUsage: sf(` 31 | 32 | Where "" is your name for the instance of the container that you 33 | are starting. The name you provide for the container instance must be unique on 34 | your host.`), 35 | Description: sf(`The start command executes the user defined process in a created container.`), 36 | Action: func(context *cli.Context) error { 37 | container, err := getContainer(context, llcHandler) 38 | if err != nil { 39 | return err 40 | } 41 | status, err := container.Status() 42 | if err != nil { 43 | return err 44 | } 45 | switch status { 46 | case libcontainer.Created: 47 | return container.Exec() 48 | case libcontainer.Stopped: 49 | return errors.New("cannot start a container that has stopped") 50 | case libcontainer.Running: 51 | return errors.New("cannot start an already running container") 52 | default: 53 | return fmt.Errorf("cannot start a container in the %s state\n", status) 54 | } 55 | }, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /llcli/state.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "encoding/json" 19 | "github.com/opencontainers/runc/libcontainer/utils" 20 | "github.com/urfave/cli" 21 | "os" 22 | "time" 23 | 24 | ll "github.com/nabla-containers/runnc/llif" 25 | ) 26 | 27 | func newStateCmd(llcHandler ll.RunllcHandler, sf stringSubFunc) cli.Command { 28 | return cli.Command{ 29 | Name: "state", 30 | Usage: "output the state of a container", 31 | ArgsUsage: sf(` 32 | 33 | Where "" is your name for the instance of the container.`), 34 | Description: sf(`The state command outputs current state information for the 35 | instance of a container.`), 36 | Action: func(context *cli.Context) error { 37 | container, err := getContainer(context, llcHandler) 38 | if err != nil { 39 | fatal(err) 40 | } 41 | 42 | state, err := container.State() 43 | if err != nil { 44 | fatal(err) 45 | } 46 | 47 | status, err := container.Status() 48 | if err != nil { 49 | fatal(err) 50 | } 51 | 52 | //bundle, annotations := utils.Annotations(state.Config.Labels) 53 | cs := containerState{ 54 | Version: state.BaseState.Config.Version, 55 | ID: state.BaseState.ID, 56 | InitProcessPid: state.BaseState.InitProcessPid, 57 | Status: status.String(), 58 | Bundle: utils.SearchLabels(state.Config.Labels, "bundle"), 59 | Rootfs: state.BaseState.Config.Rootfs, 60 | Created: state.BaseState.Created, 61 | } 62 | data, err := json.MarshalIndent(cs, "", " ") 63 | if err != nil { 64 | fatal(err) 65 | } 66 | os.Stdout.Write(data) 67 | 68 | // DEBUG 69 | //os.Stderr.Write(data) 70 | 71 | return nil 72 | }, 73 | } 74 | } 75 | 76 | // containerState represents the platform agnostic pieces relating to a 77 | // running container's status and state 78 | type containerState struct { 79 | // Version is the OCI version for the container 80 | Version string `json:"ociVersion"` 81 | // ID is the container ID 82 | ID string `json:"id"` 83 | // InitProcessPid is the init process id in the parent namespace 84 | InitProcessPid int `json:"pid"` 85 | // Status is the current status of the container, running, paused, ... 86 | Status string `json:"status"` 87 | // Bundle is the path on the filesystem to the bundle 88 | Bundle string `json:"bundle"` 89 | // Rootfs is a path to a directory containing the container's root filesystem. 90 | Rootfs string `json:"rootfs"` 91 | // Created is the unix timestamp for the creation time of the container in UTC 92 | Created time.Time `json:"created"` 93 | // Annotations is the user defined annotations added to the config. 94 | Annotations map[string]string `json:"annotations,omitempty"` 95 | } 96 | -------------------------------------------------------------------------------- /llcli/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "os" 21 | "path/filepath" 22 | "strings" 23 | 24 | "github.com/opencontainers/runtime-spec/specs-go" 25 | "github.com/sirupsen/logrus" 26 | "github.com/urfave/cli" 27 | ) 28 | 29 | type stringSubFunc func(string) string 30 | 31 | // fatal prints the error's details if it is a libcontainer specific error type 32 | // then exits the program with an exit status of 1. 33 | func fatal(err error) { 34 | // make sure the error is written to the logger 35 | logrus.Error(err) 36 | fmt.Fprintln(os.Stderr, err) 37 | os.Exit(1) 38 | } 39 | 40 | func createSubst(params map[string]string) stringSubFunc { 41 | return func(inputStr string) string { 42 | return stprintf(inputStr, params) 43 | } 44 | } 45 | 46 | func stprintf(format string, params map[string]string) string { 47 | for key, val := range params { 48 | format = strings.Replace(format, "{{"+key+"}}", fmt.Sprintf("%s", val), -1) 49 | } 50 | return format 51 | } 52 | 53 | // setupSpec performs inital setup based on the cli.Context for the container 54 | func setupSpec(context *cli.Context) (*specs.Spec, error) { 55 | bundle := context.String("bundle") 56 | if bundle != "" { 57 | if err := os.Chdir(bundle); err != nil { 58 | return nil, err 59 | } 60 | } 61 | spec, err := loadSpec(specConfig) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | if spec.Root != nil && !strings.HasPrefix(spec.Root.Path, "/") { 67 | spec.Root.Path = filepath.Join(bundle, spec.Root.Path) 68 | } 69 | 70 | notifySocket := os.Getenv("NOTIFY_SOCKET") 71 | if notifySocket != "" { 72 | setupSdNotify(spec, notifySocket) 73 | } 74 | if os.Geteuid() != 0 { 75 | return nil, fmt.Errorf("runtime should be run as root") 76 | } 77 | return spec, nil 78 | } 79 | 80 | // loadSpec loads the specification from the provided path. 81 | func loadSpec(cPath string) (spec *specs.Spec, err error) { 82 | cf, err := os.Open(cPath) 83 | if err != nil { 84 | if os.IsNotExist(err) { 85 | return nil, fmt.Errorf("JSON specification file %s not found", cPath) 86 | } 87 | return nil, err 88 | } 89 | defer cf.Close() 90 | 91 | if err = json.NewDecoder(cf).Decode(&spec); err != nil { 92 | return nil, err 93 | } 94 | return spec, validateProcessSpec(spec.Process) 95 | } 96 | -------------------------------------------------------------------------------- /llcli/util_nabla.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | "path/filepath" 22 | "syscall" 23 | 24 | "github.com/nabla-containers/runnc/libcontainer" 25 | "github.com/nabla-containers/runnc/libcontainer/configs" 26 | ll "github.com/nabla-containers/runnc/llif" 27 | "github.com/opencontainers/runtime-spec/specs-go" 28 | "github.com/sirupsen/logrus" 29 | "github.com/urfave/cli" 30 | ) 31 | 32 | var ( 33 | errEmptyID = errors.New("container id cannot be empty") 34 | ) 35 | 36 | // getContainer returns the specified container instance by loading it from state 37 | // with the default factory. 38 | func getContainer(context *cli.Context, llcHandler ll.RunllcHandler) (libcontainer.Container, error) { 39 | id := context.Args().First() 40 | if id == "" { 41 | return nil, errEmptyID 42 | } 43 | factory, err := loadFactory(context, llcHandler) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return factory.Load(id) 48 | } 49 | 50 | func startContainer(context *cli.Context, llcHandler ll.RunllcHandler, spec *specs.Spec, create bool) (int, error) { 51 | id := context.Args().First() 52 | if id == "" { 53 | return -1, errEmptyID 54 | } 55 | 56 | container, err := createContainer(context, llcHandler, id, spec) 57 | if err != nil { 58 | return -1, err 59 | } 60 | 61 | detach := context.Bool("detach") 62 | // Support on-demand socket activation by passing file descriptors into the container init process. 63 | listenFDs := []*os.File{} 64 | 65 | r := &runner{ 66 | enableSubreaper: !context.Bool("no-subreaper"), 67 | shouldDestroy: true, 68 | container: container, 69 | listenFDs: listenFDs, 70 | console: context.String("console"), 71 | detach: detach, 72 | pidFile: context.String("pid-file"), 73 | create: create, 74 | } 75 | 76 | return r.run(spec.Process) 77 | } 78 | 79 | func createContainer(context *cli.Context, llcHandler ll.RunllcHandler, id string, spec *specs.Spec) (libcontainer.Container, error) { 80 | 81 | config, err := configs.ParseSpec(spec) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | config.Labels = append(config.Labels, "bundle="+context.String("bundle")) 87 | 88 | if _, err := os.Stat(config.Rootfs); err != nil { 89 | if os.IsNotExist(err) { 90 | return nil, fmt.Errorf("rootfs (%q) does not exist", config.Rootfs) 91 | } 92 | return nil, err 93 | } 94 | 95 | factory, err := loadFactory(context, llcHandler) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | return factory.Create(id, config) 101 | } 102 | 103 | // loadFactory returns the configured factory instance for execing containers. 104 | func loadFactory(context *cli.Context, llcHandler ll.RunllcHandler) (libcontainer.Factory, error) { 105 | root := context.GlobalString("root") 106 | abs, err := filepath.Abs(root) 107 | if err != nil { 108 | return nil, err 109 | } 110 | return libcontainer.New(abs, llcHandler) 111 | } 112 | 113 | func dupStdio(process *libcontainer.Process, rootuid, rootgid int) error { 114 | process.Stdin = os.Stdin 115 | process.Stdout = os.Stdout 116 | process.Stderr = os.Stderr 117 | for _, fd := range []uintptr{ 118 | os.Stdin.Fd(), 119 | os.Stdout.Fd(), 120 | os.Stderr.Fd(), 121 | } { 122 | if err := syscall.Fchown(int(fd), rootuid, rootgid); err != nil { 123 | return err 124 | } 125 | } 126 | return nil 127 | } 128 | 129 | func destroy(container libcontainer.Container) { 130 | if err := container.Destroy(); err != nil { 131 | logrus.Error(err) 132 | } 133 | } 134 | 135 | // If systemd is supporting sd_notify protocol, this function will add support 136 | // for sd_notify protocol from within the container. 137 | func setupSdNotify(spec *specs.Spec, notifySocket string) { 138 | spec.Mounts = append(spec.Mounts, specs.Mount{Destination: notifySocket, Type: "bind", Source: notifySocket, Options: []string{"bind"}}) 139 | spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notifySocket)) 140 | } 141 | 142 | func validateProcessSpec(spec *specs.Process) error { 143 | if spec.Cwd == "" { 144 | return fmt.Errorf("Cwd property must not be empty") 145 | } 146 | if !filepath.IsAbs(spec.Cwd) { 147 | return fmt.Errorf("Cwd must be an absolute path") 148 | } 149 | if len(spec.Args) == 0 { 150 | return fmt.Errorf("args must not be empty") 151 | } 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /llcli/util_runner.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "github.com/nabla-containers/runnc/libcontainer" 19 | "github.com/opencontainers/runtime-spec/specs-go" 20 | 21 | "fmt" 22 | "io/ioutil" 23 | "os" 24 | "strconv" 25 | "syscall" 26 | 27 | "path/filepath" 28 | ) 29 | 30 | type runner struct { 31 | enableSubreaper bool 32 | shouldDestroy bool 33 | detach bool 34 | listenFDs []*os.File 35 | pidFile string 36 | console string 37 | container libcontainer.Container 38 | create bool 39 | } 40 | 41 | func (r *runner) run(config *specs.Process) (int, error) { 42 | process, err := newProcess(*config) 43 | if err != nil { 44 | r.destroy() 45 | return -1, err 46 | } 47 | if len(r.listenFDs) > 0 { 48 | process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1") 49 | process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...) 50 | } 51 | rootuid, err := r.container.Config().HostUID() 52 | if err != nil { 53 | r.destroy() 54 | return -1, err 55 | } 56 | rootgid, err := r.container.Config().HostGID() 57 | if err != nil { 58 | r.destroy() 59 | return -1, err 60 | } 61 | tty, err := setupIO(process, rootuid, rootgid, r.console, config.Terminal, r.detach || r.create) 62 | if err != nil { 63 | r.destroy() 64 | return -1, err 65 | } 66 | handler := newSignalHandler(tty, r.enableSubreaper) 67 | startFn := r.container.Start 68 | if !r.create { 69 | startFn = r.container.Run 70 | } 71 | defer tty.Close() 72 | if err := startFn(process); err != nil { 73 | r.destroy() 74 | return -1, err 75 | } 76 | if err := tty.ClosePostStart(); err != nil { 77 | r.terminate(process) 78 | r.destroy() 79 | return -1, err 80 | } 81 | if r.pidFile != "" { 82 | if err := createPidFile(r.pidFile, process); err != nil { 83 | r.terminate(process) 84 | r.destroy() 85 | return -1, err 86 | } 87 | } 88 | if process.OOMScoreAdj != nil { 89 | if err := adjustOomScore(process); err != nil { 90 | r.terminate(process) 91 | r.destroy() 92 | return -1, err 93 | } 94 | } 95 | if r.detach || r.create { 96 | return 0, nil 97 | } 98 | status, err := handler.forward(process) 99 | if err != nil { 100 | r.terminate(process) 101 | } 102 | r.destroy() 103 | return status, err 104 | } 105 | 106 | func (r *runner) destroy() { 107 | if r.shouldDestroy { 108 | destroy(r.container) 109 | } 110 | } 111 | 112 | func (r *runner) terminate(p *libcontainer.Process) { 113 | p.Signal(syscall.SIGKILL) 114 | p.Wait() 115 | } 116 | 117 | // newProcess returns a new libcontainer Process with the arguments from the 118 | // spec and stdio from the current process. 119 | func newProcess(p specs.Process) (*libcontainer.Process, error) { 120 | lp := &libcontainer.Process{ 121 | Args: p.Args, 122 | Env: p.Env, 123 | // TODO: fix libcontainer's API to better support uid/gid in a typesafe way. 124 | User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID), 125 | Cwd: p.Cwd, 126 | Label: p.SelinuxLabel, 127 | NoNewPrivileges: &p.NoNewPrivileges, 128 | AppArmorProfile: p.ApparmorProfile, 129 | OOMScoreAdj: p.OOMScoreAdj, 130 | } 131 | for _, gid := range p.User.AdditionalGids { 132 | lp.AdditionalGroups = append(lp.AdditionalGroups, strconv.FormatUint(uint64(gid), 10)) 133 | } 134 | 135 | return lp, nil 136 | } 137 | 138 | // setupIO sets the proper IO on the process depending on the configuration 139 | // If there is a nil error then there must be a non nil tty returned 140 | func setupIO(process *libcontainer.Process, rootuid, rootgid int, console string, createTTY, detach bool) (*tty, error) { 141 | // detach and createTty will not work unless a console path is passed 142 | // so error out here before changing any terminal settings 143 | if createTTY && detach && console == "" { 144 | return nil, fmt.Errorf("cannot allocate tty if runc will detach") 145 | } 146 | if createTTY { 147 | return createTty(process, rootuid, rootgid, console) 148 | } 149 | if detach { 150 | if err := dupStdio(process, rootuid, rootgid); err != nil { 151 | return nil, err 152 | } 153 | return &tty{}, nil 154 | } 155 | return createStdioPipes(process, rootuid, rootgid) 156 | } 157 | 158 | // createPidFile creates a file with the processes pid inside it atomically 159 | // it creates a temp file with the paths filename + '.' infront of it 160 | // then renames the file 161 | func createPidFile(path string, process *libcontainer.Process) error { 162 | pid, err := process.Pid() 163 | if err != nil { 164 | return err 165 | } 166 | var ( 167 | tmpDir = filepath.Dir(path) 168 | tmpName = filepath.Join(tmpDir, fmt.Sprintf(".%s", filepath.Base(path))) 169 | ) 170 | f, err := os.OpenFile(tmpName, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666) 171 | if err != nil { 172 | return err 173 | } 174 | _, err = fmt.Fprintf(f, "%d", pid) 175 | f.Close() 176 | if err != nil { 177 | return err 178 | } 179 | return os.Rename(tmpName, path) 180 | } 181 | 182 | func adjustOomScore(process *libcontainer.Process) error { 183 | pid, err := process.Pid() 184 | if err != nil { 185 | return err 186 | } 187 | oomScoreAdjPath := filepath.Join("/proc/", strconv.Itoa(pid), "oom_score_adj") 188 | if process.OOMScoreAdj == nil { 189 | return fmt.Errorf("OOMScoreAdj value is nil") 190 | } 191 | value := strconv.Itoa(*process.OOMScoreAdj) 192 | err = ioutil.WriteFile(oomScoreAdjPath, []byte(value), 0644) 193 | if err != nil { 194 | return err 195 | } 196 | return nil 197 | } 198 | -------------------------------------------------------------------------------- /llcli/util_signal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llcli 16 | 17 | import ( 18 | "os" 19 | "os/signal" 20 | "syscall" 21 | 22 | "github.com/nabla-containers/runnc/libcontainer" 23 | "github.com/opencontainers/runc/libcontainer/system" 24 | "github.com/opencontainers/runc/libcontainer/utils" 25 | "github.com/sirupsen/logrus" 26 | ) 27 | 28 | const signalBufferSize = 2048 29 | 30 | // newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals 31 | // while still forwarding all other signals to the process. 32 | func newSignalHandler(tty *tty, enableSubreaper bool) *signalHandler { 33 | if enableSubreaper { 34 | // set us as the subreaper before registering the signal handler for the container 35 | if err := system.SetSubreaper(1); err != nil { 36 | logrus.Warn(err) 37 | } 38 | } 39 | // ensure that we have a large buffer size so that we do not miss any signals 40 | // incase we are not processing them fast enough. 41 | s := make(chan os.Signal, signalBufferSize) 42 | // handle all signals for the process. 43 | signal.Notify(s) 44 | return &signalHandler{ 45 | tty: tty, 46 | signals: s, 47 | } 48 | } 49 | 50 | // exit models a process exit status with the pid and 51 | // exit status. 52 | type exit struct { 53 | pid int 54 | status int 55 | } 56 | 57 | type signalHandler struct { 58 | signals chan os.Signal 59 | tty *tty 60 | } 61 | 62 | // forward handles the main signal event loop forwarding, resizing, or reaping depending 63 | // on the signal received. 64 | func (h *signalHandler) forward(process *libcontainer.Process) (int, error) { 65 | // make sure we know the pid of our main process so that we can return 66 | // after it dies. 67 | pid1, err := process.Pid() 68 | if err != nil { 69 | return -1, err 70 | } 71 | // perform the initial tty resize. 72 | h.tty.resize() 73 | for s := range h.signals { 74 | switch s { 75 | case syscall.SIGWINCH: 76 | h.tty.resize() 77 | case syscall.SIGCHLD: 78 | exits, err := h.reap() 79 | if err != nil { 80 | logrus.Error(err) 81 | } 82 | for _, e := range exits { 83 | logrus.WithFields(logrus.Fields{ 84 | "pid": e.pid, 85 | "status": e.status, 86 | }).Debug("process exited") 87 | if e.pid == pid1 { 88 | // call Wait() on the process even though we already have the exit 89 | // status because we must ensure that any of the go specific process 90 | // fun such as flushing pipes are complete before we return. 91 | process.Wait() 92 | return e.status, nil 93 | } 94 | } 95 | default: 96 | logrus.Debugf("sending signal to process %s", s) 97 | if err := syscall.Kill(pid1, s.(syscall.Signal)); err != nil { 98 | logrus.Error(err) 99 | } 100 | } 101 | } 102 | return -1, nil 103 | } 104 | 105 | // reap runs wait4 in a loop until we have finished processing any existing exits 106 | // then returns all exits to the main event loop for further processing. 107 | func (h *signalHandler) reap() (exits []exit, err error) { 108 | var ( 109 | ws syscall.WaitStatus 110 | rus syscall.Rusage 111 | ) 112 | for { 113 | pid, err := syscall.Wait4(-1, &ws, syscall.WNOHANG, &rus) 114 | if err != nil { 115 | if err == syscall.ECHILD { 116 | return exits, nil 117 | } 118 | return nil, err 119 | } 120 | if pid <= 0 { 121 | return exits, nil 122 | } 123 | exits = append(exits, exit{ 124 | pid: pid, 125 | status: utils.ExitStatus(ws), 126 | }) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /llcli/util_tty.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Docker, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build linux 16 | 17 | package llcli 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "os" 23 | "sync" 24 | 25 | "github.com/docker/docker/pkg/term" 26 | "github.com/nabla-containers/runnc/libcontainer" 27 | ) 28 | 29 | // setup standard pipes so that the TTY of the calling runllc process 30 | // is not inherited by the container. 31 | func createStdioPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) { 32 | i, err := p.InitializeIO(rootuid, rootgid) 33 | if err != nil { 34 | return nil, err 35 | } 36 | t := &tty{ 37 | closers: []io.Closer{ 38 | i.Stdin, 39 | i.Stdout, 40 | i.Stderr, 41 | }, 42 | } 43 | // add the process's io to the post start closers if they support close 44 | for _, cc := range []interface{}{ 45 | p.Stdin, 46 | p.Stdout, 47 | p.Stderr, 48 | } { 49 | if c, ok := cc.(io.Closer); ok { 50 | t.postStart = append(t.postStart, c) 51 | } 52 | } 53 | go func() { 54 | io.Copy(i.Stdin, os.Stdin) 55 | i.Stdin.Close() 56 | }() 57 | t.wg.Add(2) 58 | go t.copyIO(os.Stdout, i.Stdout) 59 | go t.copyIO(os.Stderr, i.Stderr) 60 | return t, nil 61 | } 62 | 63 | func (t *tty) copyIO(w io.Writer, r io.ReadCloser) { 64 | defer t.wg.Done() 65 | io.Copy(w, r) 66 | r.Close() 67 | } 68 | 69 | func createTty(p *libcontainer.Process, rootuid, rootgid int, consolePath string) (*tty, error) { 70 | if consolePath != "" { 71 | if err := p.ConsoleFromPath(consolePath); err != nil { 72 | return nil, err 73 | } 74 | return &tty{}, nil 75 | } 76 | console, err := p.NewConsole(rootuid, rootgid) 77 | if err != nil { 78 | return nil, err 79 | } 80 | go io.Copy(console, os.Stdin) 81 | go io.Copy(os.Stdout, console) 82 | 83 | state, err := term.SetRawTerminal(os.Stdin.Fd()) 84 | if err != nil { 85 | return nil, fmt.Errorf("failed to set the terminal from the stdin: %v", err) 86 | } 87 | return &tty{ 88 | console: console, 89 | state: state, 90 | closers: []io.Closer{ 91 | console, 92 | }, 93 | }, nil 94 | } 95 | 96 | type tty struct { 97 | console libcontainer.Console 98 | state *term.State 99 | closers []io.Closer 100 | postStart []io.Closer 101 | wg sync.WaitGroup 102 | } 103 | 104 | // ClosePostStart closes any fds that are provided to the container and dup2'd 105 | // so that we no longer have copy in our process. 106 | func (t *tty) ClosePostStart() error { 107 | for _, c := range t.postStart { 108 | c.Close() 109 | } 110 | return nil 111 | } 112 | 113 | // Close closes all open fds for the tty and/or restores the orignal 114 | // stdin state to what it was prior to the container execution 115 | func (t *tty) Close() error { 116 | // ensure that our side of the fds are always closed 117 | for _, c := range t.postStart { 118 | c.Close() 119 | } 120 | // wait for the copy routines to finish before closing the fds 121 | t.wg.Wait() 122 | for _, c := range t.closers { 123 | c.Close() 124 | } 125 | if t.state != nil { 126 | term.RestoreTerminal(os.Stdin.Fd(), t.state) 127 | } 128 | return nil 129 | } 130 | 131 | func (t *tty) resize() error { 132 | if t.console == nil { 133 | return nil 134 | } 135 | ws, err := term.GetWinsize(os.Stdin.Fd()) 136 | if err != nil { 137 | return err 138 | } 139 | return term.SetWinsize(t.console.Fd(), ws) 140 | } 141 | -------------------------------------------------------------------------------- /llif/exec.go: -------------------------------------------------------------------------------- 1 | package llif 2 | 3 | import ( 4 | "github.com/nabla-containers/runnc/libcontainer/configs" 5 | ) 6 | 7 | type ExecGenericInput struct { 8 | // ContainerId is the id of the container 9 | ContainerId string 10 | 11 | // ContainerRoot signifies the root of the container's existence on the 12 | // host 13 | ContainerRoot string 14 | 15 | // Config contains the configuration of the container 16 | Config *configs.Config 17 | 18 | // The state of LL handlers 19 | FsState *LLState 20 | NetworkState *LLState 21 | ExecState *LLState 22 | } 23 | 24 | type ExecCreateInput struct { 25 | ExecGenericInput 26 | } 27 | 28 | type ExecRunInput struct { 29 | ExecGenericInput 30 | } 31 | 32 | type ExecDestroyInput struct { 33 | ExecGenericInput 34 | } 35 | -------------------------------------------------------------------------------- /llif/fs.go: -------------------------------------------------------------------------------- 1 | package llif 2 | 3 | import ( 4 | "github.com/nabla-containers/runnc/libcontainer/configs" 5 | ) 6 | 7 | type FsGenericInput struct { 8 | // ContainerId is the id of the container 9 | ContainerId string 10 | 11 | // ContainerRoot signifies the root of the container's existence on the 12 | // host 13 | ContainerRoot string 14 | 15 | // Config contains the configuration of the container 16 | Config *configs.Config 17 | 18 | // The state of LL handlers 19 | FsState *LLState 20 | NetworkState *LLState 21 | ExecState *LLState 22 | } 23 | 24 | type FsCreateInput struct { 25 | FsGenericInput 26 | } 27 | 28 | type FsRunInput struct { 29 | FsGenericInput 30 | } 31 | 32 | type FsDestroyInput struct { 33 | FsGenericInput 34 | } 35 | -------------------------------------------------------------------------------- /llif/llif.go: -------------------------------------------------------------------------------- 1 | package llif 2 | 3 | // RunllcHandler is the interface that is needed to be implemented in order 4 | // to create a Low Level OCI runtime with Runllc. 5 | // 6 | // There are 3 extensible components and 3 integration points. 7 | // 8 | // The 3 extensible components are the filesystem, network, and execution. 9 | // Thus, there are 3 separate handles for each of them. Fs, Network, Exec. 10 | // 11 | // There are 3 different integration points, creation of the container, 12 | // Running of the container, and finally, the destruction of the container. 13 | // 14 | // The order of which the handlers are run are as follows: 15 | // Integration: Create 16 | // Order: FsCreateFunc, NetworkCreateFunc, ExecCreateFunc 17 | // 18 | // Integration: Run 19 | // Order: FsRunFunc, NetworkRunFunc, ExecRunFunc 20 | // 21 | // Integration: Destroy (this is the backward order from the previous two) 22 | // Order: ExecDestroyFunc, NetworkDestroyFunc, FsDestroyFunc 23 | type RunllcHandler struct { 24 | FsH FsHandler 25 | NetworkH NetworkHandler 26 | ExecH ExecHandler 27 | } 28 | 29 | type FsHandler interface { 30 | FsCreateFunc(*FsCreateInput) (*LLState, error) 31 | FsRunFunc(*FsRunInput) (*LLState, error) 32 | FsDestroyFunc(*FsDestroyInput) (*LLState, error) 33 | } 34 | 35 | type NetworkHandler interface { 36 | NetworkCreateFunc(*NetworkCreateInput) (*LLState, error) 37 | NetworkRunFunc(*NetworkRunInput) (*LLState, error) 38 | NetworkDestroyFunc(*NetworkDestroyInput) (*LLState, error) 39 | } 40 | 41 | type ExecHandler interface { 42 | ExecCreateFunc(*ExecCreateInput) (*LLState, error) 43 | // ExecRunFunc should not return unless it runs into an error 44 | // TODO(runllc): Change this to possibly return state 45 | ExecRunFunc(*ExecRunInput) error 46 | ExecDestroyFunc(*ExecDestroyInput) (*LLState, error) 47 | } 48 | 49 | type LLState struct { 50 | // Options is the map of parameters that will be stored in the config and 51 | // passed along across different operations. Entries in this map set 52 | // in the output of the Create phase will be present in the input of the 53 | // Run phase. 54 | // TODO(runllc): Need to figure out how to save state in Exec phase or is 55 | // there a need to? 56 | Options map[string]string `json:"options"` 57 | 58 | // InMemoryObjects is the map of objects that can be shared with other handlers 59 | // within the same operation (i.e. in-memory data structures). The entries 60 | // from the output of the Create phase will not be accessible to the 61 | // Run phase. However, they will be accessible by the Exec handler of the 62 | // same phase. 63 | InMemoryObjects map[string]interface{} `json:"-"` 64 | } 65 | -------------------------------------------------------------------------------- /llif/network.go: -------------------------------------------------------------------------------- 1 | package llif 2 | 3 | import ( 4 | "github.com/nabla-containers/runnc/libcontainer/configs" 5 | ) 6 | 7 | type NetworkGenericInput struct { 8 | // ContainerId is the id of the container 9 | ContainerId string 10 | 11 | // ContainerRoot signifies the root of the container's existence on the 12 | // host 13 | ContainerRoot string 14 | 15 | // Config contains the configuration of the container 16 | Config *configs.Config 17 | 18 | // The state of LL handlers 19 | FsState *LLState 20 | NetworkState *LLState 21 | ExecState *LLState 22 | } 23 | 24 | type NetworkCreateInput struct { 25 | NetworkGenericInput 26 | } 27 | 28 | type NetworkRunInput struct { 29 | NetworkGenericInput 30 | } 31 | 32 | type NetworkDestroyInput struct { 33 | NetworkGenericInput 34 | } 35 | -------------------------------------------------------------------------------- /llmodules/fs/iso_storage.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/nabla-containers/runnc/libcontainer/configs" 8 | ll "github.com/nabla-containers/runnc/llif" 9 | "github.com/nabla-containers/runnc/nabla-lib/storage" 10 | "github.com/nabla-containers/runnc/utils" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | type iSOFsHandler struct{} 15 | 16 | func NewISOFsHandler() (ll.FsHandler, error) { 17 | return &iSOFsHandler{}, nil 18 | } 19 | 20 | func (h *iSOFsHandler) FsCreateFunc(i *ll.FsCreateInput) (*ll.LLState, error) { 21 | fsPath, err := createRootfsISO(i.Config, i.ContainerRoot) 22 | if err != nil { 23 | return nil, errors.Wrap(err, "Unable to create rootfs ISO") 24 | } 25 | 26 | ret := &ll.LLState{} 27 | ret.Options = map[string]string{ 28 | "FsPath": fsPath, 29 | } 30 | 31 | return ret, nil 32 | } 33 | 34 | func (h *iSOFsHandler) FsRunFunc(i *ll.FsRunInput) (*ll.LLState, error) { 35 | return i.FsState, nil 36 | } 37 | 38 | func (h *iSOFsHandler) FsDestroyFunc(i *ll.FsDestroyInput) (*ll.LLState, error) { 39 | if err := os.RemoveAll(i.ContainerRoot); err != nil { 40 | return nil, err 41 | } 42 | return i.FsState, nil 43 | } 44 | 45 | func createRootfsISO(config *configs.Config, containerRoot string) (string, error) { 46 | rootfsPath := config.Rootfs 47 | targetISOPath := filepath.Join(containerRoot, "rootfs.iso") 48 | if err := os.MkdirAll(filepath.Join(rootfsPath, "/etc"), 0755); err != nil { 49 | return "", errors.Wrap(err, "Unable to create "+filepath.Join(rootfsPath, "/etc")) 50 | } 51 | for _, mount := range config.Mounts { 52 | if (mount.Destination == "/etc/resolv.conf") || 53 | (mount.Destination == "/etc/hosts") || 54 | (mount.Destination == "/etc/hostname") { 55 | dest := filepath.Join(rootfsPath, mount.Destination) 56 | source := mount.Source 57 | if err := utils.Copy(dest, source); err != nil { 58 | return "", errors.Wrap(err, "Unable to copy "+source+" to "+dest) 59 | } 60 | } 61 | } 62 | _, err := storage.CreateIso(rootfsPath, &targetISOPath) 63 | if err != nil { 64 | return "", errors.Wrap(err, "Error creating iso from rootfs") 65 | } 66 | return targetISOPath, nil 67 | } 68 | -------------------------------------------------------------------------------- /llmodules/fs/noop_storage.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "os" 5 | 6 | ll "github.com/nabla-containers/runnc/llif" 7 | ) 8 | 9 | type noopFsHandler struct{} 10 | 11 | func NewNoopFsHandler() (ll.FsHandler, error) { 12 | return &noopFsHandler{}, nil 13 | } 14 | 15 | func (h *noopFsHandler) FsCreateFunc(i *ll.FsCreateInput) (*ll.LLState, error) { 16 | ret := &ll.LLState{} 17 | return ret, nil 18 | } 19 | 20 | func (h *noopFsHandler) FsRunFunc(i *ll.FsRunInput) (*ll.LLState, error) { 21 | return i.FsState, nil 22 | } 23 | 24 | func (h *noopFsHandler) FsDestroyFunc(i *ll.FsDestroyInput) (*ll.LLState, error) { 25 | if err := os.RemoveAll(i.ContainerRoot); err != nil { 26 | return nil, err 27 | } 28 | return i.FsState, nil 29 | } 30 | -------------------------------------------------------------------------------- /llmodules/network/noop_network.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | ll "github.com/nabla-containers/runnc/llif" 5 | ) 6 | 7 | type noopNetworkHandler struct{} 8 | 9 | func NewNoopNetworkHandler() (ll.NetworkHandler, error) { 10 | return &noopNetworkHandler{}, nil 11 | } 12 | 13 | func (h *noopNetworkHandler) NetworkCreateFunc(i *ll.NetworkCreateInput) (*ll.LLState, error) { 14 | ret := &ll.LLState{} 15 | return ret, nil 16 | } 17 | 18 | func (h *noopNetworkHandler) NetworkRunFunc(i *ll.NetworkRunInput) (*ll.LLState, error) { 19 | return i.NetworkState, nil 20 | } 21 | 22 | func (h *noopNetworkHandler) NetworkDestroyFunc(i *ll.NetworkDestroyInput) (*ll.LLState, error) { 23 | return i.NetworkState, nil 24 | } 25 | -------------------------------------------------------------------------------- /llmodules/network/tap_bridge.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "syscall" 6 | 7 | ll "github.com/nabla-containers/runnc/llif" 8 | "github.com/nabla-containers/runnc/nabla-lib/network" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type tapBrNetworkHandler struct{} 13 | 14 | func NewTapBrNetworkHandler() (ll.NetworkHandler, error) { 15 | return &tapBrNetworkHandler{}, nil 16 | } 17 | 18 | func (h *tapBrNetworkHandler) NetworkCreateFunc(i *ll.NetworkCreateInput) (*ll.LLState, error) { 19 | tapName := nablaTapName(i.ContainerId) 20 | if err := network.CreateTapInterface(tapName, nil, nil); err != nil { 21 | return nil, errors.Wrap(err, "Unable to create tap in NetworkCreate") 22 | } 23 | 24 | ret := &ll.LLState{ 25 | Options: map[string]string{ 26 | "TapName": tapName, 27 | }, 28 | } 29 | return ret, nil 30 | } 31 | 32 | func (h *tapBrNetworkHandler) NetworkRunFunc(i *ll.NetworkRunInput) (*ll.LLState, error) { 33 | tapName, ok := i.NetworkState.Options["TapName"] 34 | if !ok { 35 | return nil, errors.New("Unable to get tap name") 36 | } 37 | 38 | // The tap device will get the IP assigned to the k8s nabla 39 | // container veth pair. 40 | // XXX: This is a workaround due to an error with MacvTap, error was : 41 | // Could not create /dev/tap8863: open /sys/devices/virtual/net/macvtap8863/tap8863/dev: no such file or directory 42 | ipAddress, gateway, ipMask, mac, err := network.CreateTapInterfaceDocker(nablaTapName(i.ContainerId), "eth0") 43 | if err != nil { 44 | return nil, errors.Wrap(err, "Unable to configure network runtime") 45 | } 46 | cidr, totalBits := ipMask.Size() 47 | if totalBits != 32 { 48 | return nil, errors.New("Unexpected IP address number of bits") 49 | } 50 | 51 | ret := &ll.LLState{ 52 | Options: map[string]string{ 53 | "IPAddress": ipAddress.String(), 54 | "Gateway": gateway.String(), 55 | "IPMask": fmt.Sprintf("%d", cidr), 56 | "Mac": mac, 57 | "TapName": tapName, 58 | }, 59 | } 60 | 61 | return ret, nil 62 | } 63 | 64 | func (h *tapBrNetworkHandler) NetworkDestroyFunc(i *ll.NetworkDestroyInput) (*ll.LLState, error) { 65 | tapName, ok := i.NetworkState.Options["TapName"] 66 | if !ok { 67 | return nil, errors.New("Unable to get tap name") 68 | } 69 | if err := network.RemoveTapDevice(tapName); err != nil { 70 | return nil, err 71 | } 72 | return i.NetworkState, nil 73 | } 74 | 75 | //err = network.CreateTapInterface(nablaTapName(id), nil, nil) 76 | 77 | // nablaTapName returns the tapname of a given container ID 78 | func nablaTapName(id string) string { 79 | if len(id) < 8 { 80 | panic("Insufficient uniqueness in ID") 81 | } 82 | return ("tap" + id)[:syscall.IFNAMSIZ-1] 83 | } 84 | -------------------------------------------------------------------------------- /llruntimes/nabla/exec_handler.go: -------------------------------------------------------------------------------- 1 | package nabla 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/nabla-containers/runnc/libcontainer/configs" 10 | ll "github.com/nabla-containers/runnc/llif" 11 | "github.com/nabla-containers/runnc/llruntimes/nabla/runnc-cont" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | var ( 16 | NablaBinDir = "/opt/runnc/bin/" 17 | NablaRunBin = NablaBinDir + "nabla-run" 18 | ) 19 | 20 | type nablaExecHandler struct{} 21 | 22 | func NewNablaExecHandler() (ll.ExecHandler, error) { 23 | return &nablaExecHandler{}, nil 24 | } 25 | 26 | func (h *nablaExecHandler) ExecCreateFunc(i *ll.ExecCreateInput) (*ll.LLState, error) { 27 | ret := &ll.LLState{} 28 | return ret, nil 29 | } 30 | 31 | func (h *nablaExecHandler) ExecRunFunc(i *ll.ExecRunInput) error { 32 | networkOptions := i.NetworkState.Options 33 | fsOptions := i.FsState.Options 34 | config := i.Config 35 | contRoot := i.ContainerRoot 36 | 37 | runncCont, err := newRunncCont(contRoot, *config, networkOptions, fsOptions) 38 | if err != nil { 39 | return errors.Wrap(err, "Unable to construct nabla run args") 40 | } 41 | 42 | // Shouldn't return 43 | return runncCont.Run() 44 | } 45 | 46 | func (h *nablaExecHandler) ExecDestroyFunc(i *ll.ExecDestroyInput) (*ll.LLState, error) { 47 | ret := &ll.LLState{} 48 | return ret, nil 49 | } 50 | 51 | func newRunncCont(containerRoot string, cfg configs.Config, networkMap map[string]string, fsMap map[string]string) (*runnc_cont.RunncCont, error) { 52 | if len(cfg.Args) == 0 { 53 | return nil, fmt.Errorf("OCI process args are empty") 54 | } 55 | 56 | if !strings.HasSuffix(cfg.Args[0], ".nabla") { 57 | return nil, fmt.Errorf("entrypoint is not a .nabla file") 58 | } 59 | 60 | cidr, err := strconv.Atoi(networkMap["IPMask"]) 61 | if err != nil { 62 | return nil, fmt.Errorf("Unablae to parse IPMask: %v", cidr) 63 | } 64 | 65 | c := runnc_cont.Config{ 66 | NablaRunBin: NablaRunBin, 67 | UniKernelBin: filepath.Join(containerRoot, cfg.Args[0]), 68 | Memory: cfg.Memory, 69 | Tap: networkMap["TapName"], 70 | Disk: []string{fsMap["FsPath"]}, 71 | WorkingDir: cfg.Cwd, 72 | Env: cfg.Env, 73 | NablaRunArgs: cfg.Args[1:], 74 | Mounts: cfg.Mounts, 75 | IPAddress: networkMap["IPAddress"], 76 | Mac: networkMap["Mac"], 77 | Gateway: networkMap["Gateway"], 78 | IPMask: cidr, 79 | } 80 | 81 | cont, err := runnc_cont.NewRunncCont(c) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | return cont, nil 87 | } 88 | -------------------------------------------------------------------------------- /llruntimes/nabla/runnc-cont/config.go: -------------------------------------------------------------------------------- 1 | package runnc_cont 2 | 3 | import ( 4 | spec "github.com/opencontainers/runtime-spec/specs-go" 5 | ) 6 | 7 | // Config configuration to create a runnc-cont 8 | type Config struct { 9 | // NablaRunBin is the path to 'nabla-run' binary. 10 | NablaRunBin string 11 | 12 | NablaRunArgs []string 13 | 14 | // UniKernelBin is the path to 'unikernel' binary. 15 | UniKernelBin string 16 | 17 | // Tap tap device. (e.g. tap100) 18 | Tap string 19 | 20 | IPAddress string 21 | IPMask int 22 | Gateway string 23 | Mac string 24 | 25 | // Memory max memory size in MBs. 26 | Memory int64 27 | 28 | // Disk is the path to disk 29 | Disk []string 30 | 31 | // WorkingDir current working directory. 32 | WorkingDir string 33 | 34 | // Env is a list of environment variables. 35 | Env []string 36 | 37 | // Mounts specify source and destination paths that will be copied 38 | // inside the container's rootfs. 39 | Mounts []spec.Mount 40 | } 41 | -------------------------------------------------------------------------------- /llruntimes/nabla/runnc-cont/rumprun.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, IBM 2 | // Author(s): Brandon Lum, Ricardo Koller, Dan Williams 3 | // 4 | // Permission to use, copy, modify, and/or distribute this software for 5 | // any purpose with or without fee is hereby granted, provided that the 6 | // above copyright notice and this permission notice appear in all 7 | // copies. 8 | // 9 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 | // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 | // WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 | // DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA 14 | // OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | // PERFORMANCE OF THIS SOFTWARE. 17 | 18 | // +build linux 19 | 20 | package runnc_cont 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | "net" 26 | "strconv" 27 | "strings" 28 | 29 | "github.com/nabla-containers/runnc/nabla-lib/network" 30 | ) 31 | 32 | type rumpArgsNetwork struct { 33 | If string `json:"if"` 34 | Cloner string `json:"cloner"` 35 | Type string `json:"type"` 36 | Method string `json:"method"` 37 | Addr string `json:"addr"` 38 | Mask string `json:"mask"` 39 | Gw string `json:"gw"` 40 | } 41 | 42 | type rumpArgsBlock struct { 43 | Source string `json:"source"` 44 | Path string `json:"path"` 45 | Fstype string `json:"fstype"` 46 | Mount string `json:"mountpoint"` 47 | } 48 | 49 | type rumpArgs struct { 50 | Cmdline string `json:"cmdline"` 51 | Net rumpArgsNetwork `json:"net"` 52 | Blk *rumpArgsBlock `json:"blk,omitempty"` 53 | Env []string `json:"env,omitempty"` 54 | Cwd string `json:"cwd,omitempty"` 55 | Mem string `json:"mem,omitempty"` 56 | } 57 | 58 | // Overwrite the rumprum args marshalling since rump expects multiple env 59 | // variables to be passed in a weird way. 60 | func (ra *rumpArgs) MarshalJSON() ([]byte, error) { 61 | // Create duplicate env variables due to consumption method of rump that 62 | // requires duplicate json keys. 63 | env := ra.Env 64 | type EnvAlias struct { 65 | Env string `json:"env,omitempty"` 66 | } 67 | 68 | addString := "" 69 | type CAlias struct { 70 | C string `json:"c,omitempty"` 71 | } 72 | 73 | for _, v := range env { 74 | vb, err := json.Marshal(&EnvAlias{v}) 75 | if err != nil { 76 | return nil, err 77 | } 78 | addString += string(vb[1:len(vb)-1]) + "," 79 | } 80 | 81 | // Marshal rest of the struct minus Env 82 | type Alias rumpArgs 83 | alias := &struct { 84 | *Alias 85 | }{ 86 | Alias: (*Alias)(ra), 87 | } 88 | 89 | alias.Env = nil 90 | otherBytes, err := json.Marshal(alias) 91 | if err != nil { 92 | return nil, err 93 | } 94 | alias.Env = env 95 | 96 | // Put bytes together 97 | modified := make([]byte, 0, len(otherBytes)+len(addString)) 98 | modified = append(modified, otherBytes[:1]...) 99 | modified = append(modified, []byte(addString)...) 100 | modified = append(modified, otherBytes[1:]...) 101 | 102 | return modified, nil 103 | } 104 | 105 | // CreateRumprunArgs returns the cmdline string for rumprun (a json) 106 | func CreateRumprunArgs(ip net.IP, mask net.IPMask, gw net.IP, 107 | mountPoint string, envVars []string, cwd string, 108 | unikernel string, cmdargs []string) (string, error) { 109 | 110 | // XXX: Due to bug in: https://github.com/nabla-containers/runnc/issues/40 111 | // If we detect a /32 mask, we set it to 1 as a "fix", and hope we are in 112 | // the same subnet... (working on a fix for mask:0) 113 | cidr := strconv.Itoa(network.MaskCIDR(mask)) 114 | if cidr == "32" { 115 | fmt.Printf("WARNING: Changing CIDR from 32 to 1 due to Issue https://github.com/nabla-containers/runnc/issues/40\n") 116 | cidr = "1" 117 | } 118 | 119 | net := rumpArgsNetwork{ 120 | If: "ukvmif0", 121 | Cloner: "True", 122 | Type: "inet", 123 | Method: "static", 124 | Addr: ip.String(), 125 | Mask: cidr, 126 | Gw: gw.String(), 127 | } 128 | 129 | cmdline := append([]string{unikernel}, cmdargs...) 130 | ra := &rumpArgs{ 131 | Cwd: cwd, 132 | Cmdline: strings.Join(cmdline, " "), 133 | Net: net, 134 | } 135 | if mountPoint != "" { 136 | block := rumpArgsBlock{ 137 | Source: "etfs", 138 | Path: "/dev/ld0a", 139 | Fstype: "blk", 140 | Mount: mountPoint, 141 | } 142 | ra.Blk = &block 143 | } 144 | 145 | if len(envVars) > 0 { 146 | ra.Env = envVars 147 | } 148 | 149 | b, err := json.Marshal(ra) 150 | if err != nil { 151 | return "", fmt.Errorf("error with rumprun json: %v", err) 152 | } 153 | 154 | return string(b), nil 155 | } 156 | -------------------------------------------------------------------------------- /llruntimes/nabla/runnc-cont/runnc_cont.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, IBM 2 | // Author(s): Brandon Lum, Ricardo Koller, Dan Williams 3 | // 4 | // Permission to use, copy, modify, and/or distribute this software for 5 | // any purpose with or without fee is hereby granted, provided that the 6 | // above copyright notice and this permission notice appear in all 7 | // copies. 8 | // 9 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 | // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 | // WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 | // DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA 14 | // OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | // PERFORMANCE OF THIS SOFTWARE. 17 | 18 | package runnc_cont 19 | 20 | import ( 21 | "fmt" 22 | "net" 23 | "os" 24 | "os/exec" 25 | "strconv" 26 | "strings" 27 | "syscall" 28 | 29 | "github.com/nabla-containers/runnc/nabla-lib/storage" 30 | spec "github.com/opencontainers/runtime-spec/specs-go" 31 | ) 32 | 33 | type RunncCont struct { 34 | // NablaRunBin is the path to 'nabla-run' binary. 35 | NablaRunBin string 36 | 37 | NablaRunArgs []string 38 | 39 | // UniKernelBin is the path to 'unikernel' binary. 40 | UniKernelBin string 41 | 42 | // Tap tap device. (e.g. tap100) 43 | Tap string 44 | 45 | IPAddress net.IP 46 | IPMask net.IPMask 47 | Gateway net.IP 48 | Mac string 49 | 50 | // Memory max memory size in MBs. 51 | Memory int64 52 | 53 | // Disk is the path to disk 54 | Disk string 55 | 56 | // WorkingDir current working directory. 57 | WorkingDir string 58 | 59 | // Env is a list of environment variables. 60 | Env []string 61 | 62 | // Mounts specify source and destination paths that will be copied 63 | // inside the container's rootfs. 64 | Mounts []spec.Mount 65 | } 66 | 67 | // NewRunncCont returns a brand new runnc-cont 68 | func NewRunncCont(cfg Config) (*RunncCont, error) { 69 | if len(cfg.Disk) < 1 { 70 | return nil, fmt.Errorf("No disk provided") 71 | } 72 | 73 | // If network details are specified, use them, if not do usual network plumbing 74 | if len(cfg.IPAddress) == 0 || len(cfg.Gateway) == 0 || len(cfg.Tap) == 0 { 75 | return nil, fmt.Errorf("Insufficient network arguments set") 76 | } 77 | netstr := fmt.Sprintf("%s/%d", cfg.IPAddress, cfg.IPMask) 78 | ipAddress, ipNet, err := net.ParseCIDR(netstr) 79 | if err != nil { 80 | return nil, fmt.Errorf("not a valid IP address: %s, err: %v", netstr, err) 81 | } 82 | 83 | ipMask := ipNet.Mask 84 | 85 | gateway := net.ParseIP(cfg.Gateway) 86 | if gateway == nil { 87 | return nil, fmt.Errorf("not a valid gateway address: %s", cfg.Gateway) 88 | } 89 | 90 | mac := "" 91 | if len(cfg.Mac) > 0 { 92 | if _, err := net.ParseMAC(cfg.Mac); err != nil { 93 | return nil, fmt.Errorf("not a valid mac addr: %s, err :%v", cfg.Mac, err) 94 | } 95 | mac = cfg.Mac 96 | } 97 | 98 | return &RunncCont{ 99 | NablaRunBin: cfg.NablaRunBin, 100 | NablaRunArgs: cfg.NablaRunArgs, 101 | UniKernelBin: cfg.UniKernelBin, 102 | Tap: cfg.Tap, 103 | IPAddress: ipAddress, 104 | IPMask: ipMask, 105 | Gateway: gateway, 106 | Mac: mac, 107 | Memory: cfg.Memory, 108 | Disk: cfg.Disk[0], 109 | WorkingDir: cfg.WorkingDir, 110 | Env: cfg.Env, 111 | Mounts: cfg.Mounts, 112 | }, nil 113 | } 114 | 115 | func setupDisk(path string) (string, error) { 116 | if path == "" { 117 | return storage.CreateDummy() 118 | } 119 | 120 | pathInfo, err := os.Stat(path) 121 | if err != nil { 122 | return "", fmt.Errorf( 123 | "can not find the disk or directory %s", path) 124 | } 125 | 126 | if pathInfo.Mode()&os.ModeDir != 0 { 127 | // path is a dir, so we flat it to an iso disk 128 | return "", fmt.Errorf("input storage %s is not an ISO", path) 129 | } 130 | 131 | // "path" is a file, so we treat it like a disk 132 | return path, nil 133 | } 134 | 135 | func (r *RunncCont) Run() error { 136 | var ( 137 | mac string 138 | err error 139 | ) 140 | 141 | disk, err := setupDisk(r.Disk) 142 | if err != nil { 143 | return fmt.Errorf("could not setup the disk: %v", err) 144 | } 145 | 146 | _, err = os.Stat(r.UniKernelBin) 147 | if err != nil { 148 | // If the unikernel path doesn't exist, look in $PATH 149 | unikernel, err := exec.LookPath(r.UniKernelBin) 150 | if err != nil { 151 | return fmt.Errorf("could not find the nabla file %s: %v", r.UniKernelBin, err) 152 | } 153 | r.UniKernelBin = unikernel 154 | } 155 | 156 | unikernelArgs, err := CreateRumprunArgs(r.IPAddress, r.IPMask, r.Gateway, "/", 157 | r.Env, r.WorkingDir, r.UniKernelBin, r.NablaRunArgs) 158 | if err != nil { 159 | return fmt.Errorf("could not create the unikernel cmdline: %v\n", err) 160 | } 161 | 162 | var args []string 163 | if mac != "" { 164 | args = []string{r.NablaRunBin, 165 | "--x-exec-heap", 166 | "--mem=" + strconv.FormatInt(r.Memory, 10), 167 | "--net-mac=" + mac, 168 | "--net=" + r.Tap, 169 | "--disk=" + disk, 170 | r.UniKernelBin, 171 | unikernelArgs} 172 | } else { 173 | args = []string{r.NablaRunBin, 174 | "--x-exec-heap", 175 | "--mem=" + strconv.FormatInt(r.Memory, 10), 176 | "--net=" + r.Tap, 177 | "--disk=" + disk, 178 | r.UniKernelBin, 179 | unikernelArgs} 180 | } 181 | 182 | fmt.Printf("nabla-run arg %s\n", args) 183 | 184 | // Set LD_LIBRARY_PATH to our dynamic libraries 185 | env := os.Environ() 186 | 187 | newenv := make([]string, 0, len(env)) 188 | for _, v := range env { 189 | if strings.HasPrefix(v, "LD_LIBRARY_PATH=") { 190 | continue 191 | } else { 192 | newenv = append(newenv, v) 193 | } 194 | } 195 | newenv = append(newenv, "LD_LIBRARY_PATH=/lib64") 196 | 197 | err = syscall.Exec(r.NablaRunBin, args, newenv) 198 | if err != nil { 199 | return fmt.Errorf("Err from execve: %v\n", err) 200 | } 201 | 202 | return nil 203 | } 204 | -------------------------------------------------------------------------------- /nabla-lib/network/network_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, IBM 2 | // Author(s): Brandon Lum, Ricardo Koller 3 | // 4 | // SPDX-License-Identifier: ISC 5 | // 6 | // Copyright (c) 2016 Intel Corporation 7 | // 8 | // SPDX-License-Identifier: Apache-2.0 9 | // 10 | // Permission to use, copy, modify, and/or distribute this software for 11 | // any purpose with or without fee is hereby granted, provided that the 12 | // above copyright notice and this permission notice appear in all 13 | // copies. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 16 | // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 17 | // WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 18 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 19 | // DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA 20 | // OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 21 | // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 22 | // PERFORMANCE OF THIS SOFTWARE. 23 | 24 | // +build linux 25 | 26 | package network 27 | 28 | import ( 29 | "fmt" 30 | "github.com/vishvananda/netlink" 31 | "golang.org/x/sys/unix" 32 | "io/ioutil" 33 | "math/rand" 34 | "net" 35 | "os" 36 | "strconv" 37 | "strings" 38 | "time" 39 | 40 | "github.com/pkg/errors" 41 | ) 42 | 43 | // CreateBridge creates and returns a netklink.Bridge 44 | func CreateBridge(bridgeName string) (*netlink.Bridge, error) { 45 | la := netlink.NewLinkAttrs() 46 | la.Name = bridgeName 47 | mybridge := &netlink.Bridge{LinkAttrs: la} 48 | err := netlink.LinkAdd(mybridge) 49 | if err != nil { 50 | return nil, fmt.Errorf("could not add %s: %v", la.Name, err) 51 | } 52 | return mybridge, err 53 | } 54 | 55 | // MaskCIDR returns a mask CIDR (the 16 in 1.1.1.1/16) for a net.IPMask 56 | func MaskCIDR(mask net.IPMask) int { 57 | m, _ := mask.Size() 58 | return int(m) 59 | } 60 | 61 | // CreateTapInterface creates a new TAP interface and assignes it ip/mask as 62 | // the new address. nil pointers to ip/mask indicates not to set ip/mask 63 | func CreateTapInterface(tapName string, ip *net.IP, mask *net.IPMask) error { 64 | 65 | err := SetupTunDev() 66 | if err != nil { 67 | return errors.Wrap(err, "Unable to get tun device ready") 68 | } 69 | 70 | // ip tuntap add %s mode tap 71 | tap := &netlink.Tuntap{ 72 | LinkAttrs: netlink.LinkAttrs{Name: tapName}, 73 | Mode: netlink.TUNTAP_MODE_TAP} 74 | err = netlink.LinkAdd(tap) 75 | if err != nil { 76 | return errors.Wrap(err, "Unable to add link") 77 | } 78 | 79 | if ip != nil && mask != nil { 80 | // ip addr add %s/%s dev %s 81 | netstr := fmt.Sprintf("%s/%d", (*ip).String(), MaskCIDR(*mask)) 82 | addr, err := netlink.ParseAddr(netstr) 83 | if err != nil { 84 | return errors.Wrap(err, "Unable to add ip/mask to link") 85 | } 86 | 87 | netlink.AddrAdd(tap, addr) 88 | } 89 | 90 | // ip link set dev %s up' 91 | err = netlink.LinkSetUp(tap) 92 | if err != nil { 93 | return errors.Wrap(err, "Unable to set tap to up") 94 | } 95 | return nil 96 | } 97 | 98 | // RemoveTapDevices removes the tap device with name tapName 99 | func RemoveTapDevice(tapName string) error { 100 | err := SetupTunDev() 101 | if err != nil { 102 | return err 103 | } 104 | 105 | // ip tuntap add %s mode tap 106 | tap := &netlink.Tuntap{ 107 | LinkAttrs: netlink.LinkAttrs{Name: tapName}, 108 | Mode: netlink.TUNTAP_MODE_TAP} 109 | return netlink.LinkDel(tap) 110 | } 111 | 112 | // createMacvtapInterface creates a macvtap interface with the attributes taken 113 | // from a master link interface. 114 | // returns the macvtap, name of the tap device, dev path of the tap device and err 115 | func createMacvtapInterface(netHandle *netlink.Handle, masterLink netlink.Link) (*netlink.Macvtap, string, string, error) { 116 | masterLinkAttrs := masterLink.Attrs() 117 | 118 | rand.Seed(time.Now().Unix()) 119 | 120 | qlen := masterLinkAttrs.TxQLen 121 | if qlen <= 0 { 122 | qlen = 1000 123 | } 124 | 125 | index := 8192 + rand.Intn(1024) 126 | name := fmt.Sprintf("macvtap%d", index) 127 | 128 | macvtapLink := &netlink.Macvtap{ 129 | Macvlan: netlink.Macvlan{ 130 | Mode: netlink.MACVLAN_MODE_BRIDGE, 131 | LinkAttrs: netlink.LinkAttrs{ 132 | Index: index, 133 | Name: name, 134 | TxQLen: qlen, 135 | ParentIndex: masterLinkAttrs.Index, 136 | }, 137 | }, 138 | } 139 | 140 | err := netHandle.LinkAdd(macvtapLink) 141 | if err != nil { 142 | return nil, "", "", fmt.Errorf("Couldn't add newlink: %v", err) 143 | } 144 | 145 | return macvtapLink, name, fmt.Sprintf("/dev/tap%d", macvtapLink.Attrs().Index), nil 146 | } 147 | 148 | // CreateMacvtapInterfaceDocker creates a Macvtap interface associated with 149 | // master (usually "eth0"). Returns the assigned IP/mask and gateway IP 150 | // (previously owned by master) and the MAC of the Macvtap interface that has 151 | // to be used by the unikernel's NIC. 152 | // 153 | // Got the idea of using macvtap's and the fix for the inability to get the 154 | // right index in a network namespace from the Kata containers repository: 155 | // https://github.com/kata-containers/runtime/blob/593bd44f207aa7b21e561184ca1b3fb79da47eb6/virtcontainers/network.go 156 | // 157 | func CreateMacvtapInterfaceDocker(master string) ( 158 | net.IP, net.IP, net.IPMask, string, string, error) { 159 | 160 | netHandle, err := netlink.NewHandle() 161 | if err != nil { 162 | return nil, nil, nil, "", "", errors.Wrap(err, "Unable to create netlink handler") 163 | } 164 | 165 | err = SetupTunDev() 166 | if err != nil { 167 | return nil, nil, nil, "", "", errors.Wrap(err, "Unable to setup tun dev") 168 | } 169 | 170 | masterLink, err := netlink.LinkByName(master) 171 | if err != nil { 172 | return nil, nil, nil, "", "", errors.Wrap(err, "no master interface: %v") 173 | } 174 | 175 | macvtapLink, name, newTapName, err := createMacvtapInterface(netHandle, masterLink) 176 | if err != nil { 177 | return nil, nil, nil, "", "", errors.Wrap(err, "Unable to create Macvtapint") 178 | } 179 | 180 | addrs, err := netlink.AddrList(masterLink, netlink.FAMILY_V4) 181 | if err != nil { 182 | return nil, nil, nil, "", "", errors.Wrap(err, "Unable to get address list") 183 | } 184 | if len(addrs) == 0 { 185 | return nil, nil, nil, "", "", fmt.Errorf("master should have an IP") 186 | } 187 | masterAddr := addrs[0] 188 | masterIP := addrs[0].IPNet.IP 189 | masterMask := addrs[0].IPNet.Mask 190 | 191 | routes, err := netlink.RouteList(masterLink, netlink.FAMILY_V4) 192 | if err != nil { 193 | return nil, nil, nil, "", "", errors.Wrap(err, "Unable to get route list") 194 | } 195 | if len(routes) == 0 { 196 | return nil, nil, nil, "", "", fmt.Errorf("master should have at least one route") 197 | } 198 | // XXX: is the "gateway" always the first route? 199 | gwAddr := routes[0].Gw 200 | 201 | // ip addr del $INET_STR dev master 202 | err = netlink.AddrDel(masterLink, &masterAddr) 203 | if err != nil { 204 | return nil, nil, nil, "", "", errors.Wrap(err, "Unable to delete address of master") 205 | } 206 | 207 | err = netlink.LinkSetUp(macvtapLink) 208 | if err != nil { 209 | return nil, nil, nil, "", "", errors.Wrap(err, "Unable to set up tap link") 210 | } 211 | 212 | err = netlink.LinkSetUp(masterLink) 213 | if err != nil { 214 | return nil, nil, nil, "", "", errors.Wrap(err, "Unable to set up master link") 215 | } 216 | 217 | // The HardwareAddr Attr doesn't automatically get updated 218 | _macvtapLink, err := netlink.LinkByName(name) 219 | if err != nil { 220 | return nil, nil, nil, "", "", err 221 | } 222 | tapMac := _macvtapLink.Attrs().HardwareAddr.String() 223 | 224 | d := fmt.Sprintf("/sys/devices/virtual/net/%s/tap%d/dev", 225 | name, macvtapLink.Attrs().Index) 226 | b, err := ioutil.ReadFile(d) 227 | if err != nil { 228 | return nil, nil, nil, "", "", err 229 | } 230 | 231 | mm := strings.Split(string(b), ":") 232 | major, err := strconv.Atoi(strings.TrimSpace(mm[0])) 233 | if err != nil { 234 | return nil, nil, nil, "", "", err 235 | } 236 | 237 | minor, err := strconv.Atoi(strings.TrimSpace(mm[1])) 238 | if err != nil { 239 | return nil, nil, nil, "", "", err 240 | } 241 | 242 | err = unix.Mknod(newTapName, unix.S_IFCHR|0600, 243 | int(unix.Mkdev(uint32(major), uint32(minor)))) 244 | if err != nil { 245 | return nil, nil, nil, "", "", err 246 | } 247 | 248 | return masterIP, gwAddr, masterMask, tapMac, newTapName, nil 249 | } 250 | 251 | func getMasterDetails(masterLink netlink.Link) (masterAddr *netlink.Addr, masterIP net.IP, masterMask net.IPMask, gwAddr net.IP, mac string, err error) { 252 | addrs, err := netlink.AddrList(masterLink, netlink.FAMILY_V4) 253 | if err != nil { 254 | return nil, nil, nil, nil, "", err 255 | } 256 | if len(addrs) == 0 { 257 | return nil, nil, nil, nil, "", fmt.Errorf("master should have an IP") 258 | } 259 | masterAddr = &addrs[0] 260 | masterIP = addrs[0].IPNet.IP 261 | masterMask = addrs[0].IPNet.Mask 262 | 263 | routes, err := netlink.RouteList(masterLink, netlink.FAMILY_V4) 264 | if err != nil { 265 | return nil, nil, nil, nil, "", err 266 | } 267 | if len(routes) == 0 { 268 | return nil, nil, nil, nil, "", 269 | fmt.Errorf("master should have at least one route") 270 | } 271 | // XXX: is the "gateway" always the first route? 272 | gwAddr = routes[0].Gw 273 | 274 | macAddr := masterLink.Attrs().HardwareAddr.String() 275 | return masterAddr, masterIP, masterMask, gwAddr, macAddr, nil 276 | } 277 | 278 | // CreateTapInterfaceDocker creates a new TAP interface and a bridge, adds both 279 | // the TAP and the master link (usually eth0) to the bridge, and unsets the IP 280 | // of the master link to be used by the unikernel NIC. Returns the assigned 281 | // IP/mask and gateway IP. 282 | func CreateTapInterfaceDocker(tapName string, master string) ( 283 | net.IP, net.IP, net.IPMask, string, error) { 284 | 285 | masterLink, err := netlink.LinkByName(master) 286 | if err != nil { 287 | return nil, nil, nil, "", 288 | fmt.Errorf("no master interface: %v", err) 289 | } 290 | masterAddr, masterIP, masterMask, gwAddr, mac, err := getMasterDetails(masterLink) 291 | if err != nil { 292 | return nil, nil, nil, "", err 293 | } 294 | 295 | err = SetupTunDev() 296 | if err != nil { 297 | return nil, nil, nil, "", err 298 | } 299 | 300 | // ip tuntap add tap100 mode tap 301 | tap := &netlink.Tuntap{ 302 | LinkAttrs: netlink.LinkAttrs{Name: tapName}, 303 | Mode: netlink.TUNTAP_MODE_TAP} 304 | err = netlink.LinkAdd(tap) 305 | if err != nil { 306 | return nil, nil, nil, "", err 307 | } 308 | 309 | // ip link set dev tap100 up' 310 | err = netlink.LinkSetUp(tap) 311 | if err != nil { 312 | return nil, nil, nil, "", err 313 | } 314 | 315 | // ip addr del $INET_STR dev master 316 | err = netlink.AddrDel(masterLink, masterAddr) 317 | if err != nil { 318 | return nil, nil, nil, "", err 319 | } 320 | 321 | genmac, err := net.ParseMAC("aa:aa:aa:aa:bb:cc") 322 | if err != nil { 323 | return nil, nil, nil, "", err 324 | } 325 | 326 | err = netlink.LinkSetHardwareAddr(masterLink, genmac) 327 | if err != nil { 328 | return nil, nil, nil, "", err 329 | } 330 | 331 | br0, err := CreateBridge("br0") 332 | if err != nil { 333 | return nil, nil, nil, "", err 334 | } 335 | 336 | netlink.LinkSetMaster(masterLink, br0) 337 | netlink.LinkSetMaster(tap, br0) 338 | 339 | // ip link set dev br0 up' 340 | err = netlink.LinkSetUp(br0) 341 | if err != nil { 342 | return nil, nil, nil, "", err 343 | } 344 | return masterIP, gwAddr, masterMask, mac, nil 345 | } 346 | 347 | // SetupTunDev sets up the /dev/net/tun device if it doesn't exists 348 | func SetupTunDev() error { 349 | // Check if tun device exists and create it if required 350 | if err := verifyTunDevice(); err != nil { 351 | if err = createTunDevice(); err != nil { 352 | return fmt.Errorf("Unable to create /dev/net/tun: %v", 353 | err) 354 | } 355 | } else { 356 | return nil 357 | } 358 | 359 | // Make sure that it is the correct device we're talking to 360 | err := verifyTunDevice() 361 | 362 | return err 363 | } 364 | 365 | // createTunDevice Create directory /dev/net and create tun char device M 10 m 366 | // 200 367 | func createTunDevice() error { 368 | // Check for directory /dev/net 369 | devNetInfo, err := os.Stat("/dev/net") 370 | if err == nil { 371 | // Check that it is a directory 372 | if devNetInfo.Mode()&os.ModeDir == 0 { 373 | return fmt.Errorf("/dev/net is not a directory") 374 | } 375 | // Check if dir did not exist, create it 376 | } else if os.IsNotExist(err) { 377 | err = os.Mkdir("/dev/net", 0755) 378 | if err != nil { 379 | return err 380 | } 381 | } else { 382 | return err 383 | } 384 | 385 | // Casting to int is safe since it preserves MSB, Mkdev produces 64-bit and 386 | // it is backward compatible. 387 | // ref: https://github.com/golang/sys/blob/master/unix/dev_linux.go 388 | err = unix.Mknod("/dev/net/tun", unix.S_IFCHR|0666, int(unix.Mkdev(10, 200))) 389 | if err != nil { 390 | return err 391 | } 392 | 393 | return nil 394 | } 395 | 396 | // verifyTunDevice verifies the /dev/net/tun device that it is char device M 10 m 200 397 | func verifyTunDevice() error { 398 | var st unix.Stat_t 399 | err := unix.Stat("/dev/net/tun", &st) 400 | if err != nil { 401 | return err 402 | } 403 | 404 | // File exists, check character device name 405 | maj := unix.Major(uint64(st.Rdev)) 406 | min := unix.Minor(uint64(st.Rdev)) 407 | if maj != 10 || min != 200 { 408 | return fmt.Errorf("Expected /dev/net/tun to have M/m %d/%d, got %d/%d", 10, 200, maj, min) 409 | } 410 | 411 | return nil 412 | } 413 | -------------------------------------------------------------------------------- /nabla-lib/storage/storage_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, IBM 2 | // Author(s): Brandon Lum, Ricardo Koller 3 | // 4 | // Permission to use, copy, modify, and/or distribute this software for 5 | // any purpose with or without fee is hereby granted, provided that the 6 | // above copyright notice and this permission notice appear in all 7 | // copies. 8 | // 9 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 | // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 | // WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 | // DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA 14 | // OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | // PERFORMANCE OF THIS SOFTWARE. 17 | 18 | // +build linux 19 | 20 | package storage 21 | 22 | import ( 23 | "github.com/pkg/errors" 24 | "io/ioutil" 25 | "os/exec" 26 | "path/filepath" 27 | ) 28 | 29 | // CreateDummy creates a dummy file in /tmp 30 | func CreateDummy() (string, error) { 31 | file, err := ioutil.TempFile("/tmp", "nabla") 32 | if err != nil { 33 | return "", err 34 | } 35 | return file.Name(), nil 36 | } 37 | 38 | // CreateIso creates an ISO from the dir argument 39 | func CreateIso(dir string, target *string) (string, error) { 40 | var fname string 41 | 42 | if target == nil { 43 | f, err := ioutil.TempFile("/tmp", "nabla") 44 | if err != nil { 45 | return "", err 46 | } 47 | 48 | fname = f.Name() 49 | if err := f.Close(); err != nil { 50 | return "", err 51 | } 52 | } else { 53 | var err error 54 | fname, err = filepath.Abs(*target) 55 | if err != nil { 56 | return "", errors.Wrap(err, "Unable to resolve abs target path") 57 | } 58 | } 59 | 60 | absDir, err := filepath.Abs(dir) 61 | if err != nil { 62 | return "", errors.Wrap(err, "Unable to resolve abs dir path") 63 | } 64 | 65 | cmd := exec.Command("genisoimage", "-m", "dev", "-m", "sys", 66 | "-m", "proc", "-l", "-r", "-o", fname, absDir) 67 | err = cmd.Run() 68 | if err != nil { 69 | return "", errors.Wrap(err, "Unable to run geniso command") 70 | } 71 | 72 | return fname, nil 73 | } 74 | -------------------------------------------------------------------------------- /runnc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/nabla-containers/runnc/llcli" 5 | ll "github.com/nabla-containers/runnc/llif" 6 | llfs "github.com/nabla-containers/runnc/llmodules/fs" 7 | llnet "github.com/nabla-containers/runnc/llmodules/network" 8 | llnabla "github.com/nabla-containers/runnc/llruntimes/nabla" 9 | ) 10 | 11 | func main() { 12 | fsH, err := llfs.NewISOFsHandler() 13 | if err != nil { 14 | panic(err) 15 | } 16 | networkH, err := llnet.NewTapBrNetworkHandler() 17 | if err != nil { 18 | panic(err) 19 | } 20 | execH, err := llnabla.NewNablaExecHandler() 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | nablaLLCHandler := ll.RunllcHandler{ 26 | FsH: fsH, 27 | NetworkH: networkH, 28 | ExecH: execH, 29 | } 30 | 31 | // We run the OCI runtime called "runnc", with root dir "/run/runnc" 32 | // with the low level handlers chosen above. 33 | llcli.Runllc("runnc", "/run/runnc", nablaLLCHandler) 34 | } 35 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | bats-core is a git subtree of `bats-core/bats-core:/libexec`. The commands used to create this subtree were (getting the latest changes from bats-core should be done using the same commands): 2 | 3 | ``` 4 | git checkout bats-core/master 5 | git subtree split -P libexec -b temporary-bats-branch 6 | git checkout tests-in-bats 7 | git subtree add --squash -P tests/bats-core temporary-bats-branch 8 | ``` 9 | -------------------------------------------------------------------------------- /tests/bats-core/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 bats-core contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | --- 23 | 24 | * [bats-core] is a continuation of [bats]. Copyright for portions of the 25 | bats-core project are held by Sam Stephenson, 2014 as part of the project 26 | [bats], licensed under MIT: 27 | 28 | Copyright (c) 2014 Sam Stephenson 29 | 30 | Permission is hereby granted, free of charge, to any person obtaining 31 | a copy of this software and associated documentation files (the 32 | "Software"), to deal in the Software without restriction, including 33 | without limitation the rights to use, copy, modify, merge, publish, 34 | distribute, sublicense, and/or sell copies of the Software, and to 35 | permit persons to whom the Software is furnished to do so, subject to 36 | the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be 39 | included in all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 42 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 43 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 44 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 45 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 46 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 47 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | 49 | For details, please see the [version control history][commits]. 50 | 51 | [bats-core]: https://github.com/bats-core/bats-core 52 | [bats]:https://github.com/sstephenson/bats 53 | [commits]:https://github.com/bats-core/bats-core/commits/master 54 | -------------------------------------------------------------------------------- /tests/bats-core/bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | version() { 5 | echo "Bats 0.4.0" 6 | } 7 | 8 | usage() { 9 | version 10 | echo "Usage: bats [-c] [-p | -t] [ ...]" 11 | } 12 | 13 | help() { 14 | usage 15 | echo 16 | echo " is the path to a Bats test file, or the path to a directory" 17 | echo " containing Bats test files." 18 | echo 19 | echo " -c, --count Count the number of test cases without running any tests" 20 | echo " -h, --help Display this help message" 21 | echo " -p, --pretty Show results in pretty format (default for terminals)" 22 | echo " -t, --tap Show results in TAP format" 23 | echo " -v, --version Display the version number" 24 | echo 25 | echo " For more information, see https://github.com/bats-core/bats-core" 26 | echo 27 | } 28 | 29 | BATS_READLINK= 30 | 31 | resolve_link() { 32 | if [[ -z "$BATS_READLINK" ]]; then 33 | if command -v 'greadlink' >/dev/null; then 34 | BATS_READLINK='greadlink' 35 | elif command -v 'readlink' >/dev/null; then 36 | BATS_READLINK='readlink' 37 | else 38 | BATS_READLINK='true' 39 | fi 40 | fi 41 | "$BATS_READLINK" "$1" || return 0 42 | } 43 | 44 | abs_dirname() { 45 | local cwd="$PWD" 46 | local path="$1" 47 | 48 | while [ -n "$path" ]; do 49 | cd "${path%/*}" 50 | local name="${path##*/}" 51 | path="$(resolve_link "$name")" 52 | done 53 | 54 | printf -v "$2" -- '%s' "$PWD" 55 | cd "$cwd" 56 | } 57 | 58 | expand_path() { 59 | local path="${1%/}" 60 | local dirname="${path%/*}" 61 | 62 | if [[ "$dirname" == "$path" ]]; then 63 | dirname="$PWD" 64 | elif cd "$dirname" 2>/dev/null; then 65 | dirname="$PWD" 66 | cd "$OLDPWD" 67 | else 68 | printf '%s' "$path" 69 | return 70 | fi 71 | printf -v "$2" '%s/%s' "$dirname" "${path##*/}" 72 | } 73 | 74 | abs_dirname "$0" 'BATS_LIBEXEC' 75 | abs_dirname "$BATS_LIBEXEC" 'BATS_PREFIX' 76 | abs_dirname '.' 'BATS_CWD' 77 | 78 | export BATS_PREFIX 79 | export BATS_CWD 80 | export BATS_TEST_PATTERN="^[[:blank:]]*@test[[:blank:]]+(.*[^[:blank:]])[[:blank:]]+\{(.*)\$" 81 | export PATH="$BATS_LIBEXEC:$PATH" 82 | 83 | options=() 84 | arguments=() 85 | for arg in "$@"; do 86 | if [ "${arg:0:1}" = "-" ]; then 87 | if [ "${arg:1:1}" = "-" ]; then 88 | options[${#options[*]}]="${arg:2}" 89 | else 90 | index=1 91 | while option="${arg:$index:1}"; do 92 | [ -n "$option" ] || break 93 | options[${#options[*]}]="$option" 94 | let index+=1 95 | done 96 | fi 97 | else 98 | arguments[${#arguments[*]}]="$arg" 99 | fi 100 | done 101 | 102 | unset count_flag pretty 103 | count_flag='' 104 | pretty='' 105 | [ -t 0 ] && [ -t 1 ] && pretty="1" 106 | [ -n "${CI:-}" ] && pretty="" 107 | 108 | if [[ "${#options[@]}" -ne '0' ]]; then 109 | for option in "${options[@]}"; do 110 | case "$option" in 111 | "h" | "help" ) 112 | help 113 | exit 0 114 | ;; 115 | "v" | "version" ) 116 | version 117 | exit 0 118 | ;; 119 | "c" | "count" ) 120 | count_flag="-c" 121 | ;; 122 | "t" | "tap" ) 123 | pretty="" 124 | ;; 125 | "p" | "pretty" ) 126 | pretty="1" 127 | ;; 128 | * ) 129 | usage >&2 130 | exit 1 131 | ;; 132 | esac 133 | done 134 | fi 135 | 136 | if [ "${#arguments[@]}" -eq 0 ]; then 137 | usage >&2 138 | exit 1 139 | fi 140 | 141 | filenames=() 142 | for filename in "${arguments[@]}"; do 143 | expand_path "$filename" 'filename' 144 | 145 | if [ -d "$filename" ]; then 146 | shopt -s nullglob 147 | for suite_filename in "$filename"/*.bats; do 148 | filenames["${#filenames[@]}"]="$suite_filename" 149 | done 150 | shopt -u nullglob 151 | else 152 | filenames["${#filenames[@]}"]="$filename" 153 | fi 154 | done 155 | 156 | if [ "${#filenames[@]}" -eq 1 ]; then 157 | command="bats-exec-test" 158 | else 159 | command="bats-exec-suite" 160 | fi 161 | 162 | set -o pipefail execfail 163 | if [ -z "$pretty" ]; then 164 | exec "$command" $count_flag "${filenames[@]}" 165 | else 166 | extended_syntax_flag="-x" 167 | formatter="bats-format-tap-stream" 168 | exec "$command" $count_flag $extended_syntax_flag "${filenames[@]}" | "$formatter" 169 | fi 170 | -------------------------------------------------------------------------------- /tests/bats-core/bats-exec-suite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | count_only_flag="" 5 | if [ "$1" = "-c" ]; then 6 | count_only_flag=1 7 | shift 8 | fi 9 | 10 | extended_syntax_flag="" 11 | if [ "$1" = "-x" ]; then 12 | extended_syntax_flag="-x" 13 | shift 14 | fi 15 | 16 | trap "kill 0; exit 1" int 17 | 18 | count=0 19 | for filename in "$@"; do 20 | while IFS= read -r line; do 21 | if [[ "$line" =~ $BATS_TEST_PATTERN ]]; then 22 | let count+=1 23 | fi 24 | done <"$filename" 25 | done 26 | 27 | if [ -n "$count_only_flag" ]; then 28 | echo "$count" 29 | exit 30 | fi 31 | 32 | echo "1..$count" 33 | status=0 34 | offset=0 35 | for filename in "$@"; do 36 | index=0 37 | { 38 | IFS= read -r # 1..n 39 | while IFS= read -r line; do 40 | case "$line" in 41 | "begin "* ) 42 | let index+=1 43 | echo "${line/ $index / $(($offset + $index)) }" 44 | ;; 45 | "ok "* | "not ok "* ) 46 | [ -n "$extended_syntax_flag" ] || let index+=1 47 | echo "${line/ $index / $(($offset + $index)) }" 48 | [ "${line:0:6}" != "not ok" ] || status=1 49 | ;; 50 | * ) 51 | echo "$line" 52 | ;; 53 | esac 54 | done 55 | } < <( bats-exec-test $extended_syntax_flag "$filename" ) 56 | offset=$(($offset + $index)) 57 | done 58 | 59 | exit "$status" 60 | -------------------------------------------------------------------------------- /tests/bats-core/bats-exec-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -E 4 | set -T 5 | 6 | BATS_COUNT_ONLY="" 7 | if [ "$1" = "-c" ]; then 8 | BATS_COUNT_ONLY=1 9 | shift 10 | fi 11 | 12 | BATS_EXTENDED_SYNTAX="" 13 | if [ "$1" = "-x" ]; then 14 | BATS_EXTENDED_SYNTAX="$1" 15 | shift 16 | fi 17 | 18 | BATS_TEST_FILENAME="$1" 19 | if [ -z "$BATS_TEST_FILENAME" ]; then 20 | echo "usage: bats-exec " >&2 21 | exit 1 22 | elif [ ! -f "$BATS_TEST_FILENAME" ]; then 23 | echo "bats: $BATS_TEST_FILENAME does not exist" >&2 24 | exit 1 25 | else 26 | shift 27 | fi 28 | 29 | BATS_TEST_DIRNAME="${BATS_TEST_FILENAME%/*}" 30 | BATS_TEST_NAMES=() 31 | 32 | load() { 33 | local name="$1" 34 | local filename 35 | 36 | if [ "${name:0:1}" = "/" ]; then 37 | filename="${name}" 38 | else 39 | filename="$BATS_TEST_DIRNAME/${name}.bash" 40 | fi 41 | 42 | if [[ ! -f "$filename" ]]; then 43 | echo "bats: $filename does not exist" >&2 44 | exit 1 45 | fi 46 | 47 | source "${filename}" 48 | } 49 | 50 | run() { 51 | local e E T oldIFS 52 | [[ ! "$-" =~ e ]] || e=1 53 | [[ ! "$-" =~ E ]] || E=1 54 | [[ ! "$-" =~ T ]] || T=1 55 | set +e 56 | set +E 57 | set +T 58 | output="$("$@" 2>&1)" 59 | status="$?" 60 | oldIFS=$IFS 61 | IFS=$'\n' lines=($output) 62 | [ -z "$e" ] || set -e 63 | [ -z "$E" ] || set -E 64 | [ -z "$T" ] || set -T 65 | IFS=$oldIFS 66 | } 67 | 68 | setup() { 69 | true 70 | } 71 | 72 | teardown() { 73 | true 74 | } 75 | 76 | BATS_TEST_SKIPPED='' 77 | skip() { 78 | BATS_TEST_SKIPPED=${1:-1} 79 | BATS_TEST_COMPLETED=1 80 | exit 0 81 | } 82 | 83 | bats_test_begin() { 84 | BATS_TEST_DESCRIPTION="$1" 85 | if [ -n "$BATS_EXTENDED_SYNTAX" ]; then 86 | echo "begin $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3 87 | fi 88 | setup 89 | } 90 | 91 | bats_test_function() { 92 | local test_name="$1" 93 | BATS_TEST_NAMES+=("$test_name") 94 | } 95 | 96 | BATS_CURRENT_STACK_TRACE=() 97 | BATS_PREVIOUS_STACK_TRACE=() 98 | 99 | bats_capture_stack_trace() { 100 | if [[ "${#BATS_CURRENT_STACK_TRACE[@]}" -ne '0' ]]; then 101 | BATS_PREVIOUS_STACK_TRACE=("${BATS_CURRENT_STACK_TRACE[@]}") 102 | fi 103 | BATS_CURRENT_STACK_TRACE=() 104 | 105 | local test_pattern=" $BATS_TEST_NAME $BATS_TEST_SOURCE" 106 | local setup_pattern=" setup $BATS_TEST_SOURCE" 107 | local teardown_pattern=" teardown $BATS_TEST_SOURCE" 108 | 109 | local frame 110 | local i 111 | 112 | for ((i=2; i != ${#FUNCNAME[@]}; ++i)); do 113 | frame="${BASH_LINENO[$((i-1))]} ${FUNCNAME[$i]} ${BASH_SOURCE[$i]}" 114 | BATS_CURRENT_STACK_TRACE["${#BATS_CURRENT_STACK_TRACE[@]}"]="$frame" 115 | if [[ "$frame" = *"$test_pattern" || \ 116 | "$frame" = *"$setup_pattern" || \ 117 | "$frame" = *"$teardown_pattern" ]]; then 118 | break 119 | fi 120 | done 121 | 122 | bats_frame_filename "${BATS_CURRENT_STACK_TRACE[0]}" 'BATS_SOURCE' 123 | bats_frame_lineno "${BATS_CURRENT_STACK_TRACE[0]}" 'BATS_LINENO' 124 | } 125 | 126 | bats_print_stack_trace() { 127 | local frame 128 | local index=1 129 | local count="${#@}" 130 | local filename 131 | local lineno 132 | 133 | for frame in "$@"; do 134 | bats_frame_filename "$frame" 'filename' 135 | bats_trim_filename "$filename" 'filename' 136 | bats_frame_lineno "$frame" 'lineno' 137 | 138 | if [ $index -eq 1 ]; then 139 | echo -n "# (" 140 | else 141 | echo -n "# " 142 | fi 143 | 144 | local fn 145 | bats_frame_function "$frame" 'fn' 146 | if [ "$fn" != "$BATS_TEST_NAME" ]; then 147 | echo -n "from function \`$fn' " 148 | fi 149 | 150 | if [ $index -eq $count ]; then 151 | echo "in test file $filename, line $lineno)" 152 | else 153 | echo "in file $filename, line $lineno," 154 | fi 155 | 156 | let index+=1 157 | done 158 | } 159 | 160 | bats_print_failed_command() { 161 | local frame="$1" 162 | local status="$2" 163 | local filename 164 | local lineno 165 | local failed_line 166 | local failed_command 167 | 168 | bats_frame_filename "$frame" 'filename' 169 | bats_frame_lineno "$frame" 'lineno' 170 | bats_extract_line "$filename" "$lineno" 'failed_line' 171 | bats_strip_string "$failed_line" 'failed_command' 172 | printf '%s' "# \`${failed_command}' " 173 | 174 | if [ $status -eq 1 ]; then 175 | echo "failed" 176 | else 177 | echo "failed with status $status" 178 | fi 179 | } 180 | 181 | bats_frame_lineno() { 182 | printf -v "$2" '%s' "${1%% *}" 183 | } 184 | 185 | bats_frame_function() { 186 | local __bff_function="${1#* }" 187 | printf -v "$2" '%s' "${__bff_function%% *}" 188 | } 189 | 190 | bats_frame_filename() { 191 | local __bff_filename="${1#* }" 192 | __bff_filename="${__bff_filename#* }" 193 | 194 | if [ "$__bff_filename" = "$BATS_TEST_SOURCE" ]; then 195 | __bff_filename="$BATS_TEST_FILENAME" 196 | fi 197 | printf -v "$2" '%s' "$__bff_filename" 198 | } 199 | 200 | bats_extract_line() { 201 | local __bats_extract_line_line 202 | local __bats_extract_line_index='0' 203 | 204 | while IFS= read -r __bats_extract_line_line; do 205 | if [[ "$((++__bats_extract_line_index))" -eq "$2" ]]; then 206 | printf -v "$3" '%s' "${__bats_extract_line_line%$'\r'}" 207 | break 208 | fi 209 | done <"$1" 210 | } 211 | 212 | bats_strip_string() { 213 | [[ "$1" =~ ^[[:space:]]*(.*)[[:space:]]*$ ]] 214 | printf -v "$2" '%s' "${BASH_REMATCH[1]}" 215 | } 216 | 217 | bats_trim_filename() { 218 | if [[ "$1" =~ ^${BATS_CWD}/ ]]; then 219 | printf -v "$2" '%s' "${1#$BATS_CWD/}" 220 | else 221 | printf -v "$2" '%s' "$1" 222 | fi 223 | } 224 | 225 | bats_debug_trap() { 226 | if [ "$BASH_SOURCE" != "$1" ]; then 227 | bats_capture_stack_trace 228 | fi 229 | } 230 | 231 | # When running under Bash 3.2.57(1)-release on macOS, the `ERR` trap may not 232 | # always fire, but the `EXIT` trap will. For this reason we call it at the very 233 | # beginning of `bats_teardown_trap` (the `DEBUG` trap for the call will move 234 | # `BATS_CURRENT_STACK_TRACE` to `BATS_PREVIOUS_STACK_TRACE`) and check the value 235 | # of `$?` before taking other actions. 236 | bats_error_trap() { 237 | local status="$?" 238 | if [[ "$status" -ne '0' ]]; then 239 | BATS_ERROR_STATUS="$status" 240 | BATS_ERROR_STACK_TRACE=( "${BATS_PREVIOUS_STACK_TRACE[@]}" ) 241 | trap - debug 242 | fi 243 | } 244 | 245 | bats_teardown_trap() { 246 | bats_error_trap 247 | trap "bats_exit_trap" exit 248 | local status=0 249 | teardown >>"$BATS_OUT" 2>&1 || status="$?" 250 | 251 | if [ $status -eq 0 ]; then 252 | BATS_TEARDOWN_COMPLETED=1 253 | elif [ -n "$BATS_TEST_COMPLETED" ]; then 254 | BATS_ERROR_STATUS="$status" 255 | BATS_ERROR_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" ) 256 | fi 257 | 258 | bats_exit_trap 259 | } 260 | 261 | bats_exit_trap() { 262 | local status 263 | local skipped='' 264 | trap - err exit 265 | 266 | if [ -n "$BATS_TEST_SKIPPED" ]; then 267 | skipped=" # skip" 268 | if [ "1" != "$BATS_TEST_SKIPPED" ]; then 269 | skipped+=" $BATS_TEST_SKIPPED" 270 | fi 271 | fi 272 | 273 | if [ -z "$BATS_TEST_COMPLETED" ] || [ -z "$BATS_TEARDOWN_COMPLETED" ]; then 274 | echo "not ok $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3 275 | bats_print_stack_trace "${BATS_ERROR_STACK_TRACE[@]}" >&3 276 | bats_print_failed_command "${BATS_ERROR_STACK_TRACE[${#BATS_ERROR_STACK_TRACE[@]}-1]}" "$BATS_ERROR_STATUS" >&3 277 | sed -e "s/^/# /" < "$BATS_OUT" >&3 278 | status=1 279 | else 280 | echo "ok ${BATS_TEST_NUMBER} ${BATS_TEST_DESCRIPTION}${skipped}" >&3 281 | status=0 282 | fi 283 | 284 | rm -f "$BATS_OUT" 285 | exit "$status" 286 | } 287 | 288 | bats_perform_tests() { 289 | echo "1..$#" 290 | test_number=1 291 | status=0 292 | for test_name in "$@"; do 293 | "$0" $BATS_EXTENDED_SYNTAX "$BATS_TEST_FILENAME" "$test_name" "$test_number" || status=1 294 | let test_number+=1 295 | done 296 | exit "$status" 297 | } 298 | 299 | bats_perform_test() { 300 | BATS_TEST_NAME="$1" 301 | if declare -F "$BATS_TEST_NAME" >/dev/null; then 302 | BATS_TEST_NUMBER="$2" 303 | if [ -z "$BATS_TEST_NUMBER" ]; then 304 | echo "1..1" 305 | BATS_TEST_NUMBER="1" 306 | fi 307 | 308 | BATS_TEST_COMPLETED="" 309 | BATS_TEARDOWN_COMPLETED="" 310 | trap "bats_debug_trap \"\$BASH_SOURCE\"" debug 311 | trap "bats_error_trap" err 312 | trap "bats_teardown_trap" exit 313 | "$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1 314 | BATS_TEST_COMPLETED=1 315 | 316 | else 317 | echo "bats: unknown test name \`$BATS_TEST_NAME'" >&2 318 | exit 1 319 | fi 320 | } 321 | 322 | if [ -z "$TMPDIR" ]; then 323 | BATS_TMPDIR="/tmp" 324 | else 325 | BATS_TMPDIR="${TMPDIR%/}" 326 | fi 327 | 328 | BATS_TMPNAME="$BATS_TMPDIR/bats.$$" 329 | BATS_PARENT_TMPNAME="$BATS_TMPDIR/bats.$PPID" 330 | BATS_OUT="${BATS_TMPNAME}.out" 331 | 332 | bats_preprocess_source() { 333 | BATS_TEST_SOURCE="${BATS_TMPNAME}.src" 334 | . bats-preprocess <<< "$(< "$BATS_TEST_FILENAME")"$'\n' > "$BATS_TEST_SOURCE" 335 | trap "bats_cleanup_preprocessed_source" err exit 336 | trap "bats_cleanup_preprocessed_source; exit 1" int 337 | } 338 | 339 | bats_cleanup_preprocessed_source() { 340 | rm -f "$BATS_TEST_SOURCE" 341 | } 342 | 343 | bats_evaluate_preprocessed_source() { 344 | if [ -z "$BATS_TEST_SOURCE" ]; then 345 | BATS_TEST_SOURCE="${BATS_PARENT_TMPNAME}.src" 346 | fi 347 | source "$BATS_TEST_SOURCE" 348 | } 349 | 350 | exec 3<&1 351 | 352 | if [ "$#" -eq 0 ]; then 353 | bats_preprocess_source 354 | bats_evaluate_preprocessed_source 355 | 356 | if [ -n "$BATS_COUNT_ONLY" ]; then 357 | echo "${#BATS_TEST_NAMES[@]}" 358 | else 359 | bats_perform_tests "${BATS_TEST_NAMES[@]}" 360 | fi 361 | else 362 | bats_evaluate_preprocessed_source 363 | bats_perform_test "$@" 364 | fi 365 | -------------------------------------------------------------------------------- /tests/bats-core/bats-format-tap-stream: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Just stream the TAP output (sans extended syntax) if tput is missing 5 | command -v tput >/dev/null || exec grep -v "^begin " 6 | 7 | header_pattern='[0-9]+\.\.[0-9]+' 8 | IFS= read -r header 9 | 10 | if [[ "$header" =~ $header_pattern ]]; then 11 | count="${header:3}" 12 | index=0 13 | failures=0 14 | skipped=0 15 | name="" 16 | count_column_width=$(( ${#count} * 2 + 2 )) 17 | else 18 | # If the first line isn't a TAP plan, print it and pass the rest through 19 | printf "%s\n" "$header" 20 | exec cat 21 | fi 22 | 23 | update_screen_width() { 24 | screen_width="$(tput cols)" 25 | count_column_left=$(( $screen_width - $count_column_width )) 26 | } 27 | 28 | trap update_screen_width WINCH 29 | update_screen_width 30 | 31 | begin() { 32 | go_to_column 0 33 | printf_with_truncation $(( $count_column_left - 1 )) " %s" "$name" 34 | clear_to_end_of_line 35 | go_to_column $count_column_left 36 | printf "%${#count}s/${count}" "$index" 37 | go_to_column 1 38 | } 39 | 40 | pass() { 41 | go_to_column 0 42 | printf " ✓ %s" "$name" 43 | advance 44 | } 45 | 46 | skip() { 47 | local reason="$1" 48 | [ -z "$reason" ] || reason=": $reason" 49 | go_to_column 0 50 | printf " - %s (skipped%s)" "$name" "$reason" 51 | advance 52 | } 53 | 54 | fail() { 55 | go_to_column 0 56 | set_color 1 bold 57 | printf " ✗ %s" "$name" 58 | advance 59 | } 60 | 61 | log() { 62 | set_color 1 63 | printf " %s\n" "$1" 64 | clear_color 65 | } 66 | 67 | summary() { 68 | printf "\n%d test" "$count" 69 | if [[ "$count" -ne '1' ]]; then 70 | printf 's' 71 | fi 72 | 73 | printf ", %d failure" "$failures" 74 | if [[ "$failures" -ne '1' ]]; then 75 | printf 's' 76 | fi 77 | 78 | if [ "$skipped" -gt 0 ]; then 79 | printf ", %d skipped" "$skipped" 80 | fi 81 | 82 | printf "\n" 83 | } 84 | 85 | printf_with_truncation() { 86 | local width="$1" 87 | shift 88 | local string 89 | 90 | printf -v 'string' -- "$@" 91 | 92 | if [ "${#string}" -gt "$width" ]; then 93 | printf "%s..." "${string:0:$(( $width - 4 ))}" 94 | else 95 | printf "%s" "$string" 96 | fi 97 | } 98 | 99 | go_to_column() { 100 | local column="$1" 101 | printf "\x1B[%dG" $(( $column + 1 )) 102 | } 103 | 104 | clear_to_end_of_line() { 105 | printf "\x1B[K" 106 | } 107 | 108 | advance() { 109 | clear_to_end_of_line 110 | echo 111 | clear_color 112 | } 113 | 114 | set_color() { 115 | local color="$1" 116 | local weight='22' 117 | 118 | if [[ "$2" == 'bold' ]]; then 119 | weight='1' 120 | fi 121 | printf "\x1B[%d;%dm" $(( 30 + $color )) "$weight" 122 | } 123 | 124 | clear_color() { 125 | printf "\x1B[0m" 126 | } 127 | 128 | _buffer="" 129 | 130 | buffer() { 131 | _buffer="${_buffer}$("$@")" 132 | } 133 | 134 | flush() { 135 | printf "%s" "$_buffer" 136 | _buffer="" 137 | } 138 | 139 | finish() { 140 | flush 141 | printf "\n" 142 | } 143 | 144 | trap finish EXIT 145 | 146 | while IFS= read -r line; do 147 | case "$line" in 148 | "begin "* ) 149 | let index+=1 150 | name="${line#* $index }" 151 | buffer begin 152 | flush 153 | ;; 154 | "ok "* ) 155 | skip_expr="ok $index (.*) # skip ?(([^)]*))?" 156 | if [[ "$line" =~ $skip_expr ]]; then 157 | let skipped+=1 158 | buffer skip "${BASH_REMATCH[2]}" 159 | else 160 | buffer pass 161 | fi 162 | ;; 163 | "not ok "* ) 164 | let failures+=1 165 | buffer fail 166 | ;; 167 | "# "* ) 168 | buffer log "${line:2}" 169 | ;; 170 | esac 171 | done 172 | 173 | buffer summary 174 | -------------------------------------------------------------------------------- /tests/bats-core/bats-preprocess: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | encode_name() { 5 | local name="$1" 6 | local result="test_" 7 | local hex_code 8 | 9 | if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then 10 | name="${name//_/-5f}" 11 | name="${name//-/-2d}" 12 | name="${name// /_}" 13 | result+="$name" 14 | else 15 | local length="${#name}" 16 | local char i 17 | 18 | for ((i=0; i config.json.new < config.json 63 | mv config.json.new config.json 64 | } 65 | 66 | function teardown_test() { 67 | run rm -r "$TEST_BUNDLE" 68 | } 69 | 70 | function setup_root() { 71 | run mkdir -p "$ROOT" 72 | } 73 | function teardown_root() { 74 | run rm -r "$ROOT" 75 | } 76 | 77 | function local-test () { 78 | if [ -n "${INCONTAINER}" ]; then 79 | skip "Test cannot be run in container" 80 | fi 81 | } 82 | 83 | function container-test () { 84 | if [ -z "${INCONTAINER}" ]; then 85 | skip "Test must be run in container" 86 | fi 87 | } 88 | -------------------------------------------------------------------------------- /tests/integration/run.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | ## Copyright (c) 2018, IBM 3 | # Author(s): Brandon Lum, Ricardo Koller 4 | # 5 | # Permission to use, copy, modify, and/or distribute this software for 6 | # any purpose with or without fee is hereby granted, provided that the 7 | # above copyright notice and this permission notice appear in all 8 | # copies. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 11 | # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 12 | # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 13 | # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 14 | # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA 15 | # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 16 | # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | # PERFORMANCE OF THIS SOFTWARE. 18 | 19 | load helpers 20 | 21 | RUNNC_OUT="out" 22 | 23 | function setup() { 24 | setup_root 25 | } 26 | 27 | function teardown() { 28 | teardown_root 29 | } 30 | 31 | function docker_node_nabla_run() { 32 | local-test 33 | 34 | run sudo docker run --rm --runtime=runnc nablact/nabla-node:test /node.nabla 35 | 36 | echo "nabla-run $* (status=$status):" >&2 37 | echo "$output" >&2 38 | } 39 | 40 | function runnc_run() { 41 | daemon=${2:-nodaemon} 42 | # trick: don't start with 'run', or you will get a deadlock 43 | # waiting for the nabla program to be done but no one start it 44 | runnc create --bundle "$TEST_BUNDLE" --pid-file "$ROOT/pid" "$1" > $RUNNC_OUT 45 | run runnc start "$1" 46 | 47 | echo "nabla-run $* (status=$status):" >&2 48 | echo "$output" >&2 49 | 50 | # waiting for container process to be done 51 | if [[ ${daemon} == 'nodaemon' ]]; then 52 | tail --pid="$(cat "$ROOT"/pid)" -f /dev/null 53 | fi 54 | } 55 | 56 | @test "hello" { 57 | setup_test "hello" 58 | local name="test-nabla-hello" 59 | 60 | config_mod '.process.args |= .+ ["test_hello.nabla"]' 61 | 62 | runnc_run "$name" 63 | 64 | run cat "$TEST_BUNDLE/$RUNNC_OUT" 65 | [[ "$output" == *"Hello, World"* ]] 66 | 67 | runnc delete --force "$name" 68 | teardown_test 69 | } 70 | 71 | @test "hello with arg" { 72 | setup_test "hello" 73 | local name="test-nabla-hello-arg" 74 | 75 | config_mod '.process.args |= .+ ["test_hello.nabla", "hola"]' 76 | 77 | runnc_run "$name" 78 | 79 | run cat "$TEST_BUNDLE/$RUNNC_OUT" 80 | [[ "$output" == *"Hello, World"* ]] 81 | [[ "$output" == *"hola"* ]] 82 | 83 | runnc delete --force "$name" 84 | teardown_test 85 | } 86 | 87 | @test "hello with json arg" { 88 | setup_test "hello" 89 | local name="test-nabla-hello-json-arg" 90 | 91 | config_mod '.process.args |= .+ ["test_hello.nabla"]' 92 | config_mod '.process.args |= .+ ["{\"bla\":\"ble\"}"]' 93 | 94 | runnc_run "$name" 95 | 96 | run cat "$TEST_BUNDLE/$RUNNC_OUT" 97 | [[ "$output" == *"Hello, World"* ]] 98 | [[ "$output" == *'{\"bla\":\"ble\"}'* ]] 99 | 100 | runnc delete --force "$name" 101 | teardown_test 102 | } 103 | 104 | @test "hello with escaped json arg" { 105 | setup_test "hello" 106 | local name="test-nabla-hello-escaped-json-arg" 107 | 108 | config_mod '.process.args |= .+ ["test_hello.nabla"]' 109 | config_mod '.process.args |= .+ ["{\\\"bla\\\":\\\"ble\\\"}"]' 110 | 111 | runnc_run "$name" 112 | 113 | run cat "$TEST_BUNDLE/$RUNNC_OUT" 114 | [[ "$output" == *"Hello, World"* ]] 115 | [[ "$output" == *'{\\\"bla\\\":\\\"ble\\\"}'* ]] 116 | 117 | runnc delete --force "$name" 118 | teardown_test 119 | } 120 | 121 | @test "hello with net setting" { 122 | skip "TODO: Require proper networking for native runnc in prestart hooks" 123 | } 124 | 125 | @test "hello runnc" { 126 | local-test 127 | 128 | run sudo docker run --rm --runtime=runnc nablact/nabla-hello:test /test_hello.nabla 129 | [[ "$output" == *"Hello, World"* ]] 130 | } 131 | 132 | @test "hello runnc with arg" { 133 | local-test 134 | 135 | run sudo docker run --rm --runtime=runnc nablact/nabla-hello:test /test_hello.nabla hola 136 | [[ "$output" == *"Hello, World"* ]] 137 | [[ "$output" == *"hola"* ]] 138 | } 139 | 140 | @test "hello runnc with json arg" { 141 | local-test 142 | 143 | run sudo docker run --rm --runtime=runnc nablact/nabla-hello:test /test_hello.nabla "{\"bla\":\"ble\"}" 144 | [[ "$output" == *"Hello, World"* ]] 145 | [[ "$output" == *"{\\\"bla\\\":\\\"ble\\\"}"* ]] 146 | } 147 | 148 | @test "node hello" { 149 | setup_test "node" 150 | local name="test-nabla-node" 151 | 152 | config_mod '.process.args |= .+ ["node.nabla", "/hello/app.js"]' 153 | 154 | runnc_run "$name" 155 | 156 | run cat "$TEST_BUNDLE/$RUNNC_OUT" 157 | [[ "$output" == *"hello from node"* ]] 158 | 159 | runnc delete --force "$name" 160 | teardown_test 161 | } 162 | 163 | @test "node env" { 164 | setup_test "node" 165 | local name="test-nabla-node-env" 166 | 167 | config_mod '.process.args |= .+ ["node.nabla", "/hello/env.js"]' 168 | config_mod '.process.env |= .+ ["BLA=bla", "NABLA_ENV_TEST=blableblibloblu", "BLE=ble"]' 169 | 170 | runnc_run "$name" 171 | 172 | run cat "$TEST_BUNDLE/$RUNNC_OUT" 173 | [[ "$output" == *"env=blableblibloblu"* ]] 174 | 175 | runnc delete --force "$name" 176 | teardown_test 177 | } 178 | 179 | @test "node cwd" { 180 | setup_test "node" 181 | local name="test-nabla-node-cwd" 182 | 183 | config_mod '.process.args |= .+ ["node.nabla", "/hello/cwd.js"]' 184 | config_mod '.process.cwd |= "/hello"' 185 | 186 | runnc_run "$name" 187 | 188 | run cat "$TEST_BUNDLE/$RUNNC_OUT" 189 | [[ "$output" == *"cwd=/hello"* ]] 190 | 191 | runnc delete --force "$name" 192 | teardown_test 193 | } 194 | 195 | @test "node hello runnc" { 196 | local-test 197 | 198 | run sudo docker run --rm --runtime=runnc nablact/nabla-node:test /node.nabla /hello/app.js 199 | [[ "$output" == *"hello from node"* ]] 200 | } 201 | 202 | @test "node env runnc" { 203 | local-test 204 | 205 | # env.js just prints the NABLA_ENV_TEST environment variable 206 | run sudo docker run --rm --runtime=runnc -e NABLA_ENV_TEST=blableblibloblu nablact/nabla-node:test /node.nabla /hello/env.js 207 | [[ "$output" == *"env=blableblibloblu"* ]] 208 | } 209 | 210 | @test "curl local" { 211 | skip "TODO: Require proper networking for native runnc in prestart hooks" 212 | } 213 | 214 | @test "curl runnc" { 215 | local-test 216 | 217 | ( 218 | python "$TESTDATA"/test-http-server.py 219 | )& 220 | 221 | sleep 3 222 | 223 | HOSTIP=$( ip route get 1 | awk '{print $NF;exit}' ) 224 | run sudo docker run --rm --runtime=runnc nablact/nabla-curl:test /test_curl.nabla "$HOSTIP" 225 | 226 | echo "$output" 227 | [[ "$output" == *"XXXXXXXXXX"* ]] 228 | [ "$status" -eq 0 ] 229 | } 230 | 231 | @test "memory runnc" { 232 | local-test 233 | 234 | # Check that 1024m is passed correct to runnc as 1024. 235 | # Redirecting stderr to dev/null because there is a kernel warning 236 | memory_check() { 237 | sudo docker run -d --rm --runtime=runnc -m 1024m nablact/nabla-node:test /node.nabla /hello/app.js 2>/dev/null 238 | } 239 | 240 | run memory_check 241 | [ "$status" -eq 0 ] 242 | container_pid=$(docker inspect --format '{{.State.Pid}}' "${output}") 243 | run bash -c "sudo ps -e -o pid,command | grep ${container_pid}" 244 | [ "$status" -eq 0 ] 245 | [[ "$output" == *"--mem=1024"* ]] 246 | } 247 | 248 | @test "oom_adjust" { 249 | setup_test "node" 250 | local name="test-nabla-oom-adjust" 251 | 252 | config_mod '.process.args |= .+ ["node.nabla", "/hello/app.js"]' 253 | config_mod '.process.oomScoreAdj |= .+ 104' 254 | 255 | runnc_run "${name}" "daemon" 256 | 257 | run bash -c "cat /proc/$(cat "${ROOT}"/pid)/oom_score_adj" 258 | [[ "$output" == *"104"* ]] 259 | 260 | runnc delete --force "${name}" 261 | teardown_test 262 | } 263 | -------------------------------------------------------------------------------- /tests/integration/testdata/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ociVersion": "1.0.0", 3 | "process": { 4 | "terminal": false, 5 | "user": { 6 | "uid": 0, 7 | "gid": 0 8 | }, 9 | "args": [], 10 | "env": [ 11 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 12 | "TERM=xterm" 13 | ], 14 | "cwd": "/" 15 | }, 16 | "root": { 17 | "path": ".", 18 | "readonly": true 19 | }, 20 | "hooks": { 21 | "prestart": [ 22 | { 23 | "path": "/sbin/ip", 24 | "args": ["ip", "link", "add" , "eth0", "type", "dummy"] 25 | }, 26 | { 27 | "path": "/sbin/ip", 28 | "args": ["ip", "addr", "add", "10.0.0.2/8", "dev", "eth0"] 29 | }, 30 | { 31 | "path": "/sbin/ip", 32 | "args": ["ip", "link", "set", "eth0", "up"] 33 | }, 34 | { 35 | "path": "/sbin/ip", 36 | "args": ["ip", "route", "add", "default", "via", "10.0.0.1"] 37 | } 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/integration/testdata/hello/app.js: -------------------------------------------------------------------------------- 1 | console.log("hello from node") 2 | -------------------------------------------------------------------------------- /tests/integration/testdata/hello/cwd.js: -------------------------------------------------------------------------------- 1 | console.log("cwd=" + process.cwd()) 2 | -------------------------------------------------------------------------------- /tests/integration/testdata/hello/env.js: -------------------------------------------------------------------------------- 1 | console.log("env=" + process.env.NABLA_ENV_TEST) 2 | -------------------------------------------------------------------------------- /tests/integration/testdata/test-http-server.py: -------------------------------------------------------------------------------- 1 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 2 | from SocketServer import ThreadingMixIn 3 | import threading 4 | import sys 5 | 6 | MOD = 3 7 | 8 | class Handler(BaseHTTPRequestHandler): 9 | 10 | def do_GET(self): 11 | self.send_response(200) 12 | self.end_headers() 13 | message = 'X'* 10 14 | self.wfile.write(message) 15 | return 16 | 17 | 18 | class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): 19 | """Handle requests in a separate thread.""" 20 | 21 | if __name__ == '__main__': 22 | server = ThreadedHTTPServer(('0.0.0.0', 5000), Handler) 23 | print('Serving on 0.0.0.0:5000') 24 | server.handle_request() 25 | -------------------------------------------------------------------------------- /utils/copy.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, IBM 2 | // Author(s): Brandon Lum, Ricardo Koller, Dan Williams 3 | // 4 | // Permission to use, copy, modify, and/or distribute this software for 5 | // any purpose with or without fee is hereby granted, provided that the 6 | // above copyright notice and this permission notice appear in all 7 | // copies. 8 | // 9 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 | // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 | // WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 | // DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA 14 | // OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | // PERFORMANCE OF THIS SOFTWARE. 17 | 18 | package utils 19 | 20 | import ( 21 | "io" 22 | "io/ioutil" 23 | "os" 24 | "path/filepath" 25 | ) 26 | 27 | // Copy simulates the command `cp -r ` 28 | // copyPath adapted from 29 | // https://gist.github.com/elazarl/5507969 and 30 | // https://github.com/otiai10/copy/blob/master/copy.go 31 | // Changed to have different semantics and behaviors for file perms, overwrites 32 | // and copy attributes 33 | func Copy(dst, src string) error { 34 | srcInfo, err := os.Stat(src) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return pcopy(dst, src, srcInfo) 40 | } 41 | 42 | func pcopy(dst, src string, srcInfo os.FileInfo) error { 43 | if srcInfo.IsDir() { 44 | return dcopy(dst, src, srcInfo) 45 | } 46 | return fcopy(dst, src, srcInfo) 47 | } 48 | 49 | func fcopy(dst, src string, srcInfo os.FileInfo) error { 50 | s, err := os.Open(src) 51 | if err != nil { 52 | return err 53 | } 54 | // no need to check errors on read only file, we already got everything 55 | // we need from the filesystem, so nothing can go wrong now. 56 | defer s.Close() 57 | 58 | d, err := os.Create(dst) 59 | if err != nil { 60 | // If file already exist, overwrite it 61 | if os.IsExist(err) { 62 | if d, err = os.OpenFile(dst, os.O_RDWR|os.O_TRUNC, 0755); err != nil { 63 | return err 64 | } 65 | } else { 66 | return err 67 | } 68 | } 69 | if _, err := io.Copy(d, s); err != nil { 70 | d.Close() 71 | return err 72 | } 73 | 74 | err = os.Chmod(dst, srcInfo.Mode()) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | return d.Close() 80 | } 81 | 82 | func dcopy(dst, src string, srcInfo os.FileInfo) error { 83 | if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil { 84 | return err 85 | } 86 | 87 | infos, err := ioutil.ReadDir(src) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | for _, info := range infos { 93 | if err := pcopy( 94 | filepath.Join(dst, info.Name()), 95 | filepath.Join(src, info.Name()), 96 | info, 97 | ); err != nil { 98 | return err 99 | } 100 | } 101 | return nil 102 | 103 | } 104 | -------------------------------------------------------------------------------- /utils/slice.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // AddAbsentSlice adds an object to a slice of objects if it is not already present. 4 | func AddAbsentSlice(slice []string, add string) []string { 5 | for _, v := range slice { 6 | if add == v { 7 | return slice 8 | } 9 | } 10 | 11 | return append(slice, add) 12 | } 13 | --------------------------------------------------------------------------------