2020-07-09 22:27:23 +00:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-07-15 21:32:28 +00:00
|
|
|
"encoding/json"
|
2020-07-09 22:27:23 +00:00
|
|
|
"errors"
|
2020-07-11 11:04:37 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2020-07-09 22:27:23 +00:00
|
|
|
"log"
|
2020-07-11 11:04:37 +00:00
|
|
|
"os"
|
2020-07-13 22:01:42 +00:00
|
|
|
"strconv"
|
2020-07-09 22:27:23 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
|
|
"github.com/docker/docker/api/types/network"
|
|
|
|
dockerClient "github.com/docker/docker/client"
|
|
|
|
"github.com/docker/go-connections/nat"
|
|
|
|
)
|
|
|
|
|
2020-07-15 21:32:28 +00:00
|
|
|
// Stats delay in seconds
|
|
|
|
const statsDelay = 5
|
|
|
|
|
2020-07-09 22:27:23 +00:00
|
|
|
// Docker timeout
|
|
|
|
const dockerTimeout = 10
|
|
|
|
|
|
|
|
// DOCKER_SOCK tells where to connect to docker, it will be always local sock
|
2020-07-13 22:01:42 +00:00
|
|
|
const dockerSock = "/var/run/docker.sock"
|
|
|
|
const podmanSock = "/run/podman/podman.sock"
|
2020-07-09 22:27:23 +00:00
|
|
|
|
|
|
|
// DOCKER_API_VERSION set API version of Docker, 1.40 belongs to Docker 19.03.11
|
2020-07-13 22:01:42 +00:00
|
|
|
const dockerAPIVersion = "1.40"
|
2020-07-09 22:27:23 +00:00
|
|
|
|
|
|
|
// Driver keeps everything for connection to Docker
|
|
|
|
type Driver struct{}
|
|
|
|
|
|
|
|
func (d *Driver) getClient() (*dockerClient.Client, error) {
|
2020-07-13 22:01:42 +00:00
|
|
|
var connectTo string
|
|
|
|
if _, err := os.Stat(podmanSock); !os.IsNotExist(err) {
|
|
|
|
connectTo = podmanSock
|
|
|
|
} else {
|
|
|
|
connectTo = dockerSock
|
|
|
|
}
|
|
|
|
|
|
|
|
cli, err := dockerClient.NewClient("unix://"+connectTo, dockerAPIVersion, nil, nil)
|
2020-07-09 22:27:23 +00:00
|
|
|
return cli, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConnectionStatus checks connection to the Docker daemon
|
|
|
|
func (d *Driver) ConnectionStatus() (bool, error) {
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = cli.ServerVersion(context.TODO())
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Driver) nameToID(name string) (string, error) {
|
|
|
|
containerIDs, err := d.IsExist(name)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(containerIDs) == 0 {
|
|
|
|
return "", errors.New("no container found")
|
|
|
|
}
|
|
|
|
if len(containerIDs) > 1 {
|
|
|
|
return "", errors.New("multiple containers with the same name")
|
|
|
|
}
|
|
|
|
|
|
|
|
return containerIDs[0], nil
|
|
|
|
}
|
|
|
|
|
2020-07-11 11:04:37 +00:00
|
|
|
// Status return current status of container with given name
|
|
|
|
func (d *Driver) Status(name string) (string, error) {
|
|
|
|
status := "unknown"
|
|
|
|
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return status, err
|
|
|
|
}
|
|
|
|
|
|
|
|
containerID, err := d.nameToID(name)
|
2020-07-11 21:14:45 +00:00
|
|
|
if err != nil && err.Error() == "no container found" {
|
|
|
|
return "no-container", err
|
|
|
|
}
|
2020-07-11 11:04:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return status, err
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := cli.ContainerInspect(context.TODO(), containerID)
|
|
|
|
if err != nil {
|
|
|
|
return status, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.State.Running {
|
|
|
|
status = "running"
|
|
|
|
} else {
|
|
|
|
status = "stopped"
|
|
|
|
}
|
|
|
|
|
|
|
|
return status, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stats returns current CPU and memory usage
|
|
|
|
func (d *Driver) Stats(name string) (float64, int, error) {
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return 0.0, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
containerID, err := d.nameToID(name)
|
|
|
|
if err != nil {
|
|
|
|
return 0.0, 0, err
|
|
|
|
}
|
|
|
|
|
2020-07-15 21:32:28 +00:00
|
|
|
rows := make([]ContainerStats, 2)
|
2020-07-11 11:04:37 +00:00
|
|
|
|
2020-07-15 21:32:28 +00:00
|
|
|
for idx := range rows {
|
|
|
|
stats, err := cli.ContainerStats(context.TODO(), containerID, false)
|
|
|
|
if err != nil {
|
|
|
|
return 0.0, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := ioutil.ReadAll(stats.Body)
|
|
|
|
if err != nil {
|
|
|
|
return 0.0, 0, err
|
|
|
|
}
|
|
|
|
// It returns one JSON:
|
|
|
|
// {"read":"2020-07-11T20:42:31.486726241Z","preread":"2020-07-11T20:42:30.484048602Z","pids_stats":{"current":7},"blkio_stats":{"io_service_bytes_recursive":[{"major":253,"minor":0,"op":"Read","value":0},{"major":253,"minor":0,"op":"Write","value":20480},{"major":253,"minor":0,"op":"Sync","value":12288},{"major":253,"minor":0,"op":"Async","value":8192},{"major":253,"minor":0,"op":"Discard","value":0},{"major":253,"minor":0,"op":"Total","value":20480}],"io_serviced_recursive":[{"major":253,"minor":0,"op":"Read","value":0},{"major":253,"minor":0,"op":"Write","value":5},{"major":253,"minor":0,"op":"Sync","value":3},{"major":253,"minor":0,"op":"Async","value":2},{"major":253,"minor":0,"op":"Discard","value":0},{"major":253,"minor":0,"op":"Total","value":5}],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]},"num_procs":0,"storage_stats":{},"cpu_stats":{"cpu_usage":{"total_usage":758392753,"percpu_usage":[302688474,0,11507116,124238500,222136766,5656446,3009320,0,19406386,1397028,6201423,62151294,0,0,0,0],"usage_in_kernelmode":100000000,"usage_in_usermode":640000000},"system_cpu_usage":119385810000000,"online_cpus":12,"throttling_data":{"periods":21,"throttled_periods":1,"throttled_time":2995938}},"precpu_stats":{"cpu_usage":{"total_usage":758282347,"percpu_usage":[302688474,0,11507116,124238500,222026360,5656446,3009320,0,19406386,1397028,6201423,62151294,0,0,0,0],"usage_in_kernelmode":100000000,"usage_in_usermode":640000000},"system_cpu_usage":119373720000000,"online_cpus":12,"throttling_data":{"periods":21,"throttled_periods":1,"throttled_time":2995938}},"memory_stats":{"usage":21626880,"max_usage":22630400,"stats":{"active_anon":15949824,"active_file":0,"cache":0,"dirty":0,"hierarchical_memory_limit":144179200,"hierarchical_memsw_limit":288358400,"inactive_anon":0,"inactive_file":0,"mapped_file":0,"pgfault":13167,"pgmajfault":0,"pgpgin":7293,"pgpgout":3406,"rss":15900672,"rss_huge":0,"total_active_anon":15949824,"total_active_file":0,"total_cache":0,"total_dirty":0,"total_inactive_anon":0,"total_inactive_file":0,"total_mapped_file":0,"total_pgfault":13167,"total_pgmajfault":0,"total_pgpgin":7293,"total_pgpgout":3406,"total_rss":15900672,"total_rss_huge":0,"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0},"limit":144179200},"name":"/test_1234","id":"576878d645efecc8e5e2a57b88351f7b5c551e3fc72dc8473fd965d10dfddbec","networks":{"eth0":{"rx_bytes":6150,"rx_packets":37,"rx_errors":0,"rx_dropped":0,"tx_bytes":0,"tx_packets":0,"tx_errors":0,"tx_dropped":0}}}
|
|
|
|
|
|
|
|
err = json.Unmarshal(data, &rows[idx])
|
|
|
|
if err != nil {
|
|
|
|
return 0.0, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if idx == 0 {
|
|
|
|
time.Sleep(statsDelay * time.Second)
|
|
|
|
}
|
2020-07-11 11:04:37 +00:00
|
|
|
}
|
|
|
|
|
2020-07-15 21:32:28 +00:00
|
|
|
cpuUsage := (float64(rows[1].CPU.Usage.Total) - float64(rows[0].CPU.Usage.Total)) / statsDelay / 10000000
|
|
|
|
|
|
|
|
return cpuUsage, rows[1].Memory.Usage, nil
|
2020-07-11 11:04:37 +00:00
|
|
|
}
|
|
|
|
|
2020-07-09 22:27:23 +00:00
|
|
|
// Remove removes container represented by containerID
|
|
|
|
func (d *Driver) Remove(name string) error {
|
|
|
|
log.Println("Removing container " + name)
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
containerID, err := d.nameToID(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
timeout := time.Duration(dockerTimeout * time.Second)
|
|
|
|
err = cli.ContainerStop(context.TODO(), containerID, &timeout)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = cli.ContainerRemove(context.TODO(), containerID, types.ContainerRemoveOptions{})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start starts container represented by containerID
|
|
|
|
func (d *Driver) Start(name string) error {
|
|
|
|
log.Println("Starting container " + name)
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
containerID, err := d.nameToID(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = cli.ContainerStart(context.TODO(), containerID, types.ContainerStartOptions{})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop stops container represented by containerID
|
|
|
|
func (d *Driver) Stop(name string) error {
|
|
|
|
log.Println("Stopping container " + name)
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
containerID, err := d.nameToID(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
timeout := time.Duration(dockerTimeout * time.Second)
|
|
|
|
err = cli.ContainerStop(context.TODO(), containerID, &timeout)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsExist checks existence of the container based on container name
|
|
|
|
// Returns container IDs in case of existence. Otherwise
|
|
|
|
// empty slice.
|
|
|
|
func (d *Driver) IsExist(name string) ([]string, error) {
|
|
|
|
var containerIDs = make([]string, 0)
|
|
|
|
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return containerIDs, err
|
|
|
|
}
|
|
|
|
|
2020-07-11 11:04:37 +00:00
|
|
|
containers, err := cli.ContainerList(context.TODO(), types.ContainerListOptions{All: true})
|
2020-07-09 22:27:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return containerIDs, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We go through the containers and pick the ones which match the task name
|
|
|
|
for _, containerObject := range containers {
|
2020-07-11 11:04:37 +00:00
|
|
|
for _, containerName := range containerObject.Names {
|
|
|
|
containerName = strings.TrimLeft(containerName, "/")
|
|
|
|
if containerName == name {
|
2020-07-09 22:27:23 +00:00
|
|
|
containerIDs = append(containerIDs, containerObject.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return containerIDs, nil
|
|
|
|
}
|
|
|
|
|
2020-07-11 11:04:37 +00:00
|
|
|
// pullImage pulls image into local docker instance
|
|
|
|
func (d *Driver) pullImage(image string) error {
|
|
|
|
log.Println("Pulling image " + image)
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stream, err := cli.ImagePull(context.TODO(), image, types.ImagePullOptions{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer stream.Close()
|
|
|
|
|
|
|
|
io.Copy(os.Stdout, stream)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-09 22:27:23 +00:00
|
|
|
// Create creates the container
|
|
|
|
// image - docker image
|
|
|
|
// cmd - string slice of command and its arguments
|
|
|
|
// volumePath - host's directory to mount into the container
|
|
|
|
// returns container ID
|
|
|
|
func (d *Driver) Create(name string, image string, volumePath string, HTTPPort int, SSHPort int, CPU int, memory int, cmd []string) (string, error) {
|
|
|
|
log.Println("Creating container " + name)
|
|
|
|
cli, err := d.getClient()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2020-07-11 11:04:37 +00:00
|
|
|
err = d.pullImage(image)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2020-07-11 21:14:45 +00:00
|
|
|
portmaps := nat.PortMap{}
|
2020-07-09 22:27:23 +00:00
|
|
|
|
2020-07-13 22:01:42 +00:00
|
|
|
portbindingsHTTP := make([]nat.PortBinding, 1)
|
|
|
|
portbindingsHTTP[0] = nat.PortBinding{
|
|
|
|
HostIP: "0.0.0.0",
|
|
|
|
HostPort: strconv.Itoa(HTTPPort),
|
|
|
|
}
|
|
|
|
portmaps["8000"] = portbindingsHTTP
|
|
|
|
|
|
|
|
if SSHPort != 0 {
|
|
|
|
portbindingsSSH := make([]nat.PortBinding, 1)
|
|
|
|
portbindingsSSH[0] = nat.PortBinding{
|
|
|
|
HostIP: "0.0.0.0",
|
|
|
|
HostPort: strconv.Itoa(SSHPort),
|
|
|
|
}
|
|
|
|
portmaps["22"] = portbindingsSSH
|
|
|
|
}
|
2020-07-09 22:27:23 +00:00
|
|
|
|
|
|
|
createdContainer, err := cli.ContainerCreate(
|
2020-07-11 21:14:45 +00:00
|
|
|
context.Background(),
|
2020-07-09 22:27:23 +00:00
|
|
|
&container.Config{
|
|
|
|
Hostname: name,
|
|
|
|
Env: []string{},
|
|
|
|
Image: image,
|
|
|
|
Cmd: cmd,
|
|
|
|
},
|
|
|
|
&container.HostConfig{
|
|
|
|
Resources: container.Resources{
|
2020-07-11 11:04:37 +00:00
|
|
|
CPUPeriod: 100000,
|
|
|
|
CPUQuota: int64(CPU) * 1000,
|
2020-07-13 22:01:42 +00:00
|
|
|
Memory: int64(memory*110/100)*1024 ^ 3, // Allow 10 % more memory so we have space for MemoryReservation
|
|
|
|
MemoryReservation: int64(memory)*1024 ^ 3, // This should provide softer way how to limit the memory of our containers
|
2020-07-09 22:27:23 +00:00
|
|
|
},
|
|
|
|
PortBindings: portmaps,
|
|
|
|
AutoRemove: false,
|
|
|
|
RestartPolicy: container.RestartPolicy{
|
2020-07-11 11:04:37 +00:00
|
|
|
Name: "on-failure",
|
2020-07-09 22:27:23 +00:00
|
|
|
MaximumRetryCount: 3,
|
|
|
|
},
|
|
|
|
Binds: []string{
|
|
|
|
volumePath + ":/srv",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&network.NetworkingConfig{},
|
|
|
|
name,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
containerID := createdContainer.ID
|
|
|
|
|
|
|
|
// I dunno if we want this
|
|
|
|
// err = cli.ContainerStart(context.TODO(), createdContainer.ID, types.ContainerStartOptions{})
|
|
|
|
|
|
|
|
return containerID, nil
|
|
|
|
}
|