reason: update license to Mulan PSL v2 Signed-off-by: taleintervenor <taleintervenor@aliyun.com>
371 lines
10 KiB
Go
371 lines
10 KiB
Go
// Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved.
|
|
// syscontainer-tools is licensed under the Mulan PSL v2.
|
|
// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
// You may obtain a copy of Mulan PSL v2 at:
|
|
// http://license.coscl.org.cn/MulanPSL2
|
|
// 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 v2 for more details.
|
|
// Description: device config operation
|
|
// Author: zhangwei
|
|
// Create: 2018-01-18
|
|
|
|
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"isula.org/syscontainer-tools/types"
|
|
)
|
|
|
|
// HostMapping host path mapping to container path
|
|
type HostMapping struct {
|
|
PathOnHost string
|
|
PathInContainer string
|
|
Permission string
|
|
}
|
|
|
|
const (
|
|
// MaxPathNum is max path number for devices
|
|
MaxPathNum = 128
|
|
// ArrayLen is host bind split array len
|
|
ArrayLen = 3
|
|
)
|
|
|
|
func parseMapping(bind string) (*HostMapping, error) {
|
|
array := strings.SplitN(bind, ":", 3)
|
|
if len(array) < ArrayLen {
|
|
// this function is used for host bind.
|
|
// should not get here, in case won't crash.
|
|
return nil, fmt.Errorf("bind must have two : in string")
|
|
}
|
|
mp := &HostMapping{
|
|
PathOnHost: array[0],
|
|
PathInContainer: array[1],
|
|
Permission: array[2],
|
|
}
|
|
return mp, nil
|
|
}
|
|
|
|
// Flush will flush the config to filesystem
|
|
func (config *ContainerHookConfig) Flush() error {
|
|
if !config.dirty {
|
|
return nil
|
|
}
|
|
file, err := os.Create(config.configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
if err := file.Chmod(0600); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := json.NewEncoder(file).Encode(config); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (config *ContainerHookConfig) bindIndexInArray(bind *types.Bind, array []string) int {
|
|
for index, bindstr := range array {
|
|
mp, err := parseMapping(bindstr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if mp.PathInContainer == bind.ContainerPath && mp.PathOnHost == bind.HostPath {
|
|
return index
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// DeviceIndexInArray get device index in array
|
|
func (config *ContainerHookConfig) DeviceIndexInArray(device *types.Device) int {
|
|
|
|
for index, dev := range config.Devices {
|
|
if (device.PathOnHost == "" && dev.PathInContainer == device.Path) ||
|
|
(device.Path == "" && dev.PathOnHost == device.PathOnHost) ||
|
|
(dev.PathInContainer == device.Path && dev.PathOnHost == device.PathOnHost) {
|
|
return index
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (config *ContainerHookConfig) getConflictIndex(device *types.Device) int {
|
|
|
|
for index, dev := range config.Devices {
|
|
if dev.PathInContainer == device.Path || dev.PathOnHost == device.PathOnHost {
|
|
return index
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// FindDeviceByMapping returns if a device in DeviceToAdd Config.
|
|
func (config *ContainerHookConfig) FindDeviceByMapping(device *types.Device) *types.Device {
|
|
for _, eDevice := range config.Devices {
|
|
if eDevice.PathOnHost == device.PathOnHost && eDevice.PathInContainer == device.Path {
|
|
return &types.Device{
|
|
Path: eDevice.PathInContainer,
|
|
PathOnHost: eDevice.PathOnHost,
|
|
Permissions: eDevice.CgroupPermissions,
|
|
Major: eDevice.Major,
|
|
Minor: eDevice.Minor,
|
|
Type: eDevice.Type,
|
|
Parent: eDevice.Parent,
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FindSubPartition returns a set of sub devices of a device by config.
|
|
func (config *ContainerHookConfig) FindSubPartition(device *types.Device) []*types.Device {
|
|
var ret []*types.Device
|
|
for _, eDevice := range config.Devices {
|
|
if eDevice.Parent == device.PathOnHost {
|
|
ret = append(ret, &types.Device{
|
|
Path: eDevice.PathInContainer,
|
|
PathOnHost: eDevice.PathOnHost,
|
|
Permissions: eDevice.CgroupPermissions,
|
|
Major: eDevice.Major,
|
|
Minor: eDevice.Minor,
|
|
Type: eDevice.Type,
|
|
Parent: eDevice.Parent,
|
|
})
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// UpdateDevice will update hook config of devices
|
|
func (config *ContainerHookConfig) UpdateDevice(device *types.Device, isAddDevice bool) error {
|
|
dev := &DeviceMapping{
|
|
Type: device.Type,
|
|
Major: device.Major,
|
|
Minor: device.Minor,
|
|
PathOnHost: device.PathOnHost,
|
|
PathInContainer: device.Path,
|
|
CgroupPermissions: device.Permissions,
|
|
Parent: device.Parent,
|
|
}
|
|
|
|
// add device action:
|
|
if isAddDevice {
|
|
// if not exist in Add array, add it.
|
|
index := config.getConflictIndex(device)
|
|
if index != -1 {
|
|
conflictDev := config.Devices[index]
|
|
return fmt.Errorf("device %s:%s has been already added into container", conflictDev.PathOnHost, conflictDev.PathInContainer)
|
|
}
|
|
config.dirty = true
|
|
config.Devices = append(config.Devices, dev)
|
|
} else {
|
|
if index := config.DeviceIndexInArray(device); index != -1 {
|
|
config.dirty = true
|
|
config.Devices = append(config.Devices[:index], config.Devices[index+1:]...)
|
|
} else {
|
|
return fmt.Errorf("device %s:%s has not been added into container", device.PathOnHost, device.Path)
|
|
}
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
// IsBindInConfig returns if a device in DeviceToAdd Config.
|
|
func (config *ContainerHookConfig) IsBindInConfig(bind *types.Bind) bool {
|
|
if index := config.bindIndexInArray(bind, config.Binds); index != -1 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetBindInConfig returns device in DeviceToAdd Config.
|
|
func (config *ContainerHookConfig) GetBindInConfig(bind *types.Bind) (*HostMapping, error) {
|
|
if index := config.bindIndexInArray(bind, config.Binds); index != -1 {
|
|
return parseMapping(config.Binds[index])
|
|
}
|
|
return nil, fmt.Errorf("fail to find bind: %v", bind)
|
|
}
|
|
|
|
func (config *ContainerHookConfig) addBind(bind *types.Bind) (bool, error) {
|
|
// if not in Add array, add it.
|
|
exist, err := config.bi.add(bind.ToString())
|
|
if err != nil {
|
|
return exist, err
|
|
}
|
|
if index := config.bindIndexInArray(bind, config.Binds); index == -1 {
|
|
config.dirty = true
|
|
config.Binds = append(config.Binds, bind.ToString())
|
|
}
|
|
return exist, nil
|
|
|
|
}
|
|
func (config *ContainerHookConfig) removeBind(bind *types.Bind) (bool, error) {
|
|
// if in add array, remove it, will not restore to Rm array, as it is added by syscontainer-tools.
|
|
if index := config.bindIndexInArray(bind, config.Binds); index != -1 {
|
|
config.dirty = true
|
|
config.Binds = append(config.Binds[:index], config.Binds[index+1:]...)
|
|
}
|
|
|
|
return config.bi.remove(bind.ToString())
|
|
|
|
}
|
|
|
|
// UpdateBind will update binds of hook config
|
|
func (config *ContainerHookConfig) UpdateBind(bind *types.Bind, isAddBind bool) (bool, error) {
|
|
if isAddBind {
|
|
return config.addBind(bind)
|
|
}
|
|
return config.removeBind(bind)
|
|
}
|
|
|
|
// GetBinds get binds of hook config
|
|
func (config *ContainerHookConfig) GetBinds() []string {
|
|
return config.Binds[:]
|
|
}
|
|
|
|
// GetAllDevices get all devices of hook config
|
|
func (config *ContainerHookConfig) GetAllDevices() []*DeviceMapping {
|
|
return config.Devices[:]
|
|
}
|
|
|
|
// UpdateDeviceQos will update the qos for device
|
|
func (config *ContainerHookConfig) UpdateDeviceQos(qos *types.Qos, qType QosType) error {
|
|
update := func(qosArr []*types.Qos, qos *types.Qos) []*types.Qos {
|
|
for _, q := range qosArr {
|
|
if q.Major == qos.Major && q.Minor == qos.Minor {
|
|
if q.Value != qos.Value {
|
|
config.dirty = true
|
|
q.Value = qos.Value
|
|
}
|
|
return qosArr
|
|
}
|
|
}
|
|
config.dirty = true
|
|
qosArr = append(qosArr, qos)
|
|
return qosArr
|
|
}
|
|
switch qType {
|
|
case QosReadIOPS:
|
|
config.ReadIOPS = update(config.ReadIOPS, qos)
|
|
case QosWriteIOPS:
|
|
config.WriteIOPS = update(config.WriteIOPS, qos)
|
|
case QosReadBps:
|
|
config.ReadBps = update(config.ReadBps, qos)
|
|
case QosWriteBps:
|
|
config.WriteBps = update(config.WriteBps, qos)
|
|
case QosBlkioWeight:
|
|
config.BlkioWeight = update(config.BlkioWeight, qos)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveDeviceQos remove qos for device
|
|
func (config *ContainerHookConfig) RemoveDeviceQos(device *types.Device, qType QosType) (bool, error) {
|
|
remove := func(qosArr []*types.Qos, device *types.Device) ([]*types.Qos, bool) {
|
|
for index, q := range qosArr {
|
|
if q.Major == device.Major && q.Minor == device.Minor {
|
|
config.dirty = true
|
|
qosArr = append(qosArr[:index], qosArr[index+1:]...)
|
|
return qosArr, true
|
|
}
|
|
}
|
|
return qosArr, false
|
|
}
|
|
var ret bool
|
|
switch qType {
|
|
case QosReadIOPS:
|
|
config.ReadIOPS, ret = remove(config.ReadIOPS, device)
|
|
case QosWriteIOPS:
|
|
config.WriteIOPS, ret = remove(config.WriteIOPS, device)
|
|
case QosReadBps:
|
|
config.ReadBps, ret = remove(config.ReadBps, device)
|
|
case QosWriteBps:
|
|
config.WriteBps, ret = remove(config.WriteBps, device)
|
|
case QosBlkioWeight:
|
|
config.BlkioWeight, ret = remove(config.BlkioWeight, device)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// CheckPathNum check path num reach max limit or not
|
|
func (config *ContainerHookConfig) CheckPathNum() error {
|
|
if len(config.Binds) > MaxPathNum {
|
|
return fmt.Errorf("Path already reach max limit")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetConfigDirty set config dir dirty
|
|
func (config *ContainerHookConfig) SetConfigDirty() {
|
|
config.dirty = true
|
|
}
|
|
|
|
// UpdateQosDevNum update qos major and minor for device
|
|
func (config *ContainerHookConfig) UpdateQosDevNum(qos *types.Qos, major int64, minor int64) {
|
|
if qos.Major != major || qos.Minor != minor {
|
|
qos.Major = major
|
|
qos.Minor = minor
|
|
config.dirty = true
|
|
}
|
|
}
|
|
|
|
// UpdateDeviceNode update device node
|
|
func (config *ContainerHookConfig) UpdateDeviceNode(device string, major, minor int64) {
|
|
for index, dev := range config.Devices {
|
|
if dev.PathOnHost == device {
|
|
config.Devices[index].Major = major
|
|
config.Devices[index].Minor = minor
|
|
config.dirty = true
|
|
}
|
|
}
|
|
|
|
for index, qos := range config.ReadIOPS {
|
|
if qos.Path == device {
|
|
config.ReadIOPS[index].Major = major
|
|
config.ReadIOPS[index].Minor = minor
|
|
config.dirty = true
|
|
}
|
|
}
|
|
|
|
for index, qos := range config.WriteIOPS {
|
|
if qos.Path == device {
|
|
config.WriteIOPS[index].Major = major
|
|
config.WriteIOPS[index].Minor = minor
|
|
config.dirty = true
|
|
}
|
|
}
|
|
|
|
for index, qos := range config.ReadBps {
|
|
if qos.Path == device {
|
|
config.ReadBps[index].Major = major
|
|
config.ReadBps[index].Minor = minor
|
|
config.dirty = true
|
|
}
|
|
}
|
|
|
|
for index, qos := range config.WriteBps {
|
|
if qos.Path == device {
|
|
config.WriteBps[index].Major = major
|
|
config.WriteBps[index].Minor = minor
|
|
config.dirty = true
|
|
}
|
|
}
|
|
|
|
for index, qos := range config.BlkioWeight {
|
|
if qos.Path == device {
|
|
config.BlkioWeight[index].Major = major
|
|
config.BlkioWeight[index].Minor = minor
|
|
config.dirty = true
|
|
}
|
|
}
|
|
}
|