github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/internal/operator/orbiter/kinds/loadbalancers/dynamic/adapt.go (about)

     1  package dynamic
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  	"text/template"
    10  
    11  	"github.com/caos/orbos/internal/helpers"
    12  	"github.com/caos/orbos/internal/operator/common"
    13  	"github.com/caos/orbos/internal/operator/nodeagent/dep/sysctl"
    14  	"github.com/caos/orbos/internal/operator/orbiter"
    15  	"github.com/caos/orbos/internal/operator/orbiter/kinds/clusters/core/infra"
    16  	"github.com/caos/orbos/internal/operator/orbiter/kinds/providers/core"
    17  	"github.com/caos/orbos/mntr"
    18  	"github.com/caos/orbos/pkg/secret"
    19  	"github.com/caos/orbos/pkg/tree"
    20  
    21  	"github.com/prometheus/client_golang/prometheus"
    22  )
    23  
    24  const (
    25  	nginxVersion      = "v1.18.0"
    26  	keepalivedVersion = "v1.3.5"
    27  )
    28  
    29  var probes = prometheus.NewGaugeVec(
    30  	prometheus.GaugeOpts{
    31  		Name: "probe",
    32  		Help: "Load Balancing Probes.",
    33  	},
    34  	[]string{"name", "type", "target"},
    35  )
    36  
    37  func init() {
    38  	prometheus.MustRegister(probes)
    39  }
    40  
    41  type WhiteListFunc func() []*orbiter.CIDR
    42  
    43  type VRRP struct {
    44  	VRRPInterface string
    45  	VIPInterface  string
    46  	NotifyMaster  func(machine infra.Machine) (string, bool)
    47  	AuthCheck     func(machine infra.Machine) (string, int)
    48  }
    49  
    50  func AdaptFunc(whitelist WhiteListFunc) orbiter.AdaptFunc {
    51  	return func(monitor mntr.Monitor, finishedChan chan struct{}, desiredTree *tree.Tree, currentTree *tree.Tree) (queryFunc orbiter.QueryFunc, destroyFunc orbiter.DestroyFunc, configureFunc orbiter.ConfigureFunc, migrate bool, secrets map[string]*secret.Secret, err error) {
    52  
    53  		defer func() {
    54  			if err != nil {
    55  				err = fmt.Errorf("building %s failed: %w", desiredTree.Common.Kind, err)
    56  			}
    57  		}()
    58  		if desiredTree.Common.Version() != "v2" {
    59  			migrate = true
    60  		}
    61  		desiredKind := &Desired{Common: desiredTree.Common}
    62  		if err := desiredTree.Original.Decode(desiredKind); err != nil {
    63  			return nil, nil, nil, migrate, nil, fmt.Errorf("unmarshaling desired state for kind %s failed: %w", desiredTree.Common.Kind, err)
    64  		}
    65  
    66  		for _, pool := range desiredKind.Spec {
    67  			for _, vip := range pool {
    68  				for _, t := range vip.Transport {
    69  					sort.Strings(t.BackendPools)
    70  					if t.ProxyProtocol == nil {
    71  						trueVal := true
    72  						t.ProxyProtocol = &trueVal
    73  						migrate = true
    74  					}
    75  					if t.Name == "kubeapi" {
    76  						if t.HealthChecks.Path != "/healthz" {
    77  							t.HealthChecks.Path = "/healthz"
    78  							migrate = true
    79  						}
    80  						if t.HealthChecks.Protocol != "https" {
    81  							t.HealthChecks.Protocol = "https"
    82  							migrate = true
    83  						}
    84  						if t.ProxyProtocol == nil || *t.ProxyProtocol {
    85  							f := false
    86  							t.ProxyProtocol = &f
    87  							migrate = true
    88  						}
    89  					}
    90  					if len(t.Whitelist) == 0 {
    91  						allIPs := orbiter.CIDR("0.0.0.0/0")
    92  						t.Whitelist = []*orbiter.CIDR{&allIPs}
    93  						migrate = true
    94  					}
    95  				}
    96  			}
    97  		}
    98  
    99  		if err := desiredKind.Validate(); err != nil {
   100  			return nil, nil, nil, migrate, nil, err
   101  		}
   102  		desiredTree.Parsed = desiredKind
   103  
   104  		current := &Current{
   105  			Common: tree.NewCommon("orbiter.caos.ch/DynamicLoadBalancer", "v0", false),
   106  		}
   107  		currentTree.Parsed = current
   108  
   109  		return func(nodeAgentsCurrent *common.CurrentNodeAgents, nodeagents *common.DesiredNodeAgents, queried map[string]interface{}) (orbiter.EnsureFunc, error) {
   110  
   111  			wl := whitelist()
   112  
   113  			addresses := make(map[string]*infra.Address)
   114  			for _, pool := range desiredKind.Spec {
   115  				for _, vip := range pool {
   116  					for _, t := range vip.Transport {
   117  						addresses[t.Name] = &infra.Address{
   118  							Location:     vip.IP,
   119  							FrontendPort: uint16(t.FrontendPort),
   120  							BackendPort:  uint16(t.BackendPort),
   121  						}
   122  					}
   123  				}
   124  			}
   125  
   126  			poolMachines := curryPoolMachines()
   127  			enrichedVIPs := curryEnrichedVIPs(*desiredKind, poolMachines, wl, nodeAgentsCurrent)
   128  
   129  			current.Current.Spec = enrichedVIPs
   130  			current.Current.Desire = func(forPool string, svc core.MachinesService, vrrp *VRRP, mapVIP func(*VIP) string) (bool, error) {
   131  				var lbMachines infra.Machines
   132  
   133  				done := true
   134  				desireNodeAgent := func(machine infra.Machine, fw common.Firewall, nginx, keepalived common.Package) {
   135  					machineMonitor := monitor.WithField("machine", machine.ID())
   136  					deepNa, _ := nodeagents.Get(machine.ID())
   137  					deepNaCurr, _ := nodeAgentsCurrent.Get(machine.ID())
   138  
   139  					if !deepNa.Firewall.Contains(fw) {
   140  						machineMonitor.WithField("open", fw.ToCurrent()).Debug("Loadbalancing firewall desired")
   141  					}
   142  					deepNa.Firewall.Merge(fw)
   143  					if !fw.IsContainedIn(deepNaCurr.Open) {
   144  						machineMonitor.WithField("ports", deepNa.Firewall.ToCurrent()).Info("Awaiting firewalld config")
   145  						done = false
   146  					}
   147  					for _, port := range fw.Ports("external") {
   148  						if portInt, parseErr := strconv.ParseInt(port.Port, 10, 16); parseErr == nil && portInt == 22 {
   149  
   150  							if deepNa.Software.SSHD.Config == nil || deepNa.Software.SSHD.Config["listenaddress"] != machine.IP() {
   151  								deepNa.Software.SSHD.Config = map[string]string{"listenaddress": machine.IP()}
   152  								machineMonitor.Changed("sshd config desired")
   153  							}
   154  
   155  							if !deepNaCurr.Software.SSHD.Equals(deepNa.Software.SSHD) {
   156  								machineMonitor.Info("Awaiting sshd config")
   157  								done = false
   158  							}
   159  						}
   160  					}
   161  					if deepNa.Software == nil {
   162  						deepNa.Software = &common.Software{}
   163  					}
   164  
   165  					if !nginx.Equals(common.Package{}) {
   166  						if !deepNa.Software.Nginx.Equals(nginx) {
   167  							machineMonitor.WithField("pkg", nginx).Debug("NGINX desired")
   168  						}
   169  						deepNa.Software.Nginx = nginx
   170  						if !deepNa.Software.Nginx.Equals(deepNaCurr.Software.Nginx) {
   171  							machineMonitor.Info("Awaiting NGINX")
   172  							done = false
   173  						}
   174  						if !sysctl.Contains(deepNa.Software.Sysctl, common.Package{
   175  							Config: map[string]string{
   176  								string(common.IpForward):    "1",
   177  								string(common.NonLocalBind): "1",
   178  							},
   179  						}) {
   180  							machineMonitor.Changed("sysctl desired")
   181  						}
   182  						sysctl.Enable(&deepNa.Software.Sysctl, common.IpForward)
   183  						sysctl.Enable(&deepNa.Software.Sysctl, common.NonLocalBind)
   184  						if !sysctl.Contains(deepNaCurr.Software.Sysctl, deepNa.Software.Sysctl) {
   185  							machineMonitor.Info("Awaiting sysctl config")
   186  							done = false
   187  						}
   188  					}
   189  
   190  					if !keepalived.Equals(common.Package{}) {
   191  						if !deepNa.Software.KeepaliveD.Equals(keepalived) {
   192  							machineMonitor.WithField("pkg", keepalived).Debug("Keepalived desired")
   193  						}
   194  						deepNa.Software.KeepaliveD = keepalived
   195  						if !deepNa.Software.KeepaliveD.Equals(deepNaCurr.Software.KeepaliveD) {
   196  							monitor.Info("Awaiting keepalived")
   197  							done = false
   198  						}
   199  					}
   200  				}
   201  
   202  				templateFuncs := template.FuncMap(map[string]interface{}{
   203  					"forMachines": func(poolName string) (infra.Machines, error) {
   204  						machines, err := svc.List(poolName)
   205  						if err != nil {
   206  							return nil, err
   207  						}
   208  						sort.Sort(machines)
   209  						return machines, nil
   210  					},
   211  					"add": func(i, y int) int { return i + y },
   212  					"user": func(machine infra.Machine) (string, error) {
   213  						var user string
   214  						whoami := "whoami"
   215  						stdout, err := machine.Execute(nil, whoami)
   216  						if err != nil {
   217  							return "", fmt.Errorf("running command %s remotely failed", whoami)
   218  						}
   219  						user = strings.TrimSuffix(string(stdout), "\n")
   220  						monitor.WithFields(map[string]interface{}{
   221  							"user":    user,
   222  							"machine": machine.ID(),
   223  							"command": whoami,
   224  						}).Debug("Executed command")
   225  						return user, nil
   226  					},
   227  					"vip": mapVIP,
   228  					"routerID": func(vip *VIP) string {
   229  						vipParts := strings.Split(mapVIP(vip), ".")
   230  						if len(vipParts) != 4 || vipParts[3] == "0" {
   231  							return "55"
   232  						}
   233  						return vipParts[3]
   234  					},
   235  					"derefBool": func(in *bool) bool { return in != nil && *in },
   236  				})
   237  
   238  				var nginxNATTemplate *template.Template
   239  				var vips []*VIP
   240  
   241  				if vrrp == nil {
   242  					for _, desiredVIPs := range desiredKind.Spec {
   243  						vips = append(vips, desiredVIPs...)
   244  					}
   245  					nginxNATTemplate = template.Must(template.New("").Funcs(templateFuncs).Parse(`worker_rlimit_nofile 8192;
   246  
   247  events {
   248  	worker_connections  4096;  ## Default: 1024
   249  }
   250  stream { {{ range $nat := .NATs }}
   251  	upstream {{ $nat.Name }} {
   252  		server {{ $nat.To }};
   253  	}
   254  
   255  {{ range $from := $nat.From }}	server {
   256  		listen {{ $from }};
   257  
   258  {{ range $white := $nat.Whitelist }}		allow {{ $white }};
   259  {{ end }}
   260  		deny all;
   261  		proxy_pass {{ $nat.Name }};
   262  		proxy_protocol {{ if derefBool $nat.ProxyProtocol }}on{{ else }}off{{ end }};
   263  	}
   264  {{ end }}{{ end }}}`))
   265  
   266  				} else {
   267  
   268  					lbMachines = nil
   269  					if err := poolMachines(svc, func(pool string, machines infra.Machines) {
   270  						if forPool == pool {
   271  							lbMachines = machines
   272  						}
   273  					}); err != nil {
   274  						return false, err
   275  					}
   276  
   277  					sort.Sort(lbMachines)
   278  
   279  					spec, _, err := enrichedVIPs(svc)
   280  					if err != nil {
   281  						return false, err
   282  					}
   283  
   284  					lbData := make([]LB, len(lbMachines))
   285  					for idx, machine := range lbMachines {
   286  						lbData[idx] = LB{
   287  							VIPs: spec[forPool],
   288  							Self: machine,
   289  							Peers: deriveFilterMachines(func(cmp infra.Machine) bool {
   290  								return cmp.ID() != machine.ID()
   291  							}, append([]infra.Machine(nil), lbMachines...)),
   292  							State:                "BACKUP",
   293  							CustomMasterNotifyer: vrrp.NotifyMaster != nil,
   294  							VRRPInterface:        vrrp.VRRPInterface,
   295  							VIPInterface:         vrrp.VIPInterface,
   296  						}
   297  						if idx == 0 {
   298  							lbData[idx].State = "MASTER"
   299  						}
   300  					}
   301  
   302  					keepaliveDTemplate := template.Must(template.New("").Funcs(templateFuncs).Parse(`{{ $root := . }}global_defs {
   303  	enable_script_security
   304  	script_user {{ user $root.Self }}
   305  }
   306  
   307  vrrp_sync_group VG1 {
   308  	group {
   309  {{ range $idx, $_ := .VIPs }}        VI_{{ $idx }}
   310  {{ end }}    }
   311  }
   312  
   313  {{ range $idx, $vip := .VIPs }}vrrp_script chk_{{ vip $vip }} {
   314  	script       "/usr/local/bin/health --protocol http --ip 127.0.0.1 --port 29999 --path /ready --status 200"
   315  	interval 2   # check every 2 seconds
   316  	fall 5       # require 5 failures for KO
   317  	rise 5       # require 5 successes for OK
   318  	timeout 5    # time out after 5 seconds
   319  }
   320  
   321  vrrp_instance VI_{{ $idx }} {
   322  	state {{ $root.State }}
   323  	unicast_src_ip {{ $root.Self.IP }}
   324  	unicast_peer {
   325  		{{ range $peer := $root.Peers }}{{ $peer.IP }}
   326  		{{ end }}    }
   327  	interface {{ $root.VRRPInterface }}
   328  	virtual_router_id {{ routerID $vip }}
   329  	advert_int 1
   330  	authentication {
   331  		auth_type PASS
   332  		auth_pass [ REDACTED ]
   333  	}
   334  	track_script {
   335  		chk_{{ vip $vip }}
   336  	}
   337  
   338  	virtual_ipaddress {
   339  		{{ vip $vip }} dev {{ $root.VIPInterface }}
   340  	}
   341  
   342  {{ if $root.CustomMasterNotifyer }}	notify_master "/etc/keepalived/notifymaster.sh"
   343  {{ else }}	virtual_ipaddress {
   344  		{{ vip $vip }}
   345  	}
   346  {{ end }}
   347  }
   348  {{ end }}`))
   349  
   350  					nginxLBTemplate := template.Must(template.New("").Funcs(templateFuncs).Parse(`{{ $root := . }}worker_rlimit_nofile 8192;
   351  
   352  events {
   353  	worker_connections  4096;  ## Default: 1024
   354  }
   355  
   356  stream { {{ range $vip := .VIPs }}{{ range $src := $vip.Transport }}
   357  	upstream {{ $src.Name }} {    {{ range $dest := $src.BackendPools }}{{ range $machine := forMachines $dest }}
   358  		server {{ $machine.IP }}:{{ $src.BackendPort }}; # {{ $dest }}{{end}}{{ end }}
   359  	}
   360  	server {
   361  		listen {{ vip $vip }}:{{ $src.FrontendPort }};
   362  {{ range $white := $src.Whitelist }}		allow {{ $white }};
   363  {{ end }}
   364  		deny all;
   365  		proxy_pass {{ $src.Name }};
   366  		proxy_protocol {{ if derefBool $src.ProxyProtocol }}on{{ else }}off{{ end }};
   367  	}
   368  {{ end }}{{ end }}}
   369  
   370  http {
   371  	server {
   372  		listen 29999;
   373  
   374  		location /ready {
   375  			return 200;
   376  		}
   377  	}
   378  }`))
   379  
   380  					for _, d := range lbData {
   381  
   382  						if len(d.VIPs) == 0 {
   383  							continue
   384  						}
   385  
   386  						ngxBuf := new(bytes.Buffer)
   387  						//noinspection GoDeferInLoop
   388  						defer ngxBuf.Reset()
   389  						kaBuf := new(bytes.Buffer)
   390  						defer kaBuf.Reset()
   391  
   392  						if err := keepaliveDTemplate.Execute(kaBuf, d); err != nil {
   393  							return false, err
   394  						}
   395  
   396  						kaPkg := common.Package{Version: keepalivedVersion, Config: map[string]string{"keepalived.conf": kaBuf.String()}}
   397  						kaBuf.Reset()
   398  
   399  						if d.CustomMasterNotifyer {
   400  							var enforceEnsuring bool
   401  							kaPkg.Config["notifymaster.sh"], enforceEnsuring = vrrp.NotifyMaster(d.Self)
   402  							if enforceEnsuring {
   403  								kaPkg.Config["reensure"] = "true"
   404  							}
   405  						}
   406  
   407  						if vrrp.AuthCheck != nil {
   408  							authCheck, expectedExitCode := vrrp.AuthCheck(d.Self)
   409  							if authCheck != "" {
   410  								kaPkg.Config["authcheck.sh"] = authCheck
   411  								kaPkg.Config["authcheckexitcode"] = strconv.Itoa(expectedExitCode)
   412  							}
   413  						}
   414  
   415  						if err := nginxLBTemplate.Execute(ngxBuf, d); err != nil {
   416  							return false, err
   417  						}
   418  						ngxPkg := desireNginx(ngxBuf.String())
   419  						ngxBuf.Reset()
   420  
   421  						desireNodeAgent(d.Self, common.ToFirewall("external", make(map[string]*common.Allowed)), ngxPkg, kaPkg)
   422  					}
   423  				}
   424  
   425  				nodesNats := make(map[string]*NATDesires)
   426  				spec, _, err := enrichedVIPs(svc)
   427  				if err != nil {
   428  					return false, err
   429  				}
   430  				for srcPool, vips := range spec {
   431  					for _, vip := range vips {
   432  						for _, transport := range vip.Transport {
   433  							srcFW := map[string]*common.Allowed{
   434  								fmt.Sprintf("%s-%d-src", transport.Name, transport.FrontendPort): {
   435  									Port:     fmt.Sprintf("%d", transport.FrontendPort),
   436  									Protocol: "tcp",
   437  								},
   438  							}
   439  							ip := mapVIP(vip)
   440  							var vipProbed bool
   441  							probeVIP := func() {
   442  								if vipProbed {
   443  									return
   444  								}
   445  								probe("VIP", ip, uint16(transport.FrontendPort), false, transport.HealthChecks, *transport)
   446  								vipProbed = true
   447  							}
   448  
   449  							if vrrp != nil && forPool == srcPool {
   450  								for _, machine := range lbMachines {
   451  									desireNodeAgent(machine, common.ToFirewall("external", srcFW), common.Package{}, common.Package{})
   452  								}
   453  								probeVIP()
   454  							}
   455  							for _, dest := range transport.BackendPools {
   456  
   457  								destFW := map[string]*common.Allowed{
   458  									fmt.Sprintf("%s-%d-dest", transport.Name, transport.BackendPort): {
   459  										Port:     fmt.Sprintf("%d", transport.BackendPort),
   460  										Protocol: "tcp",
   461  									},
   462  								}
   463  
   464  								destMachines, err := svc.List(dest)
   465  								if err != nil {
   466  									return false, err
   467  								}
   468  
   469  								for idx := range destMachines {
   470  									machine := destMachines[idx]
   471  									desireNodeAgent(machine, common.ToFirewall("internal", destFW), common.Package{}, common.Package{})
   472  									probe("Upstream", machine.IP(), uint16(transport.BackendPort), *transport.ProxyProtocol, transport.HealthChecks, *transport)
   473  									if vrrp != nil || forPool != dest {
   474  										continue
   475  									}
   476  									probeVIP()
   477  
   478  									nodeNatDesires, ok := nodesNats[machine.IP()]
   479  									if !ok {
   480  										nodeNatDesires = &NATDesires{NATs: make([]*NAT, 0)}
   481  									}
   482  									nodeNatDesires.Firewall.Merge(common.ToFirewall("external", srcFW))
   483  									nodeNatDesires.Machine = machine
   484  
   485  									nodeNatDesires.NATs = append(nodeNatDesires.NATs, &NAT{
   486  										Whitelist: transport.Whitelist,
   487  										Name:      transport.Name,
   488  										From: []string{
   489  											fmt.Sprintf("%s:%d", ip, transport.FrontendPort),           // VIP
   490  											fmt.Sprintf("%s:%d", machine.IP(), transport.FrontendPort), // Node IP
   491  										},
   492  										To:            fmt.Sprintf("%s:%d", machine.IP(), transport.BackendPort),
   493  										ProxyProtocol: *transport.ProxyProtocol,
   494  									})
   495  									nodesNats[machine.IP()] = nodeNatDesires
   496  								}
   497  							}
   498  						}
   499  					}
   500  				}
   501  
   502  				for _, node := range nodesNats {
   503  					ngxBuf := new(bytes.Buffer)
   504  					//noinspection GoDeferInLoop
   505  					defer ngxBuf.Reset()
   506  					if err := nginxNATTemplate.Execute(ngxBuf, struct {
   507  						NATs []*NAT
   508  					}{
   509  						NATs: node.NATs,
   510  					}); err != nil {
   511  						return false, err
   512  					}
   513  					ngxPkg := desireNginx(ngxBuf.String())
   514  					ngxBuf.Reset()
   515  					desireNodeAgent(node.Machine, node.Firewall, ngxPkg, common.Package{})
   516  				}
   517  				return done, nil
   518  			}
   519  			return orbiter.NoopEnsure, nil
   520  		}, orbiter.NoopDestroy, orbiter.NoopConfigure, migrate, make(map[string]*secret.Secret, 0), nil
   521  	}
   522  }
   523  
   524  func addToWhitelists(makeUnique bool, vips []*VIP, cidr ...*orbiter.CIDR) []*VIP {
   525  	newVIPs := make([]*VIP, len(vips))
   526  	for vipIdx, vip := range vips {
   527  		newTransport := make([]*Transport, len(vip.Transport))
   528  		for srcIdx, src := range vip.Transport {
   529  			newSource := &Transport{
   530  				Name:          src.Name,
   531  				FrontendPort:  src.FrontendPort,
   532  				BackendPort:   src.BackendPort,
   533  				BackendPools:  src.BackendPools,
   534  				Whitelist:     append(src.Whitelist, cidr...),
   535  				HealthChecks:  src.HealthChecks,
   536  				ProxyProtocol: src.ProxyProtocol,
   537  			}
   538  			if makeUnique {
   539  				newSource.Whitelist = unique(newSource.Whitelist)
   540  			}
   541  			newTransport[srcIdx] = newSource
   542  		}
   543  		newVIPs[vipIdx] = &VIP{
   544  			IP:        vip.IP,
   545  			Transport: newTransport,
   546  		}
   547  	}
   548  	return newVIPs
   549  }
   550  
   551  func probe(probeType, ip string, port uint16, proxyProtocol bool, hc HealthChecks, source Transport) {
   552  
   553  	var success float64
   554  	_, err := helpers.Check(hc.Protocol, ip, port, hc.Path, int(hc.Code), proxyProtocol)
   555  	if err == nil {
   556  		success = 1
   557  	}
   558  
   559  	probes.With(prometheus.Labels{
   560  		"name":   source.Name,
   561  		"type":   probeType,
   562  		"target": fmt.Sprintf("%s://%s:%d%s", hc.Protocol, ip, port, hc.Path),
   563  	}).Set(success)
   564  }
   565  
   566  type NATDesires struct {
   567  	NATs     []*NAT
   568  	Machine  infra.Machine
   569  	Firewall common.Firewall
   570  }
   571  
   572  type NAT struct {
   573  	Name          string
   574  	Whitelist     []*orbiter.CIDR
   575  	From          []string
   576  	To            string
   577  	ProxyProtocol bool
   578  }
   579  
   580  type LB struct {
   581  	VIPs                 []*VIP
   582  	State                string
   583  	RouterID             int
   584  	Self                 infra.Machine
   585  	Peers                []infra.Machine
   586  	CustomMasterNotifyer bool
   587  	VRRPInterface        string
   588  	VIPInterface         string
   589  }
   590  
   591  func unique(s []*orbiter.CIDR) []*orbiter.CIDR {
   592  	m := make(map[string]bool, len(s))
   593  	us := make([]*orbiter.CIDR, len(m))
   594  	for _, elem := range s {
   595  		if len(*elem) != 0 {
   596  			if !m[string(*elem)] {
   597  				us = append(us, elem)
   598  				m[string(*elem)] = true
   599  			}
   600  		}
   601  	}
   602  
   603  	cidrs := orbiter.CIDRs(us)
   604  	sort.Sort(cidrs)
   605  	return cidrs
   606  }
   607  
   608  type poolMachinesFunc func(svc core.MachinesService, do func(string, infra.Machines)) error
   609  
   610  func curryPoolMachines() poolMachinesFunc {
   611  	var poolsCache map[string]infra.Machines
   612  	return func(svc core.MachinesService, do func(string, infra.Machines)) error {
   613  
   614  		if poolsCache == nil {
   615  			poolsCache = make(map[string]infra.Machines)
   616  			allPools, err := svc.ListPools()
   617  			if err != nil {
   618  				return err
   619  			}
   620  
   621  			for _, pool := range allPools {
   622  				machines, err := svc.List(pool)
   623  				if err != nil {
   624  					return err
   625  				}
   626  				poolsCache[pool] = machines
   627  			}
   628  		}
   629  
   630  		if poolsCache != nil {
   631  			for pool, machines := range poolsCache {
   632  				do(pool, machines)
   633  			}
   634  		}
   635  		return nil
   636  	}
   637  }
   638  
   639  func curryEnrichedVIPs(desired Desired, machines poolMachinesFunc, adaptWhitelist []*orbiter.CIDR, nodeAgents *common.CurrentNodeAgents) func(svc core.MachinesService) (map[string][]*VIP, []AuthCheckResult, error) {
   640  	var enrichVIPsCache map[string][]*VIP
   641  	var authCheckResultsCache []AuthCheckResult
   642  	return func(svc core.MachinesService) (map[string][]*VIP, []AuthCheckResult, error) {
   643  		if enrichVIPsCache != nil && authCheckResultsCache != nil {
   644  			return enrichVIPsCache, authCheckResultsCache, nil
   645  		}
   646  		enrichVIPsCache = make(map[string][]*VIP)
   647  		authCheckResultsCache = make([]AuthCheckResult, 0)
   648  
   649  		addedCIDRs := append([]*orbiter.CIDR(nil), adaptWhitelist...)
   650  		if err := machines(svc, func(_ string, machines infra.Machines) {
   651  			for _, machine := range machines {
   652  				na, found := nodeAgents.Get(machine.ID())
   653  				if found {
   654  					cfg := na.Software.KeepaliveD.Config
   655  					if cfg != nil {
   656  						authCheckExitCode, ok := cfg["authcheckexitcode"]
   657  						if ok {
   658  							authCheckExitCodeInt, err := strconv.Atoi(authCheckExitCode)
   659  							if err == nil {
   660  								authCheckResultsCache = append(authCheckResultsCache, AuthCheckResult{
   661  									Machine:  machine,
   662  									ExitCode: authCheckExitCodeInt,
   663  								})
   664  							}
   665  						}
   666  					}
   667  				}
   668  				cidr := orbiter.CIDR(fmt.Sprintf("%s/32", machine.IP()))
   669  				addedCIDRs = append(addedCIDRs, &cidr)
   670  			}
   671  		}); err != nil {
   672  			return nil, nil, err
   673  		}
   674  		for deployPool, vips := range desired.Spec {
   675  			enrichVIPsCache[deployPool] = addToWhitelists(true, vips, addedCIDRs...)
   676  		}
   677  		return enrichVIPsCache, authCheckResultsCache, nil
   678  	}
   679  }
   680  
   681  func desireNginx(cfg string) common.Package {
   682  	return common.Package{
   683  		Version: nginxVersion,
   684  		Config: map[string]string{
   685  			"nginx.conf":                  cfg,
   686  			"Systemd[Service]LimitNOFILE": "8192",
   687  		},
   688  	}
   689  }