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, &current)
    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  }