class MU::Cloud::Google::LoadBalancer

A load balancer as configured in {MU::Config::BasketofKittens::loadbalancers}

Attributes

targetgroups[R]

Public Class Methods

cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: nil, credentials: nil, flags: {}) click to toggle source

Remove all load balancers associated with the currently loaded deployment. @param noop [Boolean]: If true, will only print what would be done @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server @param region [String]: The cloud provider region @return [void]

# File modules/mu/providers/google/loadbalancer.rb, line 149
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: nil, credentials: nil, flags: {})
  flags["habitat"] ||= MU::Cloud::Google.defaultProject(credentials)
  return if !MU::Cloud.resourceClass("Google", "Habitat").isLive?(flags["habitat"], credentials)
  filter = %Q{(labels.mu-id = "#{MU.deploy_id.downcase}")}
  if !ignoremaster and MU.mu_public_ip
    filter += %Q{ AND (labels.mu-master-ip = "#{MU.mu_public_ip.gsub(/\./, "_")}")}
  end
  MU.log "Placeholder: Google LoadBalancer artifacts do not support labels, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: filter

  if region
    ["forwarding_rule", "region_backend_service"].each { |type|
      MU::Cloud::Google.compute(credentials: credentials).delete(
        type,
        flags["habitat"],
        region,
        noop
      )
    }
  end

  if flags['global']
    ["global_forwarding_rule", "target_http_proxy", "target_https_proxy", "url_map", "backend_service", "health_check", "http_health_check", "https_health_check"].each { |type|
      MU::Cloud::Google.compute(credentials: credentials).delete(
        type,
        flags["habitat"],
        nil,
        noop
      )
    }
  end
end
find(**args) click to toggle source

Locate an existing LoadBalancer or LoadBalancers and return an array containing matching Google resource descriptors for those that match. @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching LoadBalancers

# File modules/mu/providers/google/loadbalancer.rb, line 293
def self.find(**args)
  args = MU::Cloud::Google.findLocationArgs(args)
end
isGlobal?() click to toggle source

Does this resource type exist as a global (cloud-wide) artifact, or is it localized to a region/zone? @return [Boolean]

# File modules/mu/providers/google/loadbalancer.rb, line 134
def self.isGlobal?
  true
end
new(**args) click to toggle source

Initialize this cloud resource object. Calling super will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like @vpc, for us. @param args [Hash]: Hash of named arguments passed via Ruby's double-splat

Calls superclass method
# File modules/mu/providers/google/loadbalancer.rb, line 26
def initialize(**args)
  super
  @mu_name ||= @deploy.getResourceName(@config["name"])
end
quality() click to toggle source

Denote whether this resource implementation is experiment, ready for testing, or ready for production use.

# File modules/mu/providers/google/loadbalancer.rb, line 140
def self.quality
  MU::Cloud::RELEASE
end
schema(_config) click to toggle source

Cloud-specific configuration properties. @param _config [MU::Config]: The calling MU::Config object @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource

# File modules/mu/providers/google/loadbalancer.rb, line 184
def self.schema(_config)
  toplevel_required = []
  schema = {
    "named_ports" => {
      "type" => "array",
      "items" => {
        "type" => "object",
        "required" => ["name", "port"],
        "additionalProperties" => false,
        "description" => "A named network port for a Google instance group, used for health checks and forwarding targets.",
        "properties" => {
          "name" => {
            "type" => "string"
          },
          "port" => {
            "type" => "integer"
          }
        }
      }
    }
  }
  [toplevel_required, schema]
end
validateConfig(lb, _configurator) click to toggle source

Cloud-specific pre-processing of {MU::Config::BasketofKittens::loadbalancers}, bare and unvalidated. @param lb [Hash]: The resource to process and validate @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member @return [Boolean]: True if validation succeeded, False otherwise

