github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/tools/docker/docker.go (about)

     1  // Packager docker provides common utilities for managing containerized AIS deployments
     2  /*
     3   * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package docker
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"os/exec"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/NVIDIA/aistore/cmn/cos"
    16  	"github.com/NVIDIA/aistore/cmn/nlog"
    17  )
    18  
    19  // naming
    20  const (
    21  	prefixStr = "ais"
    22  	proxyStr  = "_proxy_"
    23  	targetStr = "_target_"
    24  	pattern   = "^\"" + prefixStr + "\\d+" + proxyStr + "[1-9]+\"$" // Checking '_proxy_' should suffice
    25  )
    26  
    27  var dockerRunning = false
    28  
    29  // Detect docker cluster at startup
    30  func init() {
    31  	cmd := exec.Command("docker", "ps", "--format", "\"{{.Names}}\"")
    32  	bytes, err := cmd.Output()
    33  	if err != nil {
    34  		return
    35  	}
    36  
    37  	r := regexp.MustCompile(pattern)
    38  	lines := strings.Split(string(bytes), "\n")
    39  	// Checks to see if there is any container P_proxy_{jj}" running
    40  	for _, line := range lines {
    41  		match := r.MatchString(line)
    42  		if match {
    43  			dockerRunning = true
    44  			return
    45  		}
    46  	}
    47  }
    48  
    49  // DockerRunning returns true if docker-based AIStore cluster is detected
    50  func IsRunning() bool {
    51  	return dockerRunning
    52  }
    53  
    54  func clustersMap() (map[int]int, error) {
    55  	const (
    56  		a, b, c, d = "docker", "ps", "--format", "\"{{.Names}}\""
    57  	)
    58  	cmd := exec.Command(a, b, c, d)
    59  	bytes, err := cmd.Output()
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	clusterRegex := regexp.MustCompile(`ais(\d+)_(target|proxy)_\d+`)
    65  
    66  	m := make(map[int]int)
    67  	lines := strings.Split(string(bytes), "\n")
    68  	for _, line := range lines {
    69  		if !clusterRegex.MatchString(line) {
    70  			continue
    71  		}
    72  
    73  		matches := clusterRegex.FindStringSubmatch(line)
    74  		// first match the whole string, second number after ais prefix
    75  		// matched because of \d+ being inside parenthesis
    76  		cos.Assert(len(matches) > 1)
    77  		i, err := strconv.ParseInt(matches[1], 10, 32)
    78  		cos.AssertNoErr(err)
    79  		m[int(i)]++
    80  	}
    81  
    82  	return m, nil
    83  }
    84  
    85  // ClusterIDs returns all IDs of running ais docker clusters
    86  func ClusterIDs() ([]int, error) {
    87  	m, err := clustersMap()
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	ids := make([]int, 0, len(m))
    93  	for id := range m {
    94  		ids = append(ids, id)
    95  	}
    96  	return ids, nil
    97  }
    98  
    99  // TargetsInCluster returns the names of the targets in cluster i
   100  func TargetsInCluster(i int) (ans []string) {
   101  	return nodesInCluster(i, targetStr)
   102  }
   103  
   104  // ProxiesInCluster returns the names of the targets in cluster i
   105  func ProxiesInCluster(i int) (ans []string) {
   106  	return nodesInCluster(i, proxyStr)
   107  }
   108  
   109  func nodesInCluster(i int, prefix string) (ans []string) {
   110  	cmd := exec.Command("docker", "ps", "--format", "\"{{.Names}}\"")
   111  	bytes, err := cmd.Output()
   112  	if err != nil {
   113  		return
   114  	}
   115  	targetPrefix := prefixStr + strconv.Itoa(i) + prefix
   116  	lines := strings.Split(string(bytes), "\n")
   117  	for _, line := range lines {
   118  		if strings.Contains(line, targetPrefix) {
   119  			ans = append(ans, strings.Trim(line, "\""))
   120  		}
   121  	}
   122  	return
   123  }
   124  
   125  // CreateMpathDir creates a directory that will be used as a mountpath for each target in cluster c
   126  func CreateMpathDir(c int, mpathFQN string) (err error) {
   127  	targetNames := TargetsInCluster(c)
   128  	for _, target := range targetNames {
   129  		err = _exec(target, "mkdir", "-p", mpathFQN)
   130  		if err != nil {
   131  			return err
   132  		}
   133  	}
   134  	return nil
   135  }
   136  
   137  // RemoveMpathDir removes a directory named mpathFQN for each target in cluster c
   138  func RemoveMpathDir(c int, mpathFQN string) (err error) {
   139  	targetNames := TargetsInCluster(c)
   140  	for _, target := range targetNames {
   141  		err = _exec(target, "rm", "-rf", mpathFQN)
   142  		if err != nil {
   143  			return err
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  func _exec(containerName string, args ...string) error {
   150  	if len(args) == 0 {
   151  		return errors.New("not enough arguments to execute a command")
   152  	}
   153  	temp := append([]string{"docker", "exec", containerName}, args...)
   154  	cmd := exec.Command(temp[0], temp[1:]...) //nolint:gosec // used only in tests
   155  
   156  	_, err := cmd.Output()
   157  	if err != nil {
   158  		nlog.Infof("%q error executing docker command: docker exec %s %v.\n", err.Error(), containerName, args)
   159  		return err
   160  	}
   161  	return nil
   162  }
   163  
   164  // Stop simulates killing a target or proxy (by given container id)
   165  func Stop(cid string) error {
   166  	cmd := exec.Command("docker", "stop", cid)
   167  	return cmd.Run()
   168  }
   169  
   170  // Restart restores previously killed target or proxy with given container id
   171  func Restart(cid string) error {
   172  	cmd := exec.Command("docker", "restart", cid)
   173  	return cmd.Run()
   174  }
   175  
   176  // Disconnect disconnects specific containerID from all networks.
   177  // Returns networks from which the container has been disconnected.
   178  func Disconnect(containerID string) ([]string, error) {
   179  	networks, err := containerNetworkList(containerID)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	for _, network := range networks {
   185  		cmd := exec.Command("docker", "network", "disconnect", "-f", network, containerID)
   186  		if err := cmd.Run(); err != nil {
   187  			return nil, err
   188  		}
   189  	}
   190  
   191  	return networks, nil
   192  }
   193  
   194  // Connect connects specific containerID to all provided networks.
   195  func Connect(containerID string, networks []string) error {
   196  	for _, network := range networks {
   197  		cmd := exec.Command("docker", "network", "connect", network, containerID)
   198  		if err := cmd.Run(); err != nil {
   199  			return err
   200  		}
   201  	}
   202  
   203  	return nil
   204  }
   205  
   206  func ClusterEndpoint(i int) (string, error) {
   207  	proxies := ProxiesInCluster(i)
   208  	if len(proxies) == 0 {
   209  		return "", fmt.Errorf("couldn't find any proxies in cluster %d", i)
   210  	}
   211  	var (
   212  		containerID      = proxies[0]
   213  		aisPublicNetwork = prefixStr + strconv.Itoa(i) + "_public"
   214  		format           = "--format={{.NetworkSettings.Networks." + aisPublicNetwork + ".IPAddress }}"
   215  
   216  		cmd = exec.Command("docker", "inspect", format, containerID)
   217  	)
   218  	bytes, err := cmd.Output()
   219  	if err != nil {
   220  		return "", err
   221  	}
   222  
   223  	output := strings.TrimSpace(string(bytes))
   224  	return output, nil
   225  }
   226  
   227  // containerNetworkList returns all names of networks to which given container
   228  // is connected.
   229  func containerNetworkList(containerID string) ([]string, error) {
   230  	cmd := exec.Command("docker", "inspect", "--format={{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}", containerID)
   231  	bytes, err := cmd.Output()
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	output := strings.TrimSpace(string(bytes))
   237  	return strings.Split(output, " "), nil
   238  }