container.go 7.1 KB
Newer Older
O
overweight 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
// Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved.
// isulad-tools is licensed under the Mulan PSL v1.
// You can use this software according to the terms and conditions of the Mulan PSL v1.
// You may obtain a copy of Mulan PSL v1 at:
//    http://license.coscl.org.cn/MulanPSL
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
// PURPOSE.
// See the Mulan PSL v1 for more details.
// Description: container config operation
// Author: zhangwei
// Create: 2018-01-18

package container

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

var (
	// deviceHookLock is the default isulad container file lock name
	deviceHookLock = ".device_hook.lock"

	// restrictedNameChars collects the characters allowed to represent a name, normally used to validate container and volume names.
	restrictedNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`

	// restrictedNamePattern is a regular expression to validate names against the collection of restricted characters.
	restrictedNamePattern = regexp.MustCompile(`^/?` + restrictedNameChars + `+$`)
)

// Container is a structure which contains the basic config of isulad containers
type Container struct {
	pid           int
	containerID   string
	containerPath string
	name          string
	spec          *specs.Spec
	lock          *os.File
}

// New will create a container via a container name
func New(name string) (*Container, error) {
	if !restrictedNamePattern.MatchString(name) {
		return nil, fmt.Errorf("Invalid container name (%s), only %s are allowed", name, restrictedNameChars)
	}

58
	graphDriverPath, err := getIsuladGraphDriverPath()
O
overweight 已提交
59 60 61 62 63

	var id, storagePath string
	var pid int
	var spec *specs.Spec
	storagePath = filepath.Join(graphDriverPath, "engines", "lcr")
64
	id, err = getIsuladContainerID(name)
O
overweight 已提交
65 66 67
	if err != nil {
		return nil, err
	}
68
	pid, err = getIsuladContainerPid(name)
O
overweight 已提交
69 70 71
	if err != nil {
		return nil, err
	}
72
	spec, err = getIsuladContainerSpec(id)
O
overweight 已提交
73
	if err != nil {
74
		logrus.Warnf("fail to get isulad container %v spec: %v", id, err)
O
overweight 已提交
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
	}

	container := &Container{
		pid:           pid,
		name:          name,
		containerID:   id,
		spec:          spec,
		containerPath: filepath.Join(storagePath, id),
	}
	return container, nil
}

// Pid returns the pid of the container
func (c *Container) Pid() int {
	return c.pid
}

// ContainerID returns the container id
func (c *Container) ContainerID() string {
	return c.containerID
}

// Name returns the container name input by user.
func (c *Container) Name() string {
	return c.name
}

// ContainerPath returns the container config storage path.
func (c *Container) ContainerPath() string {
	return c.containerPath
}

// NetNsPath returns the net namespace path of the container
func (c *Container) NetNsPath() string {
	return fmt.Sprintf("/proc/%d/ns/net", c.pid)
}

// Lock uses file lock to lock the container
// to make sure only one handler could access the container resource
func (c *Container) Lock() error {
	fileName := filepath.Join(c.ContainerPath(), deviceHookLock)
	f, err := os.OpenFile(fileName, os.O_RDONLY|os.O_CREATE, 0600)
	if err != nil {
		return err
	}

	// FileLock will be released at 3 conditions:
	//  1. process to unlock manully.
	//  2. Close the opened fd.
	//  3. process died without call unlock. kernel will close the file and release the lock.
	// LOCK_EX means only one process could lock it at one time.
	// LOCK_NB is not set, using block mode.
	if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil {
		f.Close()
		return err
	}
	c.lock = f
	return nil
}

// Unlock will release the file lock
func (c *Container) Unlock() error {
	defer c.lock.Close()
	return unix.Flock(int(c.lock.Fd()), unix.LOCK_UN)
}

// GetCgroupPath returns the cgroup-parent segment of the container.
// For isulad container, it is a configurable segment.
func (c *Container) GetCgroupPath() (string, error) {
144
	cmd := exec.Command("isula", "inspect", "-f", "{{json .HostConfig.CgroupParent}}", c.name)
O
overweight 已提交
145 146 147 148 149
	out, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("%s: %v", string(out), err)
	}
	cgroupPath := strings.Trim(string(out), "\n")
150 151 152
    if len(cgroupPath) >= 2 {
        cgroupPath = cgroupPath[1 : len(cgroupPath)-1]
    }
O
overweight 已提交
153 154 155 156 157 158 159 160 161 162 163 164 165 166
	if cgroupPath == "" {
		// by default, the cgroup path is "/lxc/<id>"
		cgroupPath = "/lxc"
	}
	cgroupPath = filepath.Join(cgroupPath, c.containerID)
	return cgroupPath, nil

}

// GetSpec get container spec
func (c *Container) GetSpec() *specs.Spec {
	return c.spec
}

167 168 169
// getIsuladContainerID returns the isulad container ID via the container name
func getIsuladContainerID(name string) (string, error) {
	cmd := exec.Command("isula", "inspect", "-f", "{{json .Id}}", name)
O
overweight 已提交
170 171 172 173 174 175 176
	out, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("%s: %v", string(out), err)
	}
	return strings.Trim(strings.Trim(string(out), "\n"), "\""), nil
}

177 178 179
// getIsuladContainerPid returns the isulad container process id via the container name
func getIsuladContainerPid(name string) (int, error) {
	cmd := exec.Command("isula", "inspect", "-f", "{{json .State.Pid}}", name)
O
overweight 已提交
180 181 182 183 184 185 186 187 188 189 190 191
	out, err := cmd.CombinedOutput()
	if err != nil {
		return -1, fmt.Errorf("%s: %v", string(out), err)
	}
	strPid := strings.Trim(string(out), "\n")
	pid, err := strconv.Atoi(strPid)
	if err != nil {
		return -1, fmt.Errorf("failed to convert %q to int: %v", strPid, err)
	}
	return pid, nil
}

192 193
func getIsuladContainerSpec(id string) (spec *specs.Spec, err error) {
	graphDriverPath, err := getIsuladGraphDriverPath()
O
overweight 已提交
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
	if err != nil {
		return nil, err
	}
	configPath := fmt.Sprintf("%s/engines/lcr/%s/ociconfig.json", graphDriverPath, id)
	config, err := os.Open(configPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, fmt.Errorf("config file %s not found", configPath)
		}
		return nil, err
	}
	defer func() {
		if config != nil {
			config.Close()
		}
	}()
	if err := json.NewDecoder(config).Decode(&spec); err != nil {
		return nil, err
	}
	return spec, nil
}

216 217
func getIsuladGraphDriverPath() (string, error) {
	cmd := exec.Command("isula", "info")
O
overweight 已提交
218 219
	out, err := cmd.CombinedOutput()
	if err != nil {
220
		return "", fmt.Errorf("Exec isula info failed: %v", err)
O
overweight 已提交
221 222 223 224 225 226 227 228 229 230 231 232 233
	}
	// Find "iSulad Root Dir: /xx/xx" line. and out is still has the rest characters.
	if index := strings.Index(string(out), "iSulad Root Dir:"); index != -1 {
		// Split to array, and the first line is the "iSulad Root Dir"
		arr := strings.Split(string(out)[index:], "\n")
		// Split to find "  /xxx/xxx"
		array := strings.Split(arr[0], ":")
		if len(array) > 1 {
			// Trim all the spaces.
			rootdir := strings.Trim(array[1], " ")
			return rootdir, nil
		}
	}
234
	return "", fmt.Errorf("Faild to parse isula info, no \"iSulad Root Dir:\" found")
O
overweight 已提交
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
}

// SetContainerPath set container path
func (c *Container) SetContainerPath(path string) {
	c.containerPath = path
	return
}

// CheckPidExist check pid exist or not
func (c *Container) CheckPidExist() bool {
	if _, err := os.Stat(fmt.Sprintf("/proc/%d", c.Pid())); err != nil {
		return false
	}
	return true
}