# File modules/mu/providers/google/loadbalancer.rb, line 212
def self.validateConfig(lb, _configurator)
  ok = true
  if lb['classic']
    MU.log "LoadBalancer 'classic' flag has no meaning in Google Cloud", MU::WARN
  end

  if lb['app_cookie_stickiness_policy']
    MU.log "LoadBalancer 'app_cookie_stickiness_policy' option has no meaning in Google Cloud", MU::WARN
    lb.delete('app_cookie_stickiness_policy')
  end
  if lb['ip_stickiness_policy'] 
    if !lb['private']
      if lb['ip_stickiness_policy']['map_port']
        MU.log "Can only use map_port in IP stickiness policy with private LoadBalancers", MU::ERR
        ok = false
      end
      if lb['ip_stickiness_policy']['map_proto']
        MU.log "Can only use map_proto in IP stickiness policy with private LoadBalancers", MU::ERR
        ok = false
      end
    elsif lb['ip_stickiness_policy']['map_port'] and !lb['ip_stickiness_policy']['map_proto']
      MU.log "Can't use map_port in IP stickiness policy without map_proto", MU::ERR
      ok = false
    end
  end

  if lb['private'] and lb['global']
    MU.log "Private Google Cloud LoadBalancer requested, setting 'global' flag to false", MU::WARN
    lb['global'] = false
  end

  lb["listeners"].each { |l|
    if lb["private"] and !["TCP", "UDP"].include?(l['lb_protocol'])
      MU.log "Only TCP and UDP listeners are valid for private LoadBalancers in Google Cloud", MU::ERR
      ok = false
    end

    if lb['global'] and l['lb_protocol'] == "UDP"
      MU.log "UDP LoadBalancers can only be per-region in Google Cloud. Setting 'global' to false.", MU::WARN
      lb['global'] = false
    end
    if lb['global'] and !["HTTP", "HTTPS"].include?(l['instance_protocol'])
      MU.log "Global LoadBalancers in Google Cloud can only target HTTP or HTTPS backends", MU::ERR, details: l
      ok = false
    end
  }

  lb["targetgroups"].each { |tg|
    if tg["healthcheck"]
      target = tg["healthcheck"]['target'].match(/^([^:]+):(\d+)(.*)/)
      if tg["proto"] != target[1]
        MU.log "LoadBalancer #{lb['name']} can't mix and match target group and health check protocols in Google Cloud", MU::ERR, details: tg
        ok = false
      end
    else
      # health checks are required; create a generic one
      tg["healthcheck"] = {
        "timeout" => 5,
        "interval" => 30,
        "unhealthy_threshold" => 2,
        "healthy_threshold" => 2,
      }
      if tg["proto"] == "HTTP" or tg["proto"] == "HTTPS"
        if lb['private']
          MU.log "Private GCP LoadBalancers can only target TCP or UDP protocols, changing #{tg["proto"]} to TCP", MU::NOTICE
          tg["proto"] = "TCP"
        end
        tg["healthcheck"]["target"] = tg["proto"]+":"+tg["port"].to_s+"/"
        tg["healthcheck"]["httpcode"] = "200,301,302"
      else
        tg["healthcheck"]["target"] = tg["proto"]+":"+tg["port"].to_s
      end
      MU.log "No healthcheck declared for target group #{tg['name']} in LoadBalancer #{lb['name']}, creating one.", details: tg
    end
  }

  ok
end

Public Instance Methods

create() click to toggle source

