github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/internal/operator/orbiter/kinds/providers/gce/normalize.go (about) 1 package gce 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/caos/orbos/internal/helpers" 8 9 "google.golang.org/api/googleapi" 10 11 "github.com/caos/orbos/internal/operator/orbiter" 12 13 "github.com/caos/orbos/mntr" 14 15 "google.golang.org/api/compute/v1" 16 17 "github.com/caos/orbos/internal/operator/orbiter/kinds/loadbalancers/dynamic" 18 ) 19 20 type normalizedLoadbalancer struct { 21 forwardingRule *forwardingRule // unique 22 targetPool *targetPool // unique 23 healthcheck *healthcheck // unique 24 address *address // The same externalIP reference appears in multiple normalizedLoadbalancer references 25 transport string 26 backendPort uint16 27 } 28 29 type StandardLogFunc func(msg string, debug bool) func() 30 31 type forwardingRule struct { 32 log StandardLogFunc 33 gce *compute.ForwardingRule 34 } 35 36 type targetPool struct { 37 log func(msg string, debug bool, instances []*instance) func() 38 gce *compute.TargetPool 39 destPools []string 40 } 41 42 type healthcheck struct { 43 log StandardLogFunc 44 gce *compute.HttpHealthCheck 45 desired dynamic.HealthChecks 46 proxyProtocol bool 47 pools []string 48 } 49 50 type firewall struct { 51 log StandardLogFunc 52 gce *compute.Firewall 53 } 54 type address struct { 55 log StandardLogFunc 56 gce *compute.Address 57 } 58 59 type normalizedLoadbalancing []*normalizedLoadbalancer 60 61 func (n normalizedLoadbalancing) uniqueAddresses() []*address { 62 addresses := make([]*address, 0) 63 loop: 64 for _, lb := range n { 65 for _, found := range addresses { 66 if lb.address == found { 67 continue loop 68 } 69 } 70 addresses = append(addresses, lb.address) 71 } 72 return addresses 73 } 74 75 func (n normalizedLoadbalancing) Len() int { return len(n) } 76 func (n normalizedLoadbalancing) Swap(i, j int) { n[i], n[j] = n[j], n[i] } 77 func (n normalizedLoadbalancing) Less(i, j int) bool { 78 return n[i].forwardingRule.gce.Description < n[j].forwardingRule.gce.Description 79 } 80 81 type normalizedDestination struct { 82 port dynamic.Port 83 pools []string 84 hc dynamic.HealthChecks 85 } 86 87 type sortableDestinations []*normalizedDestination 88 89 func (n sortableDestinations) Len() int { return len(n) } 90 func (n sortableDestinations) Swap(i, j int) { n[i], n[j] = n[j], n[i] } 91 func (n sortableDestinations) Less(i, j int) bool { 92 return n[i].port < n[j].port || n[i].hc.Protocol < n[i].hc.Protocol || n[i].hc.Path < n[i].hc.Path || n[i].hc.Code < n[i].hc.Code 93 } 94 95 // normalize returns a normalizedLoadBalancing for each unique destination backendPort and ip combination 96 // whereas only one random configured healthcheck is relevant 97 func normalize(ctx *context, spec map[string][]*dynamic.VIP) ([]*normalizedLoadbalancer, []*firewall) { 98 var normalized []*normalizedLoadbalancer 99 var firewalls []*firewall 100 101 providerDescription := fmt.Sprintf("orb=%s;provider=%s", ctx.orbID, ctx.providerID) 102 103 for ipName, ips := range spec { 104 for ipIdx, ip := range ips { 105 ipID := fmt.Sprintf("%s-%d", ipName, ipIdx) 106 normalizedAddress := &address{ 107 gce: &compute.Address{ 108 Description: fmt.Sprintf("orb=%s;provider=%s;id=%s", ctx.orbID, ctx.providerID, ipID), 109 }, 110 } 111 normalizedAddress.log = func(addr *address, monitor mntr.Monitor, internalID string) func(msg string, debug bool) func() { 112 localMonitor := monitor.WithField("internal-id", internalID) 113 return func(msg string, debug bool) func() { 114 if addr.gce.Name != "" { 115 localMonitor = localMonitor.WithField("external-id", addr.gce.Name) 116 } 117 level := localMonitor.Info 118 if debug { 119 level = localMonitor.Debug 120 } 121 122 return func() { 123 level(msg) 124 } 125 } 126 }(normalizedAddress, ctx.monitor, ipID) 127 128 addressTransports := make([]string, 0) 129 for _, src := range ip.Transport { 130 addressTransports = append(addressTransports, src.Name) 131 destDescription := fmt.Sprintf("%s;transport=%s", providerDescription, src.Name) 132 destMonitor := ctx.monitor.WithFields(map[string]interface{}{ 133 "transport": src.Name, 134 }) 135 fwr := &compute.ForwardingRule{ 136 Description: destDescription, 137 LoadBalancingScheme: "EXTERNAL", 138 PortRange: fmt.Sprintf("%d-%d", src.FrontendPort, src.FrontendPort), 139 } 140 141 tp := &compute.TargetPool{ 142 Description: destDescription, 143 } 144 hc := &compute.HttpHealthCheck{ 145 Description: destDescription, 146 RequestPath: src.HealthChecks.Path, 147 } 148 149 normalized = append(normalized, &normalizedLoadbalancer{ 150 backendPort: uint16(src.BackendPort), 151 forwardingRule: &forwardingRule{ 152 log: func(msg string, debug bool) func() { 153 localMonitor := destMonitor 154 if fwr.Name != "" { 155 localMonitor = localMonitor.WithField("id", fwr.Name) 156 } 157 level := localMonitor.Info 158 if debug { 159 level = localMonitor.Debug 160 } 161 162 return func() { 163 level(msg) 164 } 165 }, 166 gce: fwr, 167 }, 168 targetPool: &targetPool{ 169 log: func(msg string, debug bool, insts []*instance) func() { 170 localMonitor := destMonitor 171 if len(insts) > 0 { 172 localMonitor = localMonitor.WithField("instances", instances(insts).strings(func(i *instance) string { return i.X_ID })) 173 } 174 if tp.Name != "" { 175 localMonitor = localMonitor.WithField("id", tp.Name) 176 } 177 level := localMonitor.Info 178 if debug { 179 level = localMonitor.Debug 180 } 181 return func() { 182 level(msg) 183 } 184 }, 185 gce: tp, 186 destPools: src.BackendPools, 187 }, 188 healthcheck: &healthcheck{ 189 log: func(msg string, debug bool) func() { 190 localMonitor := destMonitor 191 if hc.Name != "" { 192 localMonitor = localMonitor.WithField("id", hc.Name) 193 } 194 level := localMonitor.Info 195 if debug { 196 level = localMonitor.Debug 197 } 198 199 return func() { 200 level(msg) 201 } 202 }, 203 gce: hc, 204 desired: src.HealthChecks, 205 pools: src.BackendPools, 206 proxyProtocol: *src.ProxyProtocol, 207 }, 208 address: normalizedAddress, 209 transport: src.Name, 210 }) 211 212 firewalls = append(firewalls, toInternalFirewall(&compute.Firewall{ 213 Network: ctx.networkURL, 214 Description: fmt.Sprintf("External %s", src.Name), 215 Allowed: []*compute.FirewallAllowed{{ 216 IPProtocol: "tcp", 217 Ports: []string{fmt.Sprintf("%d", src.FrontendPort)}, 218 }}, 219 SourceRanges: whitelistStrings(src.Whitelist), 220 TargetTags: networkTags(ctx.orbID, ctx.providerID, src.BackendPools...), 221 }, destMonitor)) 222 } 223 sort.Strings(addressTransports) 224 } 225 } 226 227 sort.Sort(normalizedLoadbalancing(normalized)) 228 229 var hcPort int64 = 6700 230 for _, lb := range normalized { 231 lb.healthcheck.gce.Port = hcPort 232 firewalls = append(firewalls, toInternalFirewall(&compute.Firewall{ 233 Description: fmt.Sprintf("Healthchecks %s", lb.transport), 234 Network: ctx.networkURL, 235 Allowed: []*compute.FirewallAllowed{{ 236 IPProtocol: "tcp", 237 Ports: []string{fmt.Sprintf("%d", hcPort)}, 238 }}, 239 SourceRanges: []string{ 240 // healthcheck sources, see https://cloud.google.com/load-balancing/docs/health-checks#fw-netlb 241 "35.191.0.0/16", 242 "209.85.152.0/22", 243 "209.85.204.0/22", 244 }, 245 TargetTags: networkTags(ctx.orbID, ctx.providerID, lb.healthcheck.pools...), 246 }, ctx.monitor)) 247 hcPort++ 248 } 249 250 return normalized, append(firewalls, toInternalFirewall(&compute.Firewall{ 251 Network: ctx.networkURL, 252 Allowed: []*compute.FirewallAllowed{{ 253 IPProtocol: "tcp", 254 Ports: []string{"0-65535"}, 255 }, { 256 IPProtocol: "udp", 257 Ports: []string{"0-65535"}, 258 }, { 259 IPProtocol: "icmp", 260 }, { 261 IPProtocol: "ipip", 262 }}, 263 Description: "Internal Communication", 264 SourceRanges: []string{"10.128.0.0/9"}, 265 TargetTags: networkTags(ctx.orbID, ctx.providerID), 266 }, ctx.monitor), toInternalFirewall(&compute.Firewall{ 267 Network: ctx.networkURL, 268 Allowed: []*compute.FirewallAllowed{{ 269 IPProtocol: "tcp", 270 Ports: []string{"22"}, 271 }}, 272 Description: "SSH through IAP", 273 SourceRanges: []string{"35.235.240.0/20"}, 274 TargetTags: networkTags(ctx.orbID, ctx.providerID), 275 }, ctx.monitor)) 276 } 277 278 func toInternalFirewall(fw *compute.Firewall, monitor mntr.Monitor) *firewall { 279 return &firewall{ 280 log: func(msg string, debug bool) func() { 281 if fw.Name != "" { 282 monitor = monitor.WithField("id", fw.Name) 283 } 284 level := monitor.Info 285 if debug { 286 level = monitor.Debug 287 } 288 289 return func() { 290 level(msg) 291 } 292 }, 293 gce: fw, 294 } 295 } 296 297 func newName() string { 298 return "orbos-" + helpers.RandomStringRunes(6, []rune("abcdefghijklmnopqrstuvwxyz0123456789")) 299 } 300 301 func removeLog(monitor mntr.Monitor, resource, id string, removed bool, debug bool) func() { 302 msg := "Removing resource" 303 if removed { 304 msg = "Resource removed" 305 } 306 monitor = monitor.WithFields(map[string]interface{}{ 307 "type": resource, 308 "id": id, 309 }) 310 level := monitor.Info 311 if debug { 312 level = monitor.Debug 313 } 314 return func() { 315 level(msg) 316 } 317 } 318 319 func removeResourceFunc(monitor mntr.Monitor, resource, id string, call func(...googleapi.CallOption) (*compute.Operation, error)) func() error { 320 return func() error { 321 if err := operateFunc( 322 removeLog(monitor, resource, id, false, true), 323 computeOpCall(call), 324 nil, 325 )(); err != nil { 326 googleErr, ok := err.(*googleapi.Error) 327 if !ok || googleErr.Code != 404 { 328 return err 329 } 330 } 331 removeLog(monitor, resource, id, true, false)() 332 return nil 333 } 334 } 335 336 func queryLB(context *context, normalized []*normalizedLoadbalancer) (func() error, error) { 337 lb, err := chainInEnsureOrder( 338 context, normalized, 339 queryHealthchecks, 340 queryTargetPools, 341 queryAddresses, 342 queryForwardingRules, 343 ) 344 345 if err != nil { 346 return nil, err 347 } 348 349 return func() error { 350 for _, fn := range lb { 351 if err := fn(); err != nil { 352 return err 353 } 354 } 355 return nil 356 }, nil 357 } 358 359 type ensureLBFunc func(*context, []*normalizedLoadbalancer) ([]func() error, []func() error, error) 360 361 type ensureFWFunc func(*context, []*firewall) ([]func() error, []func() error, error) 362 363 func chainInEnsureOrder(ctx *context, lb []*normalizedLoadbalancer, query ...ensureLBFunc) ([]func() error, error) { 364 var ensureOperations []func() error 365 var removeOperations []func() error 366 367 for _, fn := range query { 368 ensure, remove, err := fn(ctx, lb) 369 if err != nil { 370 return nil, err 371 } 372 ensureOperations = append(ensureOperations, helpers.Fanout(ensure)) 373 removeOperations = append(removeOperations, helpers.Fanout(remove)) 374 } 375 376 for i := 0; i < len(removeOperations)/2; i++ { 377 j := len(removeOperations) - i - 1 378 removeOperations[i], removeOperations[j] = removeOperations[j], removeOperations[i] 379 } 380 381 return append(removeOperations, ensureOperations...), nil 382 } 383 384 func whitelistStrings(cidrs []*orbiter.CIDR) []string { 385 l := len(cidrs) 386 wl := make([]string, l, l) 387 for idx, cidr := range cidrs { 388 wl[idx] = string(*cidr) 389 } 390 return wl 391 }