Signed-off-by: Michael Crosby <michael@docker.com>
| ... | ... |
@@ -59,7 +59,7 @@ rm -rf src/code.google.com/p/go |
| 59 | 59 |
mkdir -p src/code.google.com/p/go/src/pkg/archive |
| 60 | 60 |
mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar |
| 61 | 61 |
|
| 62 |
-clone git github.com/docker/libcontainer f2e78425c377acc7a67a35c3148069b6285a3c4b |
|
| 62 |
+clone git github.com/docker/libcontainer 29363e2d2d7b8f62a5f353be333758f83df540a9 |
|
| 63 | 63 |
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) |
| 64 | 64 |
rm -rf src/github.com/docker/libcontainer/vendor |
| 65 | 65 |
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')" |
| ... | ... |
@@ -1,5 +1,6 @@ |
| 1 | 1 |
Michael Crosby <michael@docker.com> (@crosbymichael) |
| 2 | 2 |
Rohit Jnagal <jnagal@google.com> (@rjnagal) |
| 3 | 3 |
Victor Marmol <vmarmol@google.com> (@vmarmol) |
| 4 |
+Mrunal Patel <mpatel@redhat.com> (@mrunalp) |
|
| 4 | 5 |
.travis.yml: Tianon Gravi <admwiggin@gmail.com> (@tianon) |
| 5 | 6 |
update-vendor.sh: Tianon Gravi <admwiggin@gmail.com> (@tianon) |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-## libcontainer - reference implementation for containers |
|
| 1 |
+## libcontainer - reference implementation for containers [](https://travis-ci.org/docker/libcontainer) |
|
| 2 | 2 |
|
| 3 | 3 |
### Note on API changes: |
| 4 | 4 |
|
| ... | ... |
@@ -21,12 +21,16 @@ var ( |
| 21 | 21 |
"perf_event": &PerfEventGroup{},
|
| 22 | 22 |
"freezer": &FreezerGroup{},
|
| 23 | 23 |
} |
| 24 |
+ CgroupProcesses = "cgroup.procs" |
|
| 24 | 25 |
) |
| 25 | 26 |
|
| 26 | 27 |
type subsystem interface {
|
| 27 |
- Set(*data) error |
|
| 28 |
+ // Returns the stats, as 'stats', corresponding to the cgroup under 'path'. |
|
| 29 |
+ GetStats(path string, stats *cgroups.Stats) error |
|
| 30 |
+ // Removes the cgroup represented by 'data'. |
|
| 28 | 31 |
Remove(*data) error |
| 29 |
- GetStats(string, *cgroups.Stats) error |
|
| 32 |
+ // Creates and joins the cgroup represented by data. |
|
| 33 |
+ Set(*data) error |
|
| 30 | 34 |
} |
| 31 | 35 |
|
| 32 | 36 |
type data struct {
|
| ... | ... |
@@ -149,6 +153,18 @@ func (raw *data) parent(subsystem string) (string, error) {
|
| 149 | 149 |
return filepath.Join(raw.root, subsystem, initPath), nil |
| 150 | 150 |
} |
| 151 | 151 |
|
| 152 |
+func (raw *data) Paths() (map[string]string, error) {
|
|
| 153 |
+ paths := make(map[string]string) |
|
| 154 |
+ for sysname := range subsystems {
|
|
| 155 |
+ path, err := raw.path(sysname) |
|
| 156 |
+ if err != nil {
|
|
| 157 |
+ return nil, err |
|
| 158 |
+ } |
|
| 159 |
+ paths[sysname] = path |
|
| 160 |
+ } |
|
| 161 |
+ return paths, nil |
|
| 162 |
+} |
|
| 163 |
+ |
|
| 152 | 164 |
func (raw *data) path(subsystem string) (string, error) {
|
| 153 | 165 |
// If the cgroup name/path is absolute do not look relative to the cgroup of the init process. |
| 154 | 166 |
if filepath.IsAbs(raw.cgroup) {
|
| ... | ... |
@@ -169,7 +185,7 @@ func (raw *data) join(subsystem string) (string, error) {
|
| 169 | 169 |
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
|
| 170 | 170 |
return "", err |
| 171 | 171 |
} |
| 172 |
- if err := writeFile(path, "cgroup.procs", strconv.Itoa(raw.pid)); err != nil {
|
|
| 172 |
+ if err := writeFile(path, CgroupProcesses, strconv.Itoa(raw.pid)); err != nil {
|
|
| 173 | 173 |
return "", err |
| 174 | 174 |
} |
| 175 | 175 |
return path, nil |
| ... | ... |
@@ -54,7 +54,7 @@ func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
|
| 54 | 54 |
return err |
| 55 | 55 |
} |
| 56 | 56 |
// sample for 100ms |
| 57 |
- time.Sleep(100 * time.Millisecond) |
|
| 57 |
+ time.Sleep(1000 * time.Millisecond) |
|
| 58 | 58 |
if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil {
|
| 59 | 59 |
return err |
| 60 | 60 |
} |
| ... | ... |
@@ -73,7 +73,7 @@ func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
|
| 73 | 73 |
deltaUsage = lastUsage - startUsage |
| 74 | 74 |
) |
| 75 | 75 |
if deltaSystem > 0.0 {
|
| 76 |
- percentage = ((deltaProc / deltaSystem) * clockTicks) * cpuCount |
|
| 76 |
+ percentage = uint64((float64(deltaProc) / float64(deltaSystem)) * float64(clockTicks*cpuCount)) |
|
| 77 | 77 |
} |
| 78 | 78 |
// NOTE: a percentage over 100% is valid for POSIX because that means the |
| 79 | 79 |
// processes is using multiple cores |
| ... | ... |
@@ -20,19 +20,10 @@ func (s *CpusetGroup) Set(d *data) error {
|
| 20 | 20 |
if err != nil {
|
| 21 | 21 |
return err |
| 22 | 22 |
} |
| 23 |
- if err := s.ensureParent(dir); err != nil {
|
|
| 24 |
- return err |
|
| 25 |
- } |
|
| 26 | 23 |
|
| 27 |
- // because we are not using d.join we need to place the pid into the procs file |
|
| 28 |
- // unlike the other subsystems |
|
| 29 |
- if err := writeFile(dir, "cgroup.procs", strconv.Itoa(d.pid)); err != nil {
|
|
| 30 |
- return err |
|
| 31 |
- } |
|
| 32 |
- if err := writeFile(dir, "cpuset.cpus", d.c.CpusetCpus); err != nil {
|
|
| 33 |
- return err |
|
| 34 |
- } |
|
| 24 |
+ return s.SetDir(dir, d.c.CpusetCpus, d.pid) |
|
| 35 | 25 |
} |
| 26 |
+ |
|
| 36 | 27 |
return nil |
| 37 | 28 |
} |
| 38 | 29 |
|
| ... | ... |
@@ -44,6 +35,24 @@ func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
|
| 44 | 44 |
return nil |
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 |
+func (s *CpusetGroup) SetDir(dir, value string, pid int) error {
|
|
| 48 |
+ if err := s.ensureParent(dir); err != nil {
|
|
| 49 |
+ return err |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ // because we are not using d.join we need to place the pid into the procs file |
|
| 53 |
+ // unlike the other subsystems |
|
| 54 |
+ if err := writeFile(dir, "cgroup.procs", strconv.Itoa(pid)); err != nil {
|
|
| 55 |
+ return err |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ if err := writeFile(dir, "cpuset.cpus", value); err != nil {
|
|
| 59 |
+ return err |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ return nil |
|
| 63 |
+} |
|
| 64 |
+ |
|
| 47 | 65 |
func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
|
| 48 | 66 |
if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
|
| 49 | 67 |
return |
| ... | ... |
@@ -14,7 +14,7 @@ type MemoryGroup struct {
|
| 14 | 14 |
|
| 15 | 15 |
func (s *MemoryGroup) Set(d *data) error {
|
| 16 | 16 |
dir, err := d.join("memory")
|
| 17 |
- // only return an error for memory if it was not specified |
|
| 17 |
+ // only return an error for memory if it was specified |
|
| 18 | 18 |
if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
|
| 19 | 19 |
return err |
| 20 | 20 |
} |
| ... | ... |
@@ -21,7 +21,7 @@ import ( |
| 21 | 21 |
) |
| 22 | 22 |
|
| 23 | 23 |
type systemdCgroup struct {
|
| 24 |
- cleanupDirs []string |
|
| 24 |
+ cgroup *cgroups.Cgroup |
|
| 25 | 25 |
} |
| 26 | 26 |
|
| 27 | 27 |
type subsystem interface {
|
| ... | ... |
@@ -84,39 +84,15 @@ func getIfaceForUnit(unitName string) string {
|
| 84 | 84 |
return "Unit" |
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 |
-type cgroupArg struct {
|
|
| 88 |
- File string |
|
| 89 |
- Value string |
|
| 90 |
-} |
|
| 91 |
- |
|
| 92 | 87 |
func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
|
| 93 | 88 |
var ( |
| 94 | 89 |
unitName = getUnitName(c) |
| 95 | 90 |
slice = "system.slice" |
| 96 | 91 |
properties []systemd1.Property |
| 97 |
- cpuArgs []cgroupArg |
|
| 98 |
- cpusetArgs []cgroupArg |
|
| 99 |
- memoryArgs []cgroupArg |
|
| 100 |
- res systemdCgroup |
|
| 92 |
+ res = &systemdCgroup{}
|
|
| 101 | 93 |
) |
| 102 | 94 |
|
| 103 |
- // First set up things not supported by systemd |
|
| 104 |
- |
|
| 105 |
- // -1 disables memorySwap |
|
| 106 |
- if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) {
|
|
| 107 |
- memorySwap := c.MemorySwap |
|
| 108 |
- |
|
| 109 |
- if memorySwap == 0 {
|
|
| 110 |
- // By default, MemorySwap is set to twice the size of RAM. |
|
| 111 |
- memorySwap = c.Memory * 2 |
|
| 112 |
- } |
|
| 113 |
- |
|
| 114 |
- memoryArgs = append(memoryArgs, cgroupArg{"memory.memsw.limit_in_bytes", strconv.FormatInt(memorySwap, 10)})
|
|
| 115 |
- } |
|
| 116 |
- |
|
| 117 |
- if c.CpusetCpus != "" {
|
|
| 118 |
- cpusetArgs = append(cpusetArgs, cgroupArg{"cpuset.cpus", c.CpusetCpus})
|
|
| 119 |
- } |
|
| 95 |
+ res.cgroup = c |
|
| 120 | 96 |
|
| 121 | 97 |
if c.Slice != "" {
|
| 122 | 98 |
slice = c.Slice |
| ... | ... |
@@ -150,201 +126,84 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
|
| 150 | 150 |
return nil, err |
| 151 | 151 |
} |
| 152 | 152 |
|
| 153 |
- // To work around the lack of /dev/pts/* support above we need to manually add these |
|
| 154 |
- // so, ask systemd for the cgroup used |
|
| 155 |
- props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName)) |
|
| 156 |
- if err != nil {
|
|
| 157 |
- return nil, err |
|
| 158 |
- } |
|
| 159 |
- |
|
| 160 |
- cgroup := props["ControlGroup"].(string) |
|
| 161 |
- |
|
| 162 | 153 |
if !c.AllowAllDevices {
|
| 163 |
- // Atm we can't use the systemd device support because of two missing things: |
|
| 164 |
- // * Support for wildcards to allow mknod on any device |
|
| 165 |
- // * Support for wildcards to allow /dev/pts support |
|
| 166 |
- // |
|
| 167 |
- // The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is |
|
| 168 |
- // in wide use. When both these are availalable we will be able to switch, but need to keep the old |
|
| 169 |
- // implementation for backwards compat. |
|
| 170 |
- // |
|
| 171 |
- // Note: we can't use systemd to set up the initial limits, and then change the cgroup |
|
| 172 |
- // because systemd will re-write the device settings if it needs to re-apply the cgroup context. |
|
| 173 |
- // This happens at least for v208 when any sibling unit is started. |
|
| 174 |
- |
|
| 175 |
- mountpoint, err := cgroups.FindCgroupMountpoint("devices")
|
|
| 176 |
- if err != nil {
|
|
| 154 |
+ if err := joinDevices(c, pid); err != nil {
|
|
| 177 | 155 |
return nil, err |
| 178 | 156 |
} |
| 179 |
- |
|
| 180 |
- initPath, err := cgroups.GetInitCgroupDir("devices")
|
|
| 181 |
- if err != nil {
|
|
| 182 |
- return nil, err |
|
| 183 |
- } |
|
| 184 |
- |
|
| 185 |
- dir := filepath.Join(mountpoint, initPath, c.Parent, c.Name) |
|
| 186 |
- |
|
| 187 |
- res.cleanupDirs = append(res.cleanupDirs, dir) |
|
| 188 |
- |
|
| 189 |
- if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) {
|
|
| 190 |
- return nil, err |
|
| 191 |
- } |
|
| 192 |
- |
|
| 193 |
- if err := ioutil.WriteFile(filepath.Join(dir, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
|
|
| 194 |
- return nil, err |
|
| 195 |
- } |
|
| 196 |
- |
|
| 197 |
- if err := writeFile(dir, "devices.deny", "a"); err != nil {
|
|
| 198 |
- return nil, err |
|
| 199 |
- } |
|
| 200 |
- |
|
| 201 |
- for _, dev := range c.AllowedDevices {
|
|
| 202 |
- if err := writeFile(dir, "devices.allow", dev.GetCgroupAllowString()); err != nil {
|
|
| 203 |
- return nil, err |
|
| 204 |
- } |
|
| 205 |
- } |
|
| 206 | 157 |
} |
| 207 | 158 |
|
| 208 |
- if len(cpuArgs) != 0 {
|
|
| 209 |
- mountpoint, err := cgroups.FindCgroupMountpoint("cpu")
|
|
| 210 |
- if err != nil {
|
|
| 211 |
- return nil, err |
|
| 212 |
- } |
|
| 213 |
- |
|
| 214 |
- path := filepath.Join(mountpoint, cgroup) |
|
| 215 |
- |
|
| 216 |
- for _, arg := range cpuArgs {
|
|
| 217 |
- if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
|
|
| 218 |
- return nil, err |
|
| 219 |
- } |
|
| 220 |
- } |
|
| 221 |
- } |
|
| 222 |
- |
|
| 223 |
- if len(memoryArgs) != 0 {
|
|
| 224 |
- mountpoint, err := cgroups.FindCgroupMountpoint("memory")
|
|
| 225 |
- if err != nil {
|
|
| 159 |
+ // -1 disables memorySwap |
|
| 160 |
+ if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) {
|
|
| 161 |
+ if err := joinMemory(c, pid); err != nil {
|
|
| 226 | 162 |
return nil, err |
| 227 | 163 |
} |
| 228 | 164 |
|
| 229 |
- path := filepath.Join(mountpoint, cgroup) |
|
| 230 |
- |
|
| 231 |
- for _, arg := range memoryArgs {
|
|
| 232 |
- if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
|
|
| 233 |
- return nil, err |
|
| 234 |
- } |
|
| 235 |
- } |
|
| 236 | 165 |
} |
| 237 | 166 |
|
| 238 | 167 |
// we need to manually join the freezer cgroup in systemd because it does not currently support it |
| 239 | 168 |
// via the dbus api |
| 240 |
- freezerPath, err := joinFreezer(c, pid) |
|
| 241 |
- if err != nil {
|
|
| 169 |
+ if err := joinFreezer(c, pid); err != nil {
|
|
| 242 | 170 |
return nil, err |
| 243 | 171 |
} |
| 244 |
- res.cleanupDirs = append(res.cleanupDirs, freezerPath) |
|
| 245 | 172 |
|
| 246 |
- if len(cpusetArgs) != 0 {
|
|
| 247 |
- // systemd does not atm set up the cpuset controller, so we must manually |
|
| 248 |
- // join it. Additionally that is a very finicky controller where each |
|
| 249 |
- // level must have a full setup as the default for a new directory is "no cpus", |
|
| 250 |
- // so we avoid using any hierarchies here, creating a toplevel directory. |
|
| 251 |
- mountpoint, err := cgroups.FindCgroupMountpoint("cpuset")
|
|
| 252 |
- if err != nil {
|
|
| 253 |
- return nil, err |
|
| 254 |
- } |
|
| 255 |
- |
|
| 256 |
- initPath, err := cgroups.GetInitCgroupDir("cpuset")
|
|
| 257 |
- if err != nil {
|
|
| 258 |
- return nil, err |
|
| 259 |
- } |
|
| 260 |
- |
|
| 261 |
- var ( |
|
| 262 |
- foundCpus bool |
|
| 263 |
- foundMems bool |
|
| 264 |
- |
|
| 265 |
- rootPath = filepath.Join(mountpoint, initPath) |
|
| 266 |
- path = filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name) |
|
| 267 |
- ) |
|
| 268 |
- |
|
| 269 |
- res.cleanupDirs = append(res.cleanupDirs, path) |
|
| 270 |
- |
|
| 271 |
- if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
|
|
| 173 |
+ if c.CpusetCpus != "" {
|
|
| 174 |
+ if err := joinCpuset(c, pid); err != nil {
|
|
| 272 | 175 |
return nil, err |
| 273 | 176 |
} |
| 177 |
+ } |
|
| 274 | 178 |
|
| 275 |
- for _, arg := range cpusetArgs {
|
|
| 276 |
- if arg.File == "cpuset.cpus" {
|
|
| 277 |
- foundCpus = true |
|
| 278 |
- } |
|
| 279 |
- if arg.File == "cpuset.mems" {
|
|
| 280 |
- foundMems = true |
|
| 281 |
- } |
|
| 282 |
- if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
|
|
| 283 |
- return nil, err |
|
| 284 |
- } |
|
| 285 |
- } |
|
| 286 |
- |
|
| 287 |
- // These are required, if not specified inherit from parent |
|
| 288 |
- if !foundCpus {
|
|
| 289 |
- s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.cpus")) |
|
| 290 |
- if err != nil {
|
|
| 291 |
- return nil, err |
|
| 292 |
- } |
|
| 179 |
+ return res, nil |
|
| 180 |
+} |
|
| 293 | 181 |
|
| 294 |
- if err := ioutil.WriteFile(filepath.Join(path, "cpuset.cpus"), s, 0700); err != nil {
|
|
| 295 |
- return nil, err |
|
| 296 |
- } |
|
| 297 |
- } |
|
| 182 |
+func writeFile(dir, file, data string) error {
|
|
| 183 |
+ return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) |
|
| 184 |
+} |
|
| 298 | 185 |
|
| 299 |
- // These are required, if not specified inherit from parent |
|
| 300 |
- if !foundMems {
|
|
| 301 |
- s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.mems")) |
|
| 302 |
- if err != nil {
|
|
| 303 |
- return nil, err |
|
| 304 |
- } |
|
| 186 |
+func (c *systemdCgroup) Paths() (map[string]string, error) {
|
|
| 187 |
+ paths := make(map[string]string) |
|
| 305 | 188 |
|
| 306 |
- if err := ioutil.WriteFile(filepath.Join(path, "cpuset.mems"), s, 0700); err != nil {
|
|
| 307 |
- return nil, err |
|
| 189 |
+ for sysname := range subsystems {
|
|
| 190 |
+ subsystemPath, err := getSubsystemPath(c.cgroup, sysname) |
|
| 191 |
+ if err != nil {
|
|
| 192 |
+ // Don't fail if a cgroup hierarchy was not found, just skip this subsystem |
|
| 193 |
+ if err == cgroups.ErrNotFound {
|
|
| 194 |
+ continue |
|
| 308 | 195 |
} |
| 309 |
- } |
|
| 310 | 196 |
|
| 311 |
- if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
|
|
| 312 | 197 |
return nil, err |
| 313 | 198 |
} |
| 314 |
- } |
|
| 315 | 199 |
|
| 316 |
- return &res, nil |
|
| 317 |
-} |
|
| 200 |
+ paths[sysname] = subsystemPath |
|
| 201 |
+ } |
|
| 318 | 202 |
|
| 319 |
-func writeFile(dir, file, data string) error {
|
|
| 320 |
- return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) |
|
| 203 |
+ return paths, nil |
|
| 321 | 204 |
} |
| 322 | 205 |
|
| 323 | 206 |
func (c *systemdCgroup) Cleanup() error {
|
| 324 | 207 |
// systemd cleans up, we don't need to do much |
| 208 |
+ paths, err := c.Paths() |
|
| 209 |
+ if err != nil {
|
|
| 210 |
+ return err |
|
| 211 |
+ } |
|
| 325 | 212 |
|
| 326 |
- for _, path := range c.cleanupDirs {
|
|
| 213 |
+ for _, path := range paths {
|
|
| 327 | 214 |
os.RemoveAll(path) |
| 328 | 215 |
} |
| 329 | 216 |
|
| 330 | 217 |
return nil |
| 331 | 218 |
} |
| 332 | 219 |
|
| 333 |
-func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
|
|
| 220 |
+func joinFreezer(c *cgroups.Cgroup, pid int) error {
|
|
| 334 | 221 |
path, err := getSubsystemPath(c, "freezer") |
| 335 | 222 |
if err != nil {
|
| 336 |
- return "", err |
|
| 223 |
+ return err |
|
| 337 | 224 |
} |
| 338 | 225 |
|
| 339 | 226 |
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
|
| 340 |
- return "", err |
|
| 341 |
- } |
|
| 342 |
- |
|
| 343 |
- if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
|
|
| 344 |
- return "", err |
|
| 227 |
+ return err |
|
| 345 | 228 |
} |
| 346 | 229 |
|
| 347 |
- return path, nil |
|
| 230 |
+ return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700) |
|
| 348 | 231 |
} |
| 349 | 232 |
|
| 350 | 233 |
func getSubsystemPath(c *cgroups.Cgroup, subsystem string) (string, error) {
|
| ... | ... |
@@ -389,20 +248,12 @@ func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
|
| 389 | 389 |
} |
| 390 | 390 |
|
| 391 | 391 |
func GetPids(c *cgroups.Cgroup) ([]int, error) {
|
| 392 |
- unitName := getUnitName(c) |
|
| 393 |
- |
|
| 394 |
- mountpoint, err := cgroups.FindCgroupMountpoint("cpu")
|
|
| 392 |
+ path, err := getSubsystemPath(c, "cpu") |
|
| 395 | 393 |
if err != nil {
|
| 396 | 394 |
return nil, err |
| 397 | 395 |
} |
| 398 | 396 |
|
| 399 |
- props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName)) |
|
| 400 |
- if err != nil {
|
|
| 401 |
- return nil, err |
|
| 402 |
- } |
|
| 403 |
- cgroup := props["ControlGroup"].(string) |
|
| 404 |
- |
|
| 405 |
- return cgroups.ReadProcsFile(filepath.Join(mountpoint, cgroup)) |
|
| 397 |
+ return cgroups.ReadProcsFile(path) |
|
| 406 | 398 |
} |
| 407 | 399 |
|
| 408 | 400 |
func getUnitName(c *cgroups.Cgroup) string {
|
| ... | ... |
@@ -437,3 +288,71 @@ func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
|
| 437 | 437 |
|
| 438 | 438 |
return stats, nil |
| 439 | 439 |
} |
| 440 |
+ |
|
| 441 |
+// Atm we can't use the systemd device support because of two missing things: |
|
| 442 |
+// * Support for wildcards to allow mknod on any device |
|
| 443 |
+// * Support for wildcards to allow /dev/pts support |
|
| 444 |
+// |
|
| 445 |
+// The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is |
|
| 446 |
+// in wide use. When both these are availalable we will be able to switch, but need to keep the old |
|
| 447 |
+// implementation for backwards compat. |
|
| 448 |
+// |
|
| 449 |
+// Note: we can't use systemd to set up the initial limits, and then change the cgroup |
|
| 450 |
+// because systemd will re-write the device settings if it needs to re-apply the cgroup context. |
|
| 451 |
+// This happens at least for v208 when any sibling unit is started. |
|
| 452 |
+func joinDevices(c *cgroups.Cgroup, pid int) error {
|
|
| 453 |
+ path, err := getSubsystemPath(c, "devices") |
|
| 454 |
+ if err != nil {
|
|
| 455 |
+ return err |
|
| 456 |
+ } |
|
| 457 |
+ |
|
| 458 |
+ if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
|
|
| 459 |
+ return err |
|
| 460 |
+ } |
|
| 461 |
+ |
|
| 462 |
+ if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
|
|
| 463 |
+ return err |
|
| 464 |
+ } |
|
| 465 |
+ |
|
| 466 |
+ if err := writeFile(path, "devices.deny", "a"); err != nil {
|
|
| 467 |
+ return err |
|
| 468 |
+ } |
|
| 469 |
+ |
|
| 470 |
+ for _, dev := range c.AllowedDevices {
|
|
| 471 |
+ if err := writeFile(path, "devices.allow", dev.GetCgroupAllowString()); err != nil {
|
|
| 472 |
+ return err |
|
| 473 |
+ } |
|
| 474 |
+ } |
|
| 475 |
+ |
|
| 476 |
+ return nil |
|
| 477 |
+} |
|
| 478 |
+ |
|
| 479 |
+func joinMemory(c *cgroups.Cgroup, pid int) error {
|
|
| 480 |
+ memorySwap := c.MemorySwap |
|
| 481 |
+ |
|
| 482 |
+ if memorySwap == 0 {
|
|
| 483 |
+ // By default, MemorySwap is set to twice the size of RAM. |
|
| 484 |
+ memorySwap = c.Memory * 2 |
|
| 485 |
+ } |
|
| 486 |
+ |
|
| 487 |
+ path, err := getSubsystemPath(c, "memory") |
|
| 488 |
+ if err != nil {
|
|
| 489 |
+ return err |
|
| 490 |
+ } |
|
| 491 |
+ |
|
| 492 |
+ return ioutil.WriteFile(filepath.Join(path, "memory.memsw.limit_in_bytes"), []byte(strconv.FormatInt(memorySwap, 10)), 0700) |
|
| 493 |
+} |
|
| 494 |
+ |
|
| 495 |
+// systemd does not atm set up the cpuset controller, so we must manually |
|
| 496 |
+// join it. Additionally that is a very finicky controller where each |
|
| 497 |
+// level must have a full setup as the default for a new directory is "no cpus" |
|
| 498 |
+func joinCpuset(c *cgroups.Cgroup, pid int) error {
|
|
| 499 |
+ path, err := getSubsystemPath(c, "cpuset") |
|
| 500 |
+ if err != nil {
|
|
| 501 |
+ return err |
|
| 502 |
+ } |
|
| 503 |
+ |
|
| 504 |
+ s := &fs.CpusetGroup{}
|
|
| 505 |
+ |
|
| 506 |
+ return s.SetDir(path, c.CpusetCpus, pid) |
|
| 507 |
+} |
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"bufio" |
| 5 | 5 |
"fmt" |
| 6 | 6 |
"io" |
| 7 |
+ "io/ioutil" |
|
| 7 | 8 |
"os" |
| 8 | 9 |
"path/filepath" |
| 9 | 10 |
"strconv" |
| ... | ... |
@@ -166,3 +167,23 @@ func parseCgroupFile(subsystem string, r io.Reader) (string, error) {
|
| 166 | 166 |
} |
| 167 | 167 |
return "", ErrNotFound |
| 168 | 168 |
} |
| 169 |
+ |
|
| 170 |
+func pathExists(path string) bool {
|
|
| 171 |
+ if _, err := os.Stat(path); err != nil {
|
|
| 172 |
+ return false |
|
| 173 |
+ } |
|
| 174 |
+ return true |
|
| 175 |
+} |
|
| 176 |
+ |
|
| 177 |
+func EnterPid(cgroupPaths map[string]string, pid int) error {
|
|
| 178 |
+ for _, path := range cgroupPaths {
|
|
| 179 |
+ if pathExists(path) {
|
|
| 180 |
+ if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), |
|
| 181 |
+ []byte(strconv.Itoa(pid)), 0700); err != nil {
|
|
| 182 |
+ return err |
|
| 183 |
+ } |
|
| 184 |
+ } |
|
| 185 |
+ } |
|
| 186 |
+ |
|
| 187 |
+ return nil |
|
| 188 |
+} |
| ... | ... |
@@ -114,7 +114,7 @@ func OpenPtmx() (*os.File, error) {
|
| 114 | 114 |
func OpenTerminal(name string, flag int) (*os.File, error) {
|
| 115 | 115 |
r, e := syscall.Open(name, flag, 0) |
| 116 | 116 |
if e != nil {
|
| 117 |
- return nil, &os.PathError{"open", name, e}
|
|
| 117 |
+ return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
|
| 118 | 118 |
} |
| 119 | 119 |
return os.NewFile(uintptr(r), name), nil |
| 120 | 120 |
} |
| ... | ... |
@@ -236,7 +236,7 @@ func reOpenDevNull(rootfs string) error {
|
| 236 | 236 |
if stat.Rdev == devNullStat.Rdev {
|
| 237 | 237 |
// Close and re-open the fd. |
| 238 | 238 |
if err = syscall.Dup2(int(file.Fd()), fd); err != nil {
|
| 239 |
- return fmt.Errorf("Failed to dup fd %d to fd %d - %s", file.Fd(), fd)
|
|
| 239 |
+ return fmt.Errorf("Failed to dup fd %d to fd %d - %s", file.Fd(), fd, err)
|
|
| 240 | 240 |
} |
| 241 | 241 |
} |
| 242 | 242 |
} |
| ... | ... |
@@ -56,14 +56,19 @@ func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Wri |
| 56 | 56 |
|
| 57 | 57 |
// Do this before syncing with child so that no children |
| 58 | 58 |
// can escape the cgroup |
| 59 |
- cleaner, err := SetupCgroups(container, command.Process.Pid) |
|
| 59 |
+ cgroupRef, err := SetupCgroups(container, command.Process.Pid) |
|
| 60 | 60 |
if err != nil {
|
| 61 | 61 |
command.Process.Kill() |
| 62 | 62 |
command.Wait() |
| 63 | 63 |
return -1, err |
| 64 | 64 |
} |
| 65 |
- if cleaner != nil {
|
|
| 66 |
- defer cleaner.Cleanup() |
|
| 65 |
+ defer cgroupRef.Cleanup() |
|
| 66 |
+ |
|
| 67 |
+ cgroupPaths, err := cgroupRef.Paths() |
|
| 68 |
+ if err != nil {
|
|
| 69 |
+ command.Process.Kill() |
|
| 70 |
+ command.Wait() |
|
| 71 |
+ return -1, err |
|
| 67 | 72 |
} |
| 68 | 73 |
|
| 69 | 74 |
var networkState network.NetworkState |
| ... | ... |
@@ -77,6 +82,7 @@ func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Wri |
| 77 | 77 |
InitPid: command.Process.Pid, |
| 78 | 78 |
InitStartTime: started, |
| 79 | 79 |
NetworkState: networkState, |
| 80 |
+ CgroupPaths: cgroupPaths, |
|
| 80 | 81 |
} |
| 81 | 82 |
|
| 82 | 83 |
if err := libcontainer.SaveState(dataPath, state); err != nil {
|
| ... | ... |
@@ -3,6 +3,7 @@ |
| 3 | 3 |
package namespaces |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
+ "fmt" |
|
| 6 | 7 |
"io" |
| 7 | 8 |
"os" |
| 8 | 9 |
"os/exec" |
| ... | ... |
@@ -11,6 +12,7 @@ import ( |
| 11 | 11 |
"syscall" |
| 12 | 12 |
|
| 13 | 13 |
"github.com/docker/libcontainer" |
| 14 |
+ "github.com/docker/libcontainer/cgroups" |
|
| 14 | 15 |
"github.com/docker/libcontainer/label" |
| 15 | 16 |
"github.com/docker/libcontainer/syncpipe" |
| 16 | 17 |
"github.com/docker/libcontainer/system" |
| ... | ... |
@@ -18,10 +20,10 @@ import ( |
| 18 | 18 |
|
| 19 | 19 |
// ExecIn reexec's the initPath with the argv 0 rewrite to "nsenter" so that it is able to run the |
| 20 | 20 |
// setns code in a single threaded environment joining the existing containers' namespaces. |
| 21 |
-func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath string, |
|
| 21 |
+func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath, action string, |
|
| 22 | 22 |
stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
|
| 23 | 23 |
|
| 24 |
- args := []string{"nsenter", "--nspid", strconv.Itoa(state.InitPid)}
|
|
| 24 |
+ args := []string{fmt.Sprintf("nsenter-%s", action), "--nspid", strconv.Itoa(state.InitPid)}
|
|
| 25 | 25 |
|
| 26 | 26 |
if console != "" {
|
| 27 | 27 |
args = append(args, "--console", console) |
| ... | ... |
@@ -58,6 +60,11 @@ func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs |
| 58 | 58 |
} |
| 59 | 59 |
pipe.CloseChild() |
| 60 | 60 |
|
| 61 |
+ // Enter cgroups. |
|
| 62 |
+ if err := EnterCgroups(state, cmd.Process.Pid); err != nil {
|
|
| 63 |
+ return -1, err |
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 61 | 66 |
if err := pipe.SendToChild(container); err != nil {
|
| 62 | 67 |
cmd.Process.Kill() |
| 63 | 68 |
cmd.Wait() |
| ... | ... |
@@ -101,3 +108,7 @@ func FinalizeSetns(container *libcontainer.Config, args []string) error {
|
| 101 | 101 |
|
| 102 | 102 |
panic("unreachable")
|
| 103 | 103 |
} |
| 104 |
+ |
|
| 105 |
+func EnterCgroups(state *libcontainer.State, pid int) error {
|
|
| 106 |
+ return cgroups.EnterPid(state.CgroupPaths, pid) |
|
| 107 |
+} |
| ... | ... |
@@ -31,8 +31,8 @@ void get_args(int *argc, char ***argv) |
| 31 | 31 |
contents_size += kBufSize; |
| 32 | 32 |
contents = (char *)realloc(contents, contents_size); |
| 33 | 33 |
bytes_read = |
| 34 |
- read(fd, contents + contents_offset, |
|
| 35 |
- contents_size - contents_offset); |
|
| 34 |
+ read(fd, contents + contents_offset, |
|
| 35 |
+ contents_size - contents_offset); |
|
| 36 | 36 |
contents_offset += bytes_read; |
| 37 | 37 |
} |
| 38 | 38 |
while (bytes_read > 0); |
| ... | ... |
@@ -80,20 +80,20 @@ void nsenter() |
| 80 | 80 |
char **argv; |
| 81 | 81 |
get_args(&argc, &argv); |
| 82 | 82 |
|
| 83 |
- // check argv 0 to ensure that we are supposed to setns |
|
| 84 |
- // we use strncmp to test for a value of "nsenter" but also allows alternate implmentations |
|
| 85 |
- // after the setns code path to continue to use the argv 0 to determine actions to be run |
|
| 86 |
- // resulting in the ability to specify "nsenter-mknod", "nsenter-exec", etc... |
|
| 87 |
- if (strncmp(argv[0], kNsEnter, strlen(kNsEnter)) != 0) {
|
|
| 88 |
- return; |
|
| 89 |
- } |
|
| 83 |
+ // check argv 0 to ensure that we are supposed to setns |
|
| 84 |
+ // we use strncmp to test for a value of "nsenter" but also allows alternate implmentations |
|
| 85 |
+ // after the setns code path to continue to use the argv 0 to determine actions to be run |
|
| 86 |
+ // resulting in the ability to specify "nsenter-mknod", "nsenter-exec", etc... |
|
| 87 |
+ if (strncmp(argv[0], kNsEnter, strlen(kNsEnter)) != 0) {
|
|
| 88 |
+ return; |
|
| 89 |
+ } |
|
| 90 | 90 |
|
| 91 | 91 |
static const struct option longopts[] = {
|
| 92 | 92 |
{"nspid", required_argument, NULL, 'n'},
|
| 93 | 93 |
{"console", required_argument, NULL, 't'},
|
| 94 | 94 |
{NULL, 0, NULL, 0}
|
| 95 | 95 |
}; |
| 96 |
- |
|
| 96 |
+ |
|
| 97 | 97 |
pid_t init_pid = -1; |
| 98 | 98 |
char *init_pid_str = NULL; |
| 99 | 99 |
char *console = NULL; |
| ... | ... |
@@ -651,30 +651,28 @@ func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
| 651 | 651 |
return s.HandleAck(wb.Seq) |
| 652 | 652 |
} |
| 653 | 653 |
|
| 654 |
-// Add an Ip address to an interface. This is identical to: |
|
| 655 |
-// ip addr add $ip/$ipNet dev $iface |
|
| 656 |
-func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
|
| 654 |
+func networkLinkIpAction(action, flags int, ifa IfAddr) error {
|
|
| 657 | 655 |
s, err := getNetlinkSocket() |
| 658 | 656 |
if err != nil {
|
| 659 | 657 |
return err |
| 660 | 658 |
} |
| 661 | 659 |
defer s.Close() |
| 662 | 660 |
|
| 663 |
- family := getIpFamily(ip) |
|
| 661 |
+ family := getIpFamily(ifa.IP) |
|
| 664 | 662 |
|
| 665 |
- wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) |
|
| 663 |
+ wb := newNetlinkRequest(action, flags) |
|
| 666 | 664 |
|
| 667 | 665 |
msg := newIfAddrmsg(family) |
| 668 |
- msg.Index = uint32(iface.Index) |
|
| 669 |
- prefixLen, _ := ipNet.Mask.Size() |
|
| 666 |
+ msg.Index = uint32(ifa.Iface.Index) |
|
| 667 |
+ prefixLen, _ := ifa.IPNet.Mask.Size() |
|
| 670 | 668 |
msg.Prefixlen = uint8(prefixLen) |
| 671 | 669 |
wb.AddData(msg) |
| 672 | 670 |
|
| 673 | 671 |
var ipData []byte |
| 674 | 672 |
if family == syscall.AF_INET {
|
| 675 |
- ipData = ip.To4() |
|
| 673 |
+ ipData = ifa.IP.To4() |
|
| 676 | 674 |
} else {
|
| 677 |
- ipData = ip.To16() |
|
| 675 |
+ ipData = ifa.IP.To16() |
|
| 678 | 676 |
} |
| 679 | 677 |
|
| 680 | 678 |
localData := newRtAttr(syscall.IFA_LOCAL, ipData) |
| ... | ... |
@@ -690,6 +688,26 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
| 690 | 690 |
return s.HandleAck(wb.Seq) |
| 691 | 691 |
} |
| 692 | 692 |
|
| 693 |
+// Delete an IP address from an interface. This is identical to: |
|
| 694 |
+// ip addr del $ip/$ipNet dev $iface |
|
| 695 |
+func NetworkLinkDelIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
|
| 696 |
+ return networkLinkIpAction( |
|
| 697 |
+ syscall.RTM_DELADDR, |
|
| 698 |
+ syscall.NLM_F_ACK, |
|
| 699 |
+ IfAddr{iface, ip, ipNet},
|
|
| 700 |
+ ) |
|
| 701 |
+} |
|
| 702 |
+ |
|
| 703 |
+// Add an Ip address to an interface. This is identical to: |
|
| 704 |
+// ip addr add $ip/$ipNet dev $iface |
|
| 705 |
+func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
|
| 706 |
+ return networkLinkIpAction( |
|
| 707 |
+ syscall.RTM_NEWADDR, |
|
| 708 |
+ syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK, |
|
| 709 |
+ IfAddr{iface, ip, ipNet},
|
|
| 710 |
+ ) |
|
| 711 |
+} |
|
| 712 |
+ |
|
| 693 | 713 |
func zeroTerminated(s string) []byte {
|
| 694 | 714 |
return []byte(s + "\000") |
| 695 | 715 |
} |
| ... | ... |
@@ -2,9 +2,55 @@ package netlink |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"net" |
| 5 |
+ "strings" |
|
| 5 | 6 |
"testing" |
| 6 | 7 |
) |
| 7 | 8 |
|
| 9 |
+func ipAssigned(iface *net.Interface, ip net.IP) bool {
|
|
| 10 |
+ addrs, _ := iface.Addrs() |
|
| 11 |
+ |
|
| 12 |
+ for _, addr := range addrs {
|
|
| 13 |
+ args := strings.SplitN(addr.String(), "/", 2) |
|
| 14 |
+ if args[0] == ip.String() {
|
|
| 15 |
+ return true |
|
| 16 |
+ } |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ return false |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func TestAddDelNetworkIp(t *testing.T) {
|
|
| 23 |
+ if testing.Short() {
|
|
| 24 |
+ return |
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ ifaceName := "lo" |
|
| 28 |
+ ip := net.ParseIP("127.0.1.1")
|
|
| 29 |
+ mask := net.IPv4Mask(255, 255, 255, 255) |
|
| 30 |
+ ipNet := &net.IPNet{IP: ip, Mask: mask}
|
|
| 31 |
+ |
|
| 32 |
+ iface, err := net.InterfaceByName(ifaceName) |
|
| 33 |
+ if err != nil {
|
|
| 34 |
+ t.Skip("No 'lo' interface; skipping tests")
|
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ if err := NetworkLinkAddIp(iface, ip, ipNet); err != nil {
|
|
| 38 |
+ t.Fatal(err) |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ if !ipAssigned(iface, ip) {
|
|
| 42 |
+ t.Fatalf("Could not locate address '%s' in lo address list.", ip.String())
|
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ if err := NetworkLinkDelIp(iface, ip, ipNet); err != nil {
|
|
| 46 |
+ t.Fatal(err) |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ if ipAssigned(iface, ip) {
|
|
| 50 |
+ t.Fatalf("Located address '%s' in lo address list after removal.", ip.String())
|
|
| 51 |
+ } |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 8 | 54 |
func TestCreateBridgeWithMac(t *testing.T) {
|
| 9 | 55 |
if testing.Short() {
|
| 10 | 56 |
return |
| ... | ... |
@@ -31,6 +31,10 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
| 31 | 31 |
return ErrNotImplemented |
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 |
+func NetworkLinkDelIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
|
| 35 |
+ return ErrNotImplemented |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 34 | 38 |
func AddRoute(destination, source, gateway, device string) error {
|
| 35 | 39 |
return ErrNotImplemented |
| 36 | 40 |
} |
| ... | ... |
@@ -44,6 +44,14 @@ func SetInterfaceInNamespacePid(name string, nsPid int) error {
|
| 44 | 44 |
return netlink.NetworkSetNsPid(iface, nsPid) |
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 |
+func SetInterfaceInNamespaceFd(name string, fd uintptr) error {
|
|
| 48 |
+ iface, err := net.InterfaceByName(name) |
|
| 49 |
+ if err != nil {
|
|
| 50 |
+ return err |
|
| 51 |
+ } |
|
| 52 |
+ return netlink.NetworkSetNsFd(iface, int(fd)) |
|
| 53 |
+} |
|
| 54 |
+ |
|
| 47 | 55 |
func SetInterfaceMaster(name, master string) error {
|
| 48 | 56 |
iface, err := net.InterfaceByName(name) |
| 49 | 57 |
if err != nil {
|
| 50 | 58 |
deleted file mode 100644 |
| ... | ... |
@@ -1,64 +0,0 @@ |
| 1 |
-package nsinit |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "log" |
|
| 5 |
- "os" |
|
| 6 |
- |
|
| 7 |
- "github.com/codegangsta/cli" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-var ( |
|
| 11 |
- logPath = os.Getenv("log")
|
|
| 12 |
- argvs = make(map[string]func()) |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-func init() {
|
|
| 16 |
- argvs["nsenter"] = nsenter |
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-func preload(context *cli.Context) error {
|
|
| 20 |
- if logPath != "" {
|
|
| 21 |
- if err := openLog(logPath); err != nil {
|
|
| 22 |
- return err |
|
| 23 |
- } |
|
| 24 |
- } |
|
| 25 |
- |
|
| 26 |
- return nil |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-func NsInit() {
|
|
| 30 |
- // we need to check our argv 0 for any registred functions to run instead of the |
|
| 31 |
- // normal cli code path |
|
| 32 |
- |
|
| 33 |
- action, exists := argvs[os.Args[0]] |
|
| 34 |
- if exists {
|
|
| 35 |
- action() |
|
| 36 |
- |
|
| 37 |
- return |
|
| 38 |
- } |
|
| 39 |
- |
|
| 40 |
- app := cli.NewApp() |
|
| 41 |
- |
|
| 42 |
- app.Name = "nsinit" |
|
| 43 |
- app.Version = "0.1" |
|
| 44 |
- app.Author = "libcontainer maintainers" |
|
| 45 |
- app.Flags = []cli.Flag{
|
|
| 46 |
- cli.StringFlag{Name: "nspid"},
|
|
| 47 |
- cli.StringFlag{Name: "console"},
|
|
| 48 |
- } |
|
| 49 |
- |
|
| 50 |
- app.Before = preload |
|
| 51 |
- |
|
| 52 |
- app.Commands = []cli.Command{
|
|
| 53 |
- execCommand, |
|
| 54 |
- initCommand, |
|
| 55 |
- statsCommand, |
|
| 56 |
- configCommand, |
|
| 57 |
- pauseCommand, |
|
| 58 |
- unpauseCommand, |
|
| 59 |
- } |
|
| 60 |
- |
|
| 61 |
- if err := app.Run(os.Args); err != nil {
|
|
| 62 |
- log.Fatal(err) |
|
| 63 |
- } |
|
| 64 |
-} |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-package nsinit |
|
| 1 |
+package main |
|
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| ... | ... |
@@ -15,7 +15,7 @@ var configCommand = cli.Command{
|
| 15 | 15 |
} |
| 16 | 16 |
|
| 17 | 17 |
func configAction(context *cli.Context) {
|
| 18 |
- container, err := loadContainer() |
|
| 18 |
+ container, err := loadConfig() |
|
| 19 | 19 |
if err != nil {
|
| 20 | 20 |
log.Fatal(err) |
| 21 | 21 |
} |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-package nsinit |
|
| 1 |
+package main |
|
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
"os/exec" |
| 9 | 9 |
"os/signal" |
| 10 | 10 |
"syscall" |
| 11 |
+ "text/tabwriter" |
|
| 11 | 12 |
|
| 12 | 13 |
"github.com/codegangsta/cli" |
| 13 | 14 |
"github.com/docker/docker/pkg/term" |
| ... | ... |
@@ -20,12 +21,29 @@ var execCommand = cli.Command{
|
| 20 | 20 |
Name: "exec", |
| 21 | 21 |
Usage: "execute a new command inside a container", |
| 22 | 22 |
Action: execAction, |
| 23 |
+ Flags: []cli.Flag{
|
|
| 24 |
+ cli.BoolFlag{Name: "list", Usage: "list all registered exec functions"},
|
|
| 25 |
+ cli.StringFlag{Name: "func", Value: "exec", Usage: "function name to exec inside a container"},
|
|
| 26 |
+ }, |
|
| 23 | 27 |
} |
| 24 | 28 |
|
| 25 | 29 |
func execAction(context *cli.Context) {
|
| 30 |
+ if context.Bool("list") {
|
|
| 31 |
+ w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) |
|
| 32 |
+ fmt.Fprint(w, "NAME\tUSAGE\n") |
|
| 33 |
+ |
|
| 34 |
+ for k, f := range argvs {
|
|
| 35 |
+ fmt.Fprintf(w, "%s\t%s\n", k, f.Usage) |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ w.Flush() |
|
| 39 |
+ |
|
| 40 |
+ return |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 26 | 43 |
var exitCode int |
| 27 | 44 |
|
| 28 |
- container, err := loadContainer() |
|
| 45 |
+ container, err := loadConfig() |
|
| 29 | 46 |
if err != nil {
|
| 30 | 47 |
log.Fatal(err) |
| 31 | 48 |
} |
| ... | ... |
@@ -36,7 +54,7 @@ func execAction(context *cli.Context) {
|
| 36 | 36 |
} |
| 37 | 37 |
|
| 38 | 38 |
if state != nil {
|
| 39 |
- exitCode, err = startInExistingContainer(container, state, context) |
|
| 39 |
+ exitCode, err = startInExistingContainer(container, state, context.String("func"), context)
|
|
| 40 | 40 |
} else {
|
| 41 | 41 |
exitCode, err = startContainer(container, dataPath, []string(context.Args())) |
| 42 | 42 |
} |
| ... | ... |
@@ -52,7 +70,7 @@ func execAction(context *cli.Context) {
|
| 52 | 52 |
// with the nsenter argument so that the C code can setns an the namespaces that we require. Then that |
| 53 | 53 |
// code path will drop us into the path that we can do the final setup of the namespace and exec the users |
| 54 | 54 |
// application. |
| 55 |
-func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, context *cli.Context) (int, error) {
|
|
| 55 |
+func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, action string, context *cli.Context) (int, error) {
|
|
| 56 | 56 |
var ( |
| 57 | 57 |
master *os.File |
| 58 | 58 |
console string |
| ... | ... |
@@ -102,7 +120,7 @@ func startInExistingContainer(config *libcontainer.Config, state *libcontainer.S |
| 102 | 102 |
}() |
| 103 | 103 |
} |
| 104 | 104 |
|
| 105 |
- return namespaces.ExecIn(config, state, context.Args(), os.Args[0], stdin, stdout, stderr, console, startCallback) |
|
| 105 |
+ return namespaces.ExecIn(config, state, context.Args(), os.Args[0], action, stdin, stdout, stderr, console, startCallback) |
|
| 106 | 106 |
} |
| 107 | 107 |
|
| 108 | 108 |
// startContainer starts the container. Returns the exit status or -1 and an |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-package nsinit |
|
| 1 |
+package main |
|
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"log" |
| ... | ... |
@@ -26,7 +26,7 @@ var ( |
| 26 | 26 |
func initAction(context *cli.Context) {
|
| 27 | 27 |
runtime.LockOSThread() |
| 28 | 28 |
|
| 29 |
- container, err := loadContainer() |
|
| 29 |
+ container, err := loadConfig() |
|
| 30 | 30 |
if err != nil {
|
| 31 | 31 |
log.Fatal(err) |
| 32 | 32 |
} |
| 33 | 33 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,67 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "log" |
|
| 4 |
+ "os" |
|
| 5 |
+ "strings" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/codegangsta/cli" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+var ( |
|
| 11 |
+ logPath = os.Getenv("log")
|
|
| 12 |
+ argvs = make(map[string]*rFunc) |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+func init() {
|
|
| 16 |
+ argvs["exec"] = &rFunc{
|
|
| 17 |
+ Usage: "execute a process inside an existing container", |
|
| 18 |
+ Action: nsenterExec, |
|
| 19 |
+ } |
|
| 20 |
+ |
|
| 21 |
+ argvs["mknod"] = &rFunc{
|
|
| 22 |
+ Usage: "mknod a device inside an existing container", |
|
| 23 |
+ Action: nsenterMknod, |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ argvs["ip"] = &rFunc{
|
|
| 27 |
+ Usage: "display the container's network interfaces", |
|
| 28 |
+ Action: nsenterIp, |
|
| 29 |
+ } |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func main() {
|
|
| 33 |
+ // we need to check our argv 0 for any registred functions to run instead of the |
|
| 34 |
+ // normal cli code path |
|
| 35 |
+ f, exists := argvs[strings.TrimPrefix(os.Args[0], "nsenter-")] |
|
| 36 |
+ if exists {
|
|
| 37 |
+ runFunc(f) |
|
| 38 |
+ |
|
| 39 |
+ return |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ app := cli.NewApp() |
|
| 43 |
+ |
|
| 44 |
+ app.Name = "nsinit" |
|
| 45 |
+ app.Version = "0.1" |
|
| 46 |
+ app.Author = "libcontainer maintainers" |
|
| 47 |
+ app.Flags = []cli.Flag{
|
|
| 48 |
+ cli.StringFlag{Name: "nspid"},
|
|
| 49 |
+ cli.StringFlag{Name: "console"},
|
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ app.Before = preload |
|
| 53 |
+ |
|
| 54 |
+ app.Commands = []cli.Command{
|
|
| 55 |
+ execCommand, |
|
| 56 |
+ initCommand, |
|
| 57 |
+ statsCommand, |
|
| 58 |
+ configCommand, |
|
| 59 |
+ pauseCommand, |
|
| 60 |
+ unpauseCommand, |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ if err := app.Run(os.Args); err != nil {
|
|
| 64 |
+ log.Fatal(err) |
|
| 65 |
+ } |
|
| 66 |
+} |
| ... | ... |
@@ -1,42 +1,84 @@ |
| 1 |
-package nsinit |
|
| 1 |
+package main |
|
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 4 | 5 |
"log" |
| 6 |
+ "net" |
|
| 5 | 7 |
"os" |
| 8 |
+ "strconv" |
|
| 9 |
+ "strings" |
|
| 10 |
+ "text/tabwriter" |
|
| 6 | 11 |
|
| 7 | 12 |
"github.com/docker/libcontainer" |
| 13 |
+ "github.com/docker/libcontainer/devices" |
|
| 14 |
+ "github.com/docker/libcontainer/mount/nodes" |
|
| 8 | 15 |
"github.com/docker/libcontainer/namespaces" |
| 9 | 16 |
_ "github.com/docker/libcontainer/namespaces/nsenter" |
| 10 |
- "github.com/docker/libcontainer/syncpipe" |
|
| 11 | 17 |
) |
| 12 | 18 |
|
| 13 |
-func findUserArgs() []string {
|
|
| 14 |
- i := 0 |
|
| 15 |
- for _, a := range os.Args {
|
|
| 16 |
- i++ |
|
| 19 |
+// nsenterExec exec's a process inside an existing container |
|
| 20 |
+func nsenterExec(config *libcontainer.Config, args []string) {
|
|
| 21 |
+ if err := namespaces.FinalizeSetns(config, args); err != nil {
|
|
| 22 |
+ log.Fatalf("failed to nsenter: %s", err)
|
|
| 23 |
+ } |
|
| 24 |
+} |
|
| 17 | 25 |
|
| 18 |
- if a == "--" {
|
|
| 19 |
- break |
|
| 20 |
- } |
|
| 26 |
+// nsenterMknod runs mknod inside an existing container |
|
| 27 |
+// |
|
| 28 |
+// mknod <path> <type> <major> <minor> |
|
| 29 |
+func nsenterMknod(config *libcontainer.Config, args []string) {
|
|
| 30 |
+ if len(args) != 4 {
|
|
| 31 |
+ log.Fatalf("expected mknod to have 4 arguments not %d", len(args))
|
|
| 21 | 32 |
} |
| 22 | 33 |
|
| 23 |
- return os.Args[i:] |
|
| 24 |
-} |
|
| 34 |
+ t := rune(args[1][0]) |
|
| 25 | 35 |
|
| 26 |
-// this expects that we already have our namespaces setup by the C initializer |
|
| 27 |
-// we are expected to finalize the namespace and exec the user's application |
|
| 28 |
-func nsenter() {
|
|
| 29 |
- syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3) |
|
| 36 |
+ major, err := strconv.Atoi(args[2]) |
|
| 30 | 37 |
if err != nil {
|
| 31 |
- log.Fatalf("unable to create sync pipe: %s", err)
|
|
| 38 |
+ log.Fatal(err) |
|
| 32 | 39 |
} |
| 33 | 40 |
|
| 34 |
- var config *libcontainer.Config |
|
| 35 |
- if err := syncPipe.ReadFromParent(&config); err != nil {
|
|
| 36 |
- log.Fatalf("reading container config from parent: %s", err)
|
|
| 41 |
+ minor, err := strconv.Atoi(args[3]) |
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ log.Fatal(err) |
|
| 37 | 44 |
} |
| 38 | 45 |
|
| 39 |
- if err := namespaces.FinalizeSetns(config, findUserArgs()); err != nil {
|
|
| 40 |
- log.Fatalf("failed to nsenter: %s", err)
|
|
| 46 |
+ n := &devices.Device{
|
|
| 47 |
+ Path: args[0], |
|
| 48 |
+ Type: t, |
|
| 49 |
+ MajorNumber: int64(major), |
|
| 50 |
+ MinorNumber: int64(minor), |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ if err := nodes.CreateDeviceNode("/", n); err != nil {
|
|
| 54 |
+ log.Fatal(err) |
|
| 55 |
+ } |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+// nsenterIp displays the network interfaces inside a container's net namespace |
|
| 59 |
+func nsenterIp(config *libcontainer.Config, args []string) {
|
|
| 60 |
+ interfaces, err := net.Interfaces() |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ log.Fatal(err) |
|
| 41 | 63 |
} |
| 64 |
+ |
|
| 65 |
+ w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) |
|
| 66 |
+ fmt.Fprint(w, "NAME\tMTU\tMAC\tFLAG\tADDRS\n") |
|
| 67 |
+ |
|
| 68 |
+ for _, iface := range interfaces {
|
|
| 69 |
+ addrs, err := iface.Addrs() |
|
| 70 |
+ if err != nil {
|
|
| 71 |
+ log.Fatal(err) |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ o := []string{}
|
|
| 75 |
+ |
|
| 76 |
+ for _, a := range addrs {
|
|
| 77 |
+ o = append(o, a.String()) |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", iface.Name, iface.MTU, iface.HardwareAddr, iface.Flags, strings.Join(o, ",")) |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ w.Flush() |
|
| 42 | 84 |
} |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-package nsinit |
|
| 1 |
+package main |
|
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"log" |
| ... | ... |
@@ -34,7 +34,7 @@ func unpauseAction(context *cli.Context) {
|
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 | 36 |
func toggle(state cgroups.FreezerState) error {
|
| 37 |
- container, err := loadContainer() |
|
| 37 |
+ container, err := loadConfig() |
|
| 38 | 38 |
if err != nil {
|
| 39 | 39 |
return err |
| 40 | 40 |
} |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-package nsinit |
|
| 1 |
+package main |
|
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| ... | ... |
@@ -16,7 +16,7 @@ var statsCommand = cli.Command{
|
| 16 | 16 |
} |
| 17 | 17 |
|
| 18 | 18 |
func statsAction(context *cli.Context) {
|
| 19 |
- container, err := loadContainer() |
|
| 19 |
+ container, err := loadConfig() |
|
| 20 | 20 |
if err != nil {
|
| 21 | 21 |
log.Fatal(err) |
| 22 | 22 |
} |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-package nsinit |
|
| 1 |
+package main |
|
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| ... | ... |
@@ -6,10 +6,18 @@ import ( |
| 6 | 6 |
"os" |
| 7 | 7 |
"path/filepath" |
| 8 | 8 |
|
| 9 |
+ "github.com/codegangsta/cli" |
|
| 9 | 10 |
"github.com/docker/libcontainer" |
| 11 |
+ "github.com/docker/libcontainer/syncpipe" |
|
| 10 | 12 |
) |
| 11 | 13 |
|
| 12 |
-func loadContainer() (*libcontainer.Config, error) {
|
|
| 14 |
+// rFunc is a function registration for calling after an execin |
|
| 15 |
+type rFunc struct {
|
|
| 16 |
+ Usage string |
|
| 17 |
+ Action func(*libcontainer.Config, []string) |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func loadConfig() (*libcontainer.Config, error) {
|
|
| 13 | 21 |
f, err := os.Open(filepath.Join(dataPath, "container.json")) |
| 14 | 22 |
if err != nil {
|
| 15 | 23 |
return nil, err |
| ... | ... |
@@ -35,12 +43,52 @@ func openLog(name string) error {
|
| 35 | 35 |
return nil |
| 36 | 36 |
} |
| 37 | 37 |
|
| 38 |
-func loadContainerFromJson(rawData string) (*libcontainer.Config, error) {
|
|
| 39 |
- var container *libcontainer.Config |
|
| 38 |
+func findUserArgs() []string {
|
|
| 39 |
+ i := 0 |
|
| 40 |
+ for _, a := range os.Args {
|
|
| 41 |
+ i++ |
|
| 42 |
+ |
|
| 43 |
+ if a == "--" {
|
|
| 44 |
+ break |
|
| 45 |
+ } |
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ return os.Args[i:] |
|
| 49 |
+} |
|
| 40 | 50 |
|
| 41 |
- if err := json.Unmarshal([]byte(rawData), &container); err != nil {
|
|
| 51 |
+// loadConfigFromFd loads a container's config from the sync pipe that is provided by |
|
| 52 |
+// fd 3 when running a process |
|
| 53 |
+func loadConfigFromFd() (*libcontainer.Config, error) {
|
|
| 54 |
+ syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3) |
|
| 55 |
+ if err != nil {
|
|
| 42 | 56 |
return nil, err |
| 43 | 57 |
} |
| 44 | 58 |
|
| 45 |
- return container, nil |
|
| 59 |
+ var config *libcontainer.Config |
|
| 60 |
+ if err := syncPipe.ReadFromParent(&config); err != nil {
|
|
| 61 |
+ return nil, err |
|
| 62 |
+ } |
|
| 63 |
+ |
|
| 64 |
+ return config, nil |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+func preload(context *cli.Context) error {
|
|
| 68 |
+ if logPath != "" {
|
|
| 69 |
+ if err := openLog(logPath); err != nil {
|
|
| 70 |
+ return err |
|
| 71 |
+ } |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ return nil |
|
| 75 |
+} |
|
| 76 |
+ |
|
| 77 |
+func runFunc(f *rFunc) {
|
|
| 78 |
+ userArgs := findUserArgs() |
|
| 79 |
+ |
|
| 80 |
+ config, err := loadConfigFromFd() |
|
| 81 |
+ if err != nil {
|
|
| 82 |
+ log.Fatalf("unable to receive config from sync pipe: %s", err)
|
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ f.Action(config, userArgs) |
|
| 46 | 86 |
} |
| ... | ... |
@@ -18,6 +18,9 @@ type State struct {
|
| 18 | 18 |
|
| 19 | 19 |
// Network runtime state. |
| 20 | 20 |
NetworkState network.NetworkState `json:"network_state,omitempty"` |
| 21 |
+ |
|
| 22 |
+ // Path to all the cgroups setup for a container. Key is cgroup subsystem name. |
|
| 23 |
+ CgroupPaths map[string]string `json:"cgroup_paths,omitempty"` |
|
| 21 | 24 |
} |
| 22 | 25 |
|
| 23 | 26 |
// The running state of the container. |