reason: update license to Mulan PSL v2 Signed-off-by: taleintervenor <taleintervenor@aliyun.com>
226 lines
6.9 KiB
Go
226 lines
6.9 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 hook config
|
|
// Author: zhangwei
|
|
// Create: 2018-01-18
|
|
|
|
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
|
|
"isula.org/syscontainer-tools/container"
|
|
"isula.org/syscontainer-tools/types"
|
|
)
|
|
|
|
const (
|
|
defaultConfigFile = "device_hook.json"
|
|
// IsuladToolsDir is syscontainer-tools run dir
|
|
IsuladToolsDir = "/run/syscontainer-tools"
|
|
)
|
|
|
|
// QosType defines the qos type by int
|
|
type QosType int
|
|
|
|
const (
|
|
// QosReadIOPS defines the read device iops type
|
|
QosReadIOPS QosType = iota
|
|
// QosWriteIOPS defines the write device iops type
|
|
QosWriteIOPS
|
|
// QosReadBps defines the read device bps type
|
|
QosReadBps
|
|
// QosWriteBps defines the write device bps type
|
|
QosWriteBps
|
|
// QosBlkioWeight defines the device blkio weight type
|
|
QosBlkioWeight
|
|
)
|
|
|
|
// ContainerConfig is the interface of container config handler
|
|
type ContainerConfig interface {
|
|
FindDeviceByMapping(dev *types.Device) *types.Device
|
|
FindSubPartition(dev *types.Device) []*types.Device
|
|
UpdateDevice(device *types.Device, isAddDevice bool) error
|
|
UpdateDeviceNode(device string, major, minor int64)
|
|
|
|
IsBindInConfig(bind *types.Bind) bool
|
|
UpdateBind(bind *types.Bind, isAddBind bool) (bool, error)
|
|
GetBinds() []string
|
|
GetBindInConfig(bind *types.Bind) (*HostMapping, error)
|
|
GetAllDevices() []*DeviceMapping
|
|
DeviceIndexInArray(device *types.Device) int
|
|
UpdateDeviceQos(qos *types.Qos, qType QosType) error
|
|
RemoveDeviceQos(device *types.Device, qType QosType) (bool, error)
|
|
|
|
FindInterfaceByName(config *types.InterfaceConf) *types.InterfaceConf
|
|
IsConflictInterface(nic *types.InterfaceConf) error
|
|
IsSameInterface(nic *types.InterfaceConf) bool
|
|
UpdateNetworkInterface(nic *types.InterfaceConf, isAdd bool) error
|
|
GetNics(filter *types.InterfaceConf) []*types.InterfaceConf
|
|
|
|
IsRouteExist(route *types.Route) bool
|
|
IsConflictRoute(route *types.Route) error
|
|
UpdateNetworkRoutes(route *types.Route, isAdd bool) error
|
|
GetRoutes(filter *types.Route) []*types.Route
|
|
|
|
Flush() error
|
|
CheckPathNum() error
|
|
CheckNicNum() error
|
|
}
|
|
|
|
// NewContainerConfig will create the container config handler by name
|
|
func NewContainerConfig(c *container.Container) (ContainerConfig, error) {
|
|
configfile := filepath.Join(c.ContainerPath(), defaultConfigFile)
|
|
hConfig, err := LoadContainerHookConfig(configfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hConfig.configPath = configfile
|
|
return hConfig, nil
|
|
}
|
|
|
|
// DeviceMapping represents the device mapping between the host and the container.
|
|
type DeviceMapping struct {
|
|
Type string
|
|
Minor int64
|
|
Major int64
|
|
PathOnHost string
|
|
PathInContainer string
|
|
CgroupPermissions string
|
|
Parent string
|
|
}
|
|
|
|
type info struct {
|
|
count int
|
|
perm string
|
|
}
|
|
type bindsInfo struct {
|
|
pathInHost map[string]*info
|
|
pathInContainer map[string]int
|
|
l *sync.Mutex
|
|
}
|
|
|
|
func checkEuqal(old, new string) bool {
|
|
oldp := strings.Split(strings.Replace(old, " ", "", -1), ",")
|
|
newp := strings.Split(strings.Replace(new, " ", "", -1), ",")
|
|
return reflect.DeepEqual(oldp, newp)
|
|
|
|
}
|
|
|
|
// we do not allow mount more than on host paths to a single path in container
|
|
// if we mount a single host path to multi paths in contiainer, return true,nil
|
|
func (bi *bindsInfo) add(bindstr string) (bool, error) {
|
|
|
|
bi.l.Lock()
|
|
defer bi.l.Unlock()
|
|
|
|
hostPathExist := false
|
|
|
|
mp, err := parseMapping(bindstr)
|
|
if err != nil {
|
|
return hostPathExist, fmt.Errorf("Wrong bind format: %s,err %s", bindstr, err)
|
|
}
|
|
|
|
if _, exist := bi.pathInContainer[mp.PathInContainer]; exist == true {
|
|
return hostPathExist, fmt.Errorf("Mount more than one host paths to a single path in container")
|
|
}
|
|
|
|
bi.pathInContainer[mp.PathInContainer] = 1
|
|
|
|
if _, exist := bi.pathInHost[mp.PathOnHost]; exist == true {
|
|
if checkEuqal(mp.Permission, bi.pathInHost[mp.PathOnHost].perm) == false {
|
|
return hostPathExist, fmt.Errorf("Mount one host path with different permissions, old: %s, new: %s", bi.pathInHost[mp.PathOnHost].perm, mp.Permission)
|
|
}
|
|
bi.pathInHost[mp.PathOnHost].count++
|
|
hostPathExist = true
|
|
return hostPathExist, nil
|
|
}
|
|
bi.pathInHost[mp.PathOnHost] = &info{count: 1, perm: mp.Permission}
|
|
return hostPathExist, nil
|
|
}
|
|
|
|
func (bi *bindsInfo) remove(bindstr string) (bool, error) {
|
|
bi.l.Lock()
|
|
defer bi.l.Unlock()
|
|
mp, err := parseMapping(bindstr)
|
|
if err != nil {
|
|
return true, fmt.Errorf("Wrong bind format: %s,err %s", bindstr, err)
|
|
}
|
|
|
|
// always delete the item of container path
|
|
delete(bi.pathInContainer, mp.PathInContainer)
|
|
|
|
if _, exist := bi.pathInHost[mp.PathOnHost]; exist == true {
|
|
bi.pathInHost[mp.PathOnHost].count--
|
|
if bi.pathInHost[mp.PathOnHost].count <= 0 {
|
|
delete(bi.pathInHost, mp.PathOnHost)
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
return true, fmt.Errorf("%s not in memory datebase", mp.PathOnHost)
|
|
|
|
}
|
|
|
|
// ContainerHookConfig is the data config structure for device hook storage file.
|
|
type ContainerHookConfig struct {
|
|
Binds []string `json:"bindToAdd,omitempty"`
|
|
Devices []*DeviceMapping `json:"deviceToAdd,omitempty"`
|
|
ReadIOPS []*types.Qos `json:"readIops,omitempty"`
|
|
WriteIOPS []*types.Qos `json:"writeIops,omitempty"`
|
|
ReadBps []*types.Qos `json:"readBps,omitempty"`
|
|
WriteBps []*types.Qos `json:"writeBps,omitempty"`
|
|
BlkioWeight []*types.Qos `json:"blkioWeight,omitempty"`
|
|
NetworkInterfaces []*types.InterfaceConf `json:"networkInterfaces,omitempty"`
|
|
NetworkRoutes []*types.Route `json:"networkRoute,omitempty"`
|
|
configPath string
|
|
dirty bool
|
|
bi *bindsInfo
|
|
}
|
|
|
|
// LoadContainerHookConfig will parse and unmarshal ContainerHookConfig
|
|
func LoadContainerHookConfig(path string) (*ContainerHookConfig, error) {
|
|
// if config file do not exist, just return empty DevieHookConfig.
|
|
bi := bindsInfo{
|
|
pathInHost: make(map[string]*info),
|
|
pathInContainer: make(map[string]int),
|
|
l: &sync.Mutex{},
|
|
}
|
|
if _, err := os.Stat(path); err != nil {
|
|
return &ContainerHookConfig{bi: &bi}, nil
|
|
}
|
|
bytes, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config := &ContainerHookConfig{}
|
|
|
|
if err := json.Unmarshal(bytes, &config); err != nil {
|
|
return nil, err
|
|
}
|
|
config.bi = &bi
|
|
|
|
for _, bindstr := range config.Binds {
|
|
if _, err := config.bi.add(bindstr); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
config.configPath = path
|
|
|
|
return config, nil
|
|
}
|