github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/internal/operator/nodeagent/networking/centos/centOS.go (about) 1 package centos 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "strings" 11 "text/template" 12 13 "github.com/caos/orbos/internal/operator/common" 14 "github.com/caos/orbos/internal/operator/nodeagent" 15 "github.com/caos/orbos/mntr" 16 ) 17 18 const ( 19 prefix string = "orbos" 20 ) 21 22 func Ensurer(monitor mntr.Monitor) nodeagent.NetworkingEnsurer { 23 return nodeagent.NetworkingEnsurerFunc(func(desired common.Networking) (common.NetworkingCurrent, func() error, error) { 24 current := make(common.NetworkingCurrent, 0) 25 ensurers := make([]func() error, 0) 26 27 ensurer, err := ensureInterfaces(monitor, &desired, ¤t) 28 if err != nil { 29 return current, ensurer, err 30 } 31 if ensurer != nil { 32 ensurers = append(ensurers, ensurer) 33 } 34 35 if ensurers == nil || len(ensurers) == 0 { 36 monitor.Debug("Not changing networking") 37 return current, nil, nil 38 } 39 40 return current, func() error { 41 monitor.Debug("Ensuring networking") 42 for _, ensurer := range ensurers { 43 if err := ensurer(); err != nil { 44 return err 45 } 46 } 47 return nil 48 }, nil 49 }) 50 } 51 52 func ensureInterfaces( 53 54 monitor mntr.Monitor, 55 desired *common.Networking, 56 current *common.NetworkingCurrent, 57 ) ( 58 func() error, 59 error, 60 ) { 61 ensurers := make([]func() error, 0) 62 changes := []string{} 63 64 if desired.Interfaces == nil { 65 desired.Interfaces = make(map[string]*common.NetworkingInterface, 0) 66 } 67 68 interfaces, err := queryExisting() 69 if err != nil { 70 return nil, err 71 } 72 73 addLoop: 74 for ifaceName := range desired.Interfaces { 75 iface := desired.Interfaces[ifaceName] 76 if iface == nil { 77 return nil, errors.New("void interface") 78 } 79 //ensure ips for every desired interface 80 ifaceNameWithPrefix := prefix + ifaceName 81 ensureFunc, err := ensureInterface(monitor, ifaceNameWithPrefix, iface) 82 if err != nil { 83 return nil, err 84 } 85 86 if ensureFunc != nil { 87 ensurers = append(ensurers, ensureFunc) 88 } 89 90 for _, alreadyIface := range interfaces { 91 if alreadyIface == ifaceName { 92 continue addLoop 93 } 94 } 95 96 changes = append(changes, fmt.Sprintf("link add %s type %s", ifaceNameWithPrefix, iface.Type)) 97 } 98 99 deleteLoop: 100 for _, ifaceName := range interfaces { 101 if ifaceName == "" { 102 continue 103 } 104 ifaceNameWithPrefix := prefix + ifaceName 105 ipsByte, err := queryExistingInterface(ifaceNameWithPrefix) 106 if err != nil { 107 return nil, err 108 } 109 actualIps := bytes.Split(ipsByte, []byte("\n")) 110 ips := make(common.MarshallableSlice, 0) 111 for _, actualIp := range actualIps { 112 if string(actualIp) != "" { 113 ips = append(ips, string(actualIp)) 114 } 115 } 116 117 *current = append(*current, &common.NetworkingInterfaceCurrent{ 118 Name: ifaceName, 119 IPs: ips, 120 }) 121 122 for desiredIfaceName := range desired.Interfaces { 123 if strings.TrimPrefix(ifaceName, prefix) == desiredIfaceName { 124 continue deleteLoop 125 } 126 } 127 128 for filename, _ := range getNetworkFiles(ifaceNameWithPrefix, "", []string{}) { 129 if err := os.RemoveAll(filename); err != nil && err != os.ErrNotExist { 130 return nil, err 131 } 132 } 133 changes = append(changes, fmt.Sprintf("link delete %s", ifaceNameWithPrefix)) 134 } 135 136 if (changes == nil || len(changes) == 0) && 137 (ensurers == nil || len(ensurers) == 0) { 138 return nil, nil 139 } 140 141 current.Sort() 142 return func() error { 143 monitor.Debug(fmt.Sprintf("Ensuring part of networking")) 144 if changes != nil && len(changes) != 0 { 145 if err := ensureIP(monitor, changes); err != nil { 146 return err 147 } 148 } 149 150 if ensurers != nil { 151 for _, ensureFunc := range ensurers { 152 if err := ensureFunc(); err != nil { 153 return err 154 } 155 } 156 } 157 return nil 158 }, nil 159 } 160 161 func ensureInterface( 162 163 monitor mntr.Monitor, 164 name string, 165 desired *common.NetworkingInterface, 166 ) ( 167 func() error, 168 error, 169 ) { 170 171 changes := []string{} 172 173 fullInterface, err := queryExistingInterface(name) 174 addedVIPs := make([][]byte, 0) 175 if err == nil { 176 addedVIPs = bytes.Split(fullInterface, []byte("\n")) 177 } else if fullInterface != nil && len(fullInterface) == 0 { 178 return nil, err 179 } 180 181 addLoop: 182 for idx := range desired.IPs { 183 ip := desired.IPs[idx] 184 if ip == "" { 185 return nil, errors.New("void ip") 186 } 187 for idx := range addedVIPs { 188 already := addedVIPs[idx] 189 if string(already) == ip { 190 continue addLoop 191 } 192 } 193 if !bytes.Contains(fullInterface, []byte(ip)) { 194 changes = append(changes, fmt.Sprintf("addr add %s/32 dev %s", ip, name)) 195 } 196 } 197 198 deleteLoop: 199 for idx := range addedVIPs { 200 added := string(addedVIPs[idx]) 201 if added == "" { 202 continue 203 } 204 205 for idx := range desired.IPs { 206 ip := desired.IPs[idx] 207 if added == ip { 208 continue deleteLoop 209 } 210 } 211 changes = append(changes, fmt.Sprintf("addr delete %s/32 dev %s", added, name)) 212 } 213 214 if changes == nil || len(changes) == 0 { 215 return nil, nil 216 } 217 218 return func() error { 219 monitor.Debug(fmt.Sprintf("Ensuring part of networking with interface %s", name)) 220 if changes != nil && len(changes) != 0 { 221 if err := ensureIP(monitor, changes); err != nil { 222 return err 223 } 224 225 for filename, content := range getNetworkFiles(name, desired.Type, desired.IPs) { 226 if err := ioutil.WriteFile(filename, []byte(content), os.ModePerm); err != nil { 227 return err 228 } 229 } 230 } 231 return nil 232 }, nil 233 } 234 235 func queryExisting() ([]string, error) { 236 cmd := exec.Command("/bin/sh", "-c", `ip link show | awk 'NR % 2 == 1'`) 237 238 output, err := cmd.CombinedOutput() 239 if err != nil { 240 return nil, err 241 } 242 243 interfaceNames := []string{} 244 interfaces := strings.Split(string(output), "\n") 245 for _, iface := range interfaces { 246 if iface == "" { 247 continue 248 } 249 250 parts := strings.Split(iface, ":") 251 if len(parts) > 1 { 252 name := strings.TrimSpace(parts[1]) 253 if strings.HasPrefix(name, prefix) { 254 interfaceNames = append(interfaceNames, strings.TrimPrefix(name, prefix)) 255 } 256 } 257 } 258 return interfaceNames, nil 259 } 260 261 func queryExistingInterface(interfaceName string) ([]byte, error) { 262 cmdStr := fmt.Sprintf(`set -o pipefail && ip address show %s | grep %s | tail -n +2 | awk '{print $2}' | cut -d "/" -f 1`, interfaceName, interfaceName) 263 264 cmd := exec.Command("/bin/sh", "-c", cmdStr) 265 return cmd.CombinedOutput() 266 } 267 268 func ensureIP(monitor mntr.Monitor, changes []string) (err error) { 269 defer func() { 270 if err == nil { 271 monitor.Debug("networking changed") 272 } else { 273 monitor.Error(err) 274 } 275 }() 276 cmdStr := "true" 277 for _, change := range changes { 278 cmdStr += fmt.Sprintf(" && sudo ip %s", change) 279 } 280 281 errBuf := new(bytes.Buffer) 282 defer errBuf.Reset() 283 if len(changes) == 0 { 284 return nil 285 } 286 287 errBuf.Reset() 288 cmd := exec.Command("/bin/bash", "-c", cmdStr) 289 cmd.Stderr = errBuf 290 291 if monitor.IsVerbose() { 292 fmt.Println(cmdStr) 293 cmd.Stdout = os.Stdout 294 } 295 296 err = cmd.Run() 297 if err != nil { 298 err = fmt.Errorf("running %s failed with stderr %s: %w", cmdStr, errBuf.String(), err) 299 } 300 301 return err 302 } 303 304 func getNetworkScriptPath(interfaceName string) string { 305 return "/etc/sysconfig/network-scripts/ifcfg-" + interfaceName 306 } 307 308 func getNetworkFiles(name string, ty string, ips []string) map[string]string { 309 tmpBuf := new(bytes.Buffer) 310 defer tmpBuf.Reset() 311 tmpl := template.Must(template.New("").Parse(`NAME={{ .Name }} 312 DEVICE={{ .Name }} 313 ONBOOT=yes 314 TYPE=Ethernet 315 NM_CONTROLLED=no{{ range $ip := .IPs }} 316 IPADDR={{ $ip }}{{ end }}`)) 317 if err := tmpl.Execute(tmpBuf, 318 struct { 319 IPs []string 320 Name string 321 }{ 322 IPs: ips, 323 Name: name, 324 }, 325 ); err != nil { 326 return map[string]string{} 327 } 328 329 return map[string]string{ 330 getNetworkScriptPath(name): tmpBuf.String(), 331 "/etc/modules-load.d/" + name + ".conf": name, 332 "/etc/modprobe.d/" + name + ".conf": "install " + name + " /sbin/modprobe --ignore-install " + name + "; /sbin/ip link add " + name + " type " + ty, 333 } 334 }