Called automatically by {MU::Deploy#createResources}

# File modules/mu/providers/google/loadbalancer.rb, line 32
        def create
          parent_thread_id = Thread.current.object_id

          backends = {}
          targets = {}
          if @config['targetgroups']
            threads = []
            @config['targetgroups'].each { |tg|
              threads << Thread.new {
                MU.dupGlobals(parent_thread_id)

                if !@config['private']
                  backends[tg['name']] = createBackendService(tg)
                  targets[tg['name']] = createProxy(tg, backends[tg['name']])
                else
                  backends[tg['name']] = createBackendService(tg)
                end
              }
            }
            threads.each do |t|
              t.join
            end
          end

          @config["listeners"].each { |l|
            ruleobj = nil
            if !@config["private"]
#TODO ip_address, port_range, target
              realproto = ["HTTP", "HTTPS"].include?(l['lb_protocol']) ? l['lb_protocol'] : "TCP"
              ruleobj = ::Google::Apis::ComputeV1::ForwardingRule.new(
                name: MU::Cloud::Google.nameStr(@mu_name+"-"+l['targetgroup']),
                description: @deploy.deploy_id,
                load_balancing_scheme: "EXTERNAL",
                target: targets[l['targetgroup']].self_link,
                ip_protocol: realproto,
                port_range: l['lb_port'].to_s
              )
            else
# TODO network, subnetwork, port_range, target
              ruleobj = ::Google::Apis::ComputeV1::ForwardingRule.new(
                name: MU::Cloud::Google.nameStr(@mu_name+"-"+l['targetgroup']),
                description: @deploy.deploy_id,
                load_balancing_scheme: "INTERNAL",
                backend_service: backends[l['targetgroup']].self_link,
                ip_protocol: l['lb_protocol'],
                ports: [l['lb_port'].to_s]
              )
            end
            if @config['global']
              MU.log "Creating Global Forwarding Rule #{@mu_name}", MU::NOTICE, details: ruleobj
              MU::Cloud::Google.compute(credentials: @config['credentials']).insert_global_forwarding_rule(
                @project_id,
                ruleobj
              )
            else
              MU.log "Creating regional Forwarding Rule #{@mu_name} in #{@config['region']}", MU::NOTICE, details: ruleobj
              MU::Cloud::Google.compute(credentials: @config['credentials']).insert_forwarding_rule(
                @project_id,
                @config['region'],
                ruleobj
              )
            end
          }

        end
notify() click to toggle source

Return the metadata for this LoadBalancer @return [Hash]

# File modules/mu/providers/google/loadbalancer.rb, line 100
def notify
  rules = {}
  resp = MU::Cloud::Google.compute(credentials: @config['credentials']).list_global_forwarding_rules(
    @project_id,
    filter: "description eq #{@deploy.deploy_id}"
  )
  if resp.nil? or resp.items.nil? or resp.items.size == 0
    resp = MU::Cloud::Google.compute(credentials: @config['credentials']).list_forwarding_rules(
      @project_id,
      @config['region'],
      filter: "description eq #{@deploy.deploy_id}"
    )
  end
  if !resp.nil? and !resp.items.nil?
    resp.items.each { |rule|
      rules[rule.name] = rule.to_h
      rules[rule.name].delete(:label_fingerprint)
    }
  end
  rules["project_id"] = @project_id

  rules
end
registerNode(instance_id, targetgroups: nil) click to toggle source

Register a Server node with an existing LoadBalancer.

@param instance_id [String] A node to register. @param targetgroups [Array<String>] The target group(s) of which this node should be made a member. Not applicable to classic LoadBalancers. If not supplied, the node will be registered to all available target groups on this LoadBalancer.

# File modules/mu/providers/google/loadbalancer.rb, line 128
def registerNode(instance_id, targetgroups: nil)
end

Private Instance Methods

createBackendService(tg) click to toggle source
# File modules/mu/providers/google/loadbalancer.rb, line 344
        def createBackendService(tg)
          desc = {
            :name => MU::Cloud::Google.nameStr(@deploy.getResourceName(tg["name"])),
            :description => @deploy.deploy_id,
            :load_balancing_scheme => @config['private'] ? "INTERNAL" : "EXTERNAL",
            :global => @config['global'],
            :protocol => tg['proto'],
            :timeout_sec => @config['idle_timeout']
          }
# TODO EXTERNAL only: port_name, enable_cdn
          if @config['connection_draining_timeout'] > 0
            desc[:connection_draining] = MU::Cloud::Google.compute(:ConnectionDraining).new(
              draining_timeout_sec: @config['connection_draining_timeout']
            )
          end
          if @config['lb_cookie_stickiness_policy'] and !@config["private"]
            desc[:session_affinity] = "GENERATED_COOKIE"
            desc[:affinity_cookie_ttl_sec] = @config['lb_cookie_stickiness_policy']['timeout']
          elsif @config['ip_stickiness_policy'] and tg['proto'] != "UDP"
            desc[:session_affinity] = "CLIENT_IP"
            if @config["private"]
              if @config['ip_stickiness_policy']["map_port"]
                desc[:session_affinity] += "_PORT"
              end
              if @config['ip_stickiness_policy']["map_proto"]
                desc[:session_affinity] += "_PROTO"
              end
            end
          else
            desc[:session_affinity] = "NONE"
          end
          if tg["healthcheck"]
            hc = createHealthCheck(tg["healthcheck"], tg["name"])
            desc[:health_checks] = [hc.self_link]
          end

          backend_obj = MU::Cloud::Google.compute(:BackendService).new(desc)
          MU.log "Creating backend service #{MU::Cloud::Google.nameStr(@deploy.getResourceName(tg["name"]))}", details: backend_obj
          if @config['private'] and !@config['global']
            return MU::Cloud::Google.compute(credentials: @config['credentials']).insert_region_backend_service(
              @project_id,
              @config['region'],
              backend_obj
            )
          else
            return MU::Cloud::Google.compute(credentials: @config['credentials']).insert_backend_service(
              @project_id,
              backend_obj
            )
          end
        end
createHealthCheck(hc, namebase) click to toggle source
# File modules/mu/providers/google/loadbalancer.rb, line 396
        def createHealthCheck(hc, namebase)
#          MU.log "HEALTH CHECK", MU::NOTICE, details: hc
          target = hc['target'].match(/^([^:]+):(\d+)(.*)/)
          proto = target[1]
          port = target[2]
          path = target[3]
          name = MU::Cloud::Google.nameStr(@deploy.getResourceName(namebase+"-hc-"+proto.downcase+"-"+port.to_s))

          if proto == "HTTP" or proto == "HTTPS"
            hc_obj = MU::Cloud::Google.compute(proto == "HTTP" ? :HttpHealthCheck : :HttpsHealthCheck).new(
              check_interval_sec: hc["interval"],
              timeout_sec: hc["timeout"],
              unhealthy_threshold: hc["unhealthy_threshold"],
              healthy_threshold: hc["healthy_threshold"],
              description: @deploy.deploy_id,
              name: name,
              port: port,
              request_path: path ? path : "/"
            )
# other types:
# type: SSL, HTTP2
            MU.log "Creating #{proto} health check #{name}", details: hc_obj
            if proto == "HTTP"
              return MU::Cloud::Google.compute(credentials: @config['credentials']).insert_http_health_check(
                @project_id,
                hc_obj
              )
            else
              return MU::Cloud::Google.compute(credentials: @config['credentials']).insert_https_health_check(
                @project_id,
                hc_obj
              )
            end
          else
            desc = {
              :check_interval_sec => hc["interval"],
              :timeout_sec => hc["timeout"],
              :unhealthy_threshold => hc["unhealthy_threshold"],
              :healthy_threshold => hc["healthy_threshold"],
              :description => @deploy.deploy_id,
              :name => name
            }
            if proto == "TCP"
              desc[:type] = "TCP"
              desc[:tcp_health_check] = MU::Cloud::Google.compute(:TcpHealthCheck).new(
                port: port,
                proxy_header: "NONE",
                request: "",
                response: ""
              )
            elsif proto == "SSL"
              desc[:type] = "SSL"
              desc[:ssl_health_check] = MU::Cloud::Google.compute(:SslHealthCheck).new(
                port: port,
                proxy_header: "NONE",
                request: "", # XXX needs to be configurable
                response: "" # XXX needs to be configurable
              )
            elsif proto == "UDP"
              desc[:type] = "UDP"
              desc[:udp_health_check] = MU::Cloud::Google.compute(:UdpHealthCheck).new(
                port: port,
                request: "ORLY", # XXX needs to be configurable
                response: "YARLY" # XXX needs to be configurable
              )
            end
            hc_obj = MU::Cloud::Google.compute(:HealthCheck).new(desc)
            MU.log "INSERTING HEALTH CHECK", MU::NOTICE, details: hc_obj
            return MU::Cloud::Google.compute(credentials: @config['credentials']).insert_health_check(
              @project_id,
              hc_obj
            )
          end

        end
createProxy(tg, backend) click to toggle source
# File modules/mu/providers/google/loadbalancer.rb, line 299
        def createProxy(tg, backend)
          name = MU::Cloud::Google.nameStr(@deploy.getResourceName(tg["name"]))

          urlmap_obj = MU::Cloud::Google.compute(:UrlMap).new(
            name: name,
            description: @deploy.deploy_id,
# TODO this is where path_matchers, host_rules, and tests go (the sophisticated
# Layer 7 stuff)
            default_service: backend.self_link
          )
          MU.log "Creating url map #{tg['name']}", details: urlmap_obj
          urlmap = MU::Cloud::Google.compute(credentials: @config['credentials']).insert_url_map(
            @project_id,
            urlmap_obj
          )

          desc = {
            :name => name,
            :description => @deploy.deploy_id,
            :url_map => urlmap.self_link
          }

          if tg['proto'] == "HTTP"
            target_obj = MU::Cloud::Google.compute(:TargetHttpProxy).new(desc)
            MU.log "Creating http target proxy #{tg['name']}", details: target_obj
            MU::Cloud::Google.compute(credentials: @config['credentials']).insert_target_http_proxy(
              @project_id,
              target_obj
            )
          else
            certdata = @deploy.nodeSSLCerts(self, false, 2048)
            cert_pem = certdata[0].to_s+File.read("/etc/pki/Mu_CA.pem")
            gcpcert = MU::Cloud::Google.createSSLCertificate(@mu_name.downcase+"-"+tg['name'], cert_pem, certdata[1], credentials: @config['credentials'])

# TODO we need a method like MU::Cloud::AWS.findSSLCertificate, with option to hunt down an existing one
            desc[:ssl_certificates] = [gcpcert.self_link]
            target_obj = MU::Cloud::Google.compute(:TargetHttpsProxy).new(desc)
            MU.log "Creating https target proxy #{tg['name']}", details: target_obj
            MU::Cloud::Google.compute(credentials: @config['credentials']).insert_target_https_proxy(
              @project_id,
              target_obj
            )
          end
        end