2019-09-30 10:53:51 -04:00

652 lines
18 KiB
Go

// 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: device operation lib
// Author: zhangwei
// Create: 2018-01-18
package libdevice
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
hconfig "isula.org/isulad-tools/config"
"isula.org/isulad-tools/container"
"isula.org/isulad-tools/libdevice/nsexec"
"isula.org/isulad-tools/pkg/udevd"
"isula.org/isulad-tools/types"
"isula.org/isulad-tools/utils"
)
func checkDevice(config hconfig.ContainerConfig, devs []*types.Device, opts *types.AddDeviceOptions) error {
devices := devs
// check all devices, config updated
if len(devs) == 0 {
for _, dm := range config.GetAllDevices() {
devices = append(devices, &types.Device{
Type: dm.Type,
Major: dm.Major,
Minor: dm.Minor,
PathOnHost: dm.PathOnHost,
Path: dm.PathInContainer,
})
}
}
checkDeviceQos := func(qos *types.Qos) bool {
for _, device := range devices {
if device.Major == qos.Major && device.Minor == qos.Minor {
return true
}
}
return false
}
qosOpts := append(opts.ReadBps, opts.WriteBps...)
qosOpts = append(qosOpts, opts.ReadIOPS...)
qosOpts = append(qosOpts, opts.WriteIOPS...)
for _, opt := range qosOpts {
if !checkDeviceQos(opt) {
return fmt.Errorf("device %v was not added to container or not in add-device args", opt.Path)
}
}
return nil
}
// UpdateDeviceOwner update device owner
func UpdateDeviceOwner(spec *specs.Spec, device *types.Device) {
if spec == nil {
return
}
uid, gid := utils.GetUIDGid(spec)
if uid != -1 {
device.UID = uint32(uid)
}
if gid != -1 {
device.GID = uint32(gid)
}
}
// AddDevice will add devices to a container.
func AddDevice(c *container.Container, devices []*types.Device, opts *types.AddDeviceOptions) error {
driver := nsexec.NewDefaultNsDriver()
pid := strconv.Itoa(c.Pid())
innerPath, err := c.GetCgroupPath()
if err != nil {
return err
}
cgroupPath, err := FindCgroupPath(pid, "devices", innerPath)
if err != nil {
return err
}
for _, device := range devices {
UpdateDeviceOwner(c.GetSpec(), device)
}
udevdCtrl := udevd.NewUdevdController()
// lockFile := <container config path>/lock
// 1. use file lock, to make sure only one process to access this config file.
// 2. different container has different lock, will not block other container.
if err := c.Lock(); err != nil {
return err
}
defer c.Unlock()
// create config file handler.
config, err := hconfig.NewContainerConfig(c)
if err != nil {
return err
}
if err := checkDevice(config, devices, opts); err != nil {
return err
}
defer func() {
if err := config.Flush(); err != nil {
logrus.Infof("config Flush error:%v", err)
}
}()
if err := udevdCtrl.Lock(); err != nil {
return err
}
defer udevdCtrl.Unlock()
if err := udevdCtrl.LoadRules(); err != nil {
return err
}
defer udevdCtrl.ToDisk()
var retErr []error
// add device and udpate cgroup here
for _, device := range devices {
// update config here
if err = config.UpdateDevice(device, true); err != nil {
retErr = append(retErr, err)
continue
}
r := &udevd.Rule{
Name: device.PathOnHost,
Container: c.ContainerID(),
CtrDevName: device.Path,
}
if device.Type != "c" {
devType, err := types.GetDeviceType(device.PathOnHost)
if err != nil {
retErr = append(retErr, err)
config.UpdateDevice(device, false)
continue
}
if devType == "disk" {
udevdCtrl.AddRule(r)
}
}
// Do not insert device and update cgroup when:
// 1. update-config-only flag is set
// 2. container isn't running (pid==0)
if !opts.UpdateConfigOnly && c.Pid() > 0 && c.CheckPidExist() {
// add device to container.
if err = driver.AddDevice(pid, device, opts.Force); err != nil {
retErr = append(retErr, err)
// roll back config and udev rules
config.UpdateDevice(device, false)
udevdCtrl.RemoveRule(r)
continue
}
// update cgroup access permission.
if err = UpdateCgroupPermission(cgroupPath, device, true); err != nil {
retErr = append(retErr, err)
// roll back config and udev rules and remove device
driver.RemoveDevice(pid, device)
config.UpdateDevice(device, false)
udevdCtrl.RemoveRule(r)
continue
}
}
fmt.Fprintf(os.Stdout, "Add device (%s) to container(%s,%s) done.\n", device.PathOnHost, c.Name(), device.Path)
logrus.Infof("Add device (%s) to container(%s,%s) done", device.PathOnHost, c.Name(), device.Path)
}
if err := updateQos(config, pid, innerPath, opts); err != nil {
return err
}
if len(retErr) == 0 {
return nil
}
for i := 0; i < len(retErr); i++ {
retErr[i] = fmt.Errorf("%s", retErr[i].Error())
}
return errors.New(strings.Trim(fmt.Sprint(retErr), "[]"))
}
// UpdateDevice will update device for container.
func UpdateDevice(c *container.Container, opts *types.AddDeviceOptions) error {
pid := strconv.Itoa(c.Pid())
innerPath, err := c.GetCgroupPath()
if err != nil {
return err
}
if err := c.Lock(); err != nil {
return err
}
defer c.Unlock()
// create config file handler.
config, err := hconfig.NewContainerConfig(c)
if err != nil {
return err
}
if err := checkDevice(config, []*types.Device{}, opts); err != nil {
return err
}
defer func() {
if err := config.Flush(); err != nil {
logrus.Infof("config Flush error:%v", err)
}
}()
if err := updateQos(config, pid, innerPath, opts); err != nil {
return err
}
return nil
}
// RemoveDevice will remove devices from container
func RemoveDevice(c *container.Container, devices []*types.Device, followPartition bool) error {
driver := nsexec.NewDefaultNsDriver()
pid := strconv.Itoa(c.Pid())
innerPath, err := c.GetCgroupPath()
if err != nil {
return err
}
cgroupPath, err := FindCgroupPath(pid, "devices", innerPath)
if err != nil {
return err
}
if err := c.Lock(); err != nil {
return err
}
defer c.Unlock()
config, err := hconfig.NewContainerConfig(c)
if err != nil {
return err
}
defer config.Flush()
udevdCtrl := udevd.NewUdevdController()
if err := udevdCtrl.Lock(); err != nil {
return err
}
defer udevdCtrl.Unlock()
if err := udevdCtrl.LoadRules(); err != nil {
return err
}
defer udevdCtrl.ToDisk()
var retErr []error
var newDevices []*types.Device
for _, device := range devices {
newDevice := config.FindDeviceByMapping(device)
if newDevice == nil {
errinfo := fmt.Sprint("Device pair(", device.PathOnHost, ":", device.Path, ") is not added by isulad-tools, can not remove it, please check input parameter.")
retErr = append(retErr, errors.New(errinfo))
continue
}
newDevices = append(newDevices, newDevice)
if followPartition {
subDevices := config.FindSubPartition(newDevice)
for _, subDev := range subDevices {
// check the sub partition is added by isulad-tools
if found := config.FindDeviceByMapping(subDev); found == nil {
continue
}
found := false
for _, eDev := range newDevices {
if subDev.Path == eDev.Path && subDev.PathOnHost == eDev.PathOnHost {
found = true
break
}
}
if !found {
newDevices = append(newDevices, subDev)
}
}
}
}
for _, device := range newDevices {
// update config.
if err = config.UpdateDevice(device, false); err != nil {
retErr = append(retErr, err)
continue
}
r := &udevd.Rule{
Name: device.PathOnHost,
CtrDevName: device.Path,
Container: c.ContainerID(),
}
udevdCtrl.RemoveRule(r)
// only update for running container
if c.Pid() > 0 && c.CheckPidExist() {
if err = driver.RemoveDevice(pid, device); err != nil {
config.UpdateDevice(device, true)
if device.Type != "c" {
devType, err := types.GetDeviceType(device.PathOnHost)
if err != nil {
retErr = append(retErr, err)
config.UpdateDevice(device, true)
continue
}
if devType == "disk" {
udevdCtrl.AddRule(r)
}
}
retErr = append(retErr, err)
continue
}
// update cgroup access permission.
if err = UpdateCgroupPermission(cgroupPath, device, false); err != nil {
// TODO: also need a roll back?
retErr = append(retErr, err)
continue
}
}
fmt.Fprintf(os.Stdout, "Remove device (%s) from container(%s,%s) done.\n", device.PathOnHost, c.Name(), device.Path)
logrus.Infof("Remove device (%s) from container(%s,%s) done.\n", device.PathOnHost, c.Name(), device.Path)
if err := removeQos(config, pid, innerPath, device); err != nil {
retErr = append(retErr, err)
}
}
if len(retErr) == 0 {
return nil
}
for i := 0; i < len(retErr); i++ {
retErr[i] = fmt.Errorf("%s", retErr[i].Error())
}
return errors.New(strings.Trim(fmt.Sprint(retErr), "[]"))
}
// ListDevice list container devices
func ListDevice(c *container.Container) ([]*hconfig.DeviceMapping, []*hconfig.DeviceMapping, error) {
if err := c.Lock(); err != nil {
return nil, nil, err
}
defer c.Unlock()
hConfig, err := hconfig.NewContainerConfig(c)
if err != nil {
return nil, nil, err
}
allDevice := hConfig.GetAllDevices()
var majorDevices []*hconfig.DeviceMapping
for _, sDevice := range allDevice {
if sDevice.Parent == "" {
majorDevices = append(majorDevices, sDevice)
}
}
return allDevice, majorDevices, nil
}
// AddPath will add paths from host to container
func AddPath(c *container.Container, binds []*types.Bind) error {
driver := nsexec.NewDefaultNsDriver()
pid := strconv.Itoa(c.Pid())
if err := c.Lock(); err != nil {
return fmt.Errorf("AddPath: failed to get lock, err: %s", err)
}
defer c.Unlock()
config, err := hconfig.NewContainerConfig(c)
if err != nil {
return fmt.Errorf("AddPath: failed to create config, err: %s", err)
}
defer config.Flush()
if err := config.CheckPathNum(); err != nil {
return err
}
var retErr []error
for _, bind := range binds {
logrus.Debugf("Adding path: %+v", bind)
// 1. update config.
hostPathExist, err := config.UpdateBind(bind, true)
if err != nil {
return fmt.Errorf("AddPath: failed to UpdateBind, err: %s", err)
}
if c.Pid() > 0 && c.CheckPidExist() {
// 2. prepare transferpath if needed
if err := utils.PrepareTransferPath("/", c.ContainerID(), bind, !hostPathExist); err != nil {
config.UpdateBind(bind, false)
// if no existed, do unmount
if !hostPathExist {
utils.RemoveTransferPath(c.ContainerID(), bind)
}
return fmt.Errorf("AddPath: failed to prepare transfer base, err: %s", err)
}
if err = driver.AddBind(pid, bind); err != nil {
retErr = append(retErr, err)
config.UpdateBind(bind, false)
if !hostPathExist {
utils.RemoveTransferPath(c.ContainerID(), bind)
}
return fmt.Errorf("AddPath: failed to add bind, err: %s", err)
}
}
msg := fmt.Sprintf("Add path (%s) to container(%s,%s) done.", bind.HostPath, c.Name(), bind.ContainerPath)
fmt.Fprintln(os.Stdout, msg)
logrus.Info(msg)
}
if len(retErr) == 0 {
return nil
}
for i := 0; i < len(retErr); i++ {
retErr[i] = fmt.Errorf("%s", retErr[i].Error())
}
return errors.New(strings.Trim(fmt.Sprint(retErr), "[]"))
}
// RemovePath will remove paths from container
func RemovePath(c *container.Container, binds []*types.Bind) error {
driver := nsexec.NewDefaultNsDriver()
pid := strconv.Itoa(c.Pid())
if err := c.Lock(); err != nil {
return err
}
defer c.Unlock()
config, err := hconfig.NewContainerConfig(c)
if err != nil {
return err
}
defer config.Flush()
var retErr []error
for _, bind := range binds {
mp, err := config.GetBindInConfig(bind)
if err != nil {
retErr = append(retErr, err)
continue
}
if mp == nil {
errinfo := fmt.Sprint("Path pair(", bind.HostPath, ":", bind.ContainerPath, ") is not added by isulad-tools, can not remove it, please check input parameter")
retErr = append(retErr, errors.New(errinfo))
continue
}
bind.MountOption = mp.Permission
// update config.
removeHostPath, err := config.UpdateBind(bind, false)
if err != nil {
retErr = append(retErr, fmt.Errorf("Failed to update bind(%v), error: %s, still try to remove it", bind, err))
}
// remove from container.
if c.Pid() > 0 && c.CheckPidExist() {
if err := driver.RemoveBind(pid, bind); err != nil {
retErr = append(retErr, fmt.Errorf("Failed to remove bind(%v),error: %s", bind, err))
config.UpdateBind(bind, true)
continue
}
if removeHostPath == true {
if err := utils.RemoveTransferPath(c.ContainerID(), bind); err != nil {
retErr = append(retErr, fmt.Errorf("Remove path (%s) from %s failed, err: %s", bind.HostPath, c.Name(), err))
}
}
}
msg := fmt.Sprintf("Remove path (%s) from container(%s,%s) done", bind.HostPath, c.Name(), bind.ContainerPath)
fmt.Fprintln(os.Stdout, msg)
logrus.Info(msg)
}
if len(retErr) == 0 {
return nil
}
for i := 0; i < len(retErr); i++ {
retErr[i] = fmt.Errorf("%s", retErr[i].Error())
}
return errors.New(strings.Trim(fmt.Sprint(retErr), "[]"))
}
// ListPath list container paths
func ListPath(ctr *container.Container) ([]string, error) {
if err := ctr.Lock(); err != nil {
return nil, err
}
defer ctr.Unlock()
hConfig, err := hconfig.NewContainerConfig(ctr)
if err != nil {
return nil, err
}
return hConfig.GetBinds(), nil
}
func updateQos(config hconfig.ContainerConfig, pid, innerPath string, opts *types.AddDeviceOptions) error {
// update device read iops
for _, devReadIOPS := range opts.ReadIOPS {
if err := UpdateCgroupDeviceReadIOPS(pid, innerPath, devReadIOPS.String()); err != nil {
return err
}
if err := config.UpdateDeviceQos(devReadIOPS, hconfig.QosReadIOPS); err != nil {
return err
}
msg := fmt.Sprintf("Update read iops for device (%s,%s) done.", devReadIOPS.Path, devReadIOPS.Value)
fmt.Fprintln(os.Stdout, msg)
logrus.Info(msg)
}
// update device write iops
for _, devWriteIOPS := range opts.WriteIOPS {
if err := UpdateCgroupDeviceWriteIOPS(pid, innerPath, devWriteIOPS.String()); err != nil {
return err
}
if err := config.UpdateDeviceQos(devWriteIOPS, hconfig.QosWriteIOPS); err != nil {
return err
}
msg := fmt.Sprintf("Update write iops for device (%s,%s) done.", devWriteIOPS.Path, devWriteIOPS.Value)
fmt.Fprintln(os.Stdout, msg)
logrus.Info(msg)
}
// update device read bps
for _, devReadBps := range opts.ReadBps {
if err := UpdateCgroupDeviceReadBps(pid, innerPath, devReadBps.String()); err != nil {
return err
}
if err := config.UpdateDeviceQos(devReadBps, hconfig.QosReadBps); err != nil {
return err
}
msg := fmt.Sprintf("Update read bps for device (%s,%s) done.", devReadBps.Path, devReadBps.Value)
fmt.Fprintln(os.Stdout, msg)
logrus.Info(msg)
}
// update device write bps
for _, devWriteBps := range opts.WriteBps {
if err := UpdateCgroupDeviceWriteBps(pid, innerPath, devWriteBps.String()); err != nil {
return err
}
if err := config.UpdateDeviceQos(devWriteBps, hconfig.QosWriteBps); err != nil {
return err
}
msg := fmt.Sprintf("Update write bps for device (%s,%s) done.", devWriteBps.Path, devWriteBps.Value)
fmt.Fprintln(os.Stdout, msg)
logrus.Info(msg)
}
// update device blkio weight
for _, devBlkioWeight := range opts.BlkioWeight {
cfqEnable, err := devBlkioWeight.GetCfqAbility()
if err == nil && cfqEnable {
if err := UpdateCgroupDeviceWeight(pid, innerPath, devBlkioWeight.String()); err != nil {
return err
}
if err := config.UpdateDeviceQos(devBlkioWeight, hconfig.QosBlkioWeight); err != nil {
return err
}
msg := fmt.Sprintf("Update blkio weight for device (%s,%s) done.", devBlkioWeight.Path, devBlkioWeight.Value)
fmt.Fprintln(os.Stdout, msg)
logrus.Info(msg)
} else {
msg := fmt.Sprintf("device not support cfq:%s", devBlkioWeight.Path)
fmt.Fprintln(os.Stdout, msg)
logrus.Info(msg)
}
}
return nil
}
func removeQos(config hconfig.ContainerConfig, pid, innerPath string, device *types.Device) error {
cleanString := fmt.Sprintf("%d:%d 0", device.Major, device.Minor)
if exist, err := config.RemoveDeviceQos(device, hconfig.QosReadIOPS); err != nil {
return err
} else if exist {
if err := UpdateCgroupDeviceReadIOPS(pid, innerPath, cleanString); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Remove read iops for device (%s) done.\n", device.PathOnHost)
}
if exist, err := config.RemoveDeviceQos(device, hconfig.QosWriteIOPS); err != nil {
return err
} else if exist {
if err := UpdateCgroupDeviceWriteIOPS(pid, innerPath, cleanString); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Remove write iops for device (%s) done.\n", device.PathOnHost)
}
if exist, err := config.RemoveDeviceQos(device, hconfig.QosReadBps); err != nil {
return err
} else if exist {
if err := UpdateCgroupDeviceReadBps(pid, innerPath, cleanString); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Remove read bps for device (%s) done.\n", device.PathOnHost)
}
if exist, err := config.RemoveDeviceQos(device, hconfig.QosWriteBps); err != nil {
return err
} else if exist {
if err := UpdateCgroupDeviceWriteBps(pid, innerPath, cleanString); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Remove write bps for device (%s) done.\n", device.PathOnHost)
}
if exist, err := config.RemoveDeviceQos(device, hconfig.QosBlkioWeight); err != nil {
return err
} else if exist {
if err := UpdateCgroupDeviceWeight(pid, innerPath, cleanString); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Remove blkio weight for device (%s) done.\n", device.PathOnHost)
}
return nil
}