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 }