Class: KubeLinkSpecs

Inherits:
Object
  • Object
show all
Defined in:
lib/kube_link_generator.rb

Overview

KubeLinkSpecs provides the information required to generate BOSH links by pretending to be a hash.

Constant Summary collapse

ANNOTATION_AZ =

ANNOTATION_AZ is the Kube annotation for the (availability) zone

'failure-domain.beta.kubernetes.io/zone'.freeze

Instance Method Summary collapse

Constructor Details

#initialize(spec, namespace, kube_client, kube_client_stateful_set) ⇒ KubeLinkSpecs

Returns a new instance of KubeLinkSpecs.



11
12
13
14
15
16
17
# File 'lib/kube_link_generator.rb', line 11

def initialize(spec, namespace, kube_client, kube_client_stateful_set)
  @links = {}
  @client = kube_client
  @client_stateful_set = kube_client_stateful_set
  @namespace = namespace
  @spec = spec || {}
end

Instance Method Details

#[](key) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/kube_link_generator.rb', line 133

def [](key)
  return @links[key] if @links.key? key

  # Resolve the role we're looking for
  provider = @spec['consumes'][key]
  unless provider
    $stderr.puts "No link provider found for #{key}"
    return @links[key] = nil
  end

  if provider['role'] == this_name
    $stderr.puts "Resolving link #{key} via self provider #{provider}"
    pods = get_pods_for_role(provider['role'], true)
    pods_per_image = get_pods_per_image(pods)
    instances = pods.map { |p| get_pod_instance_info(p, provider['job'], pods_per_image) }
  elsif service? provider['role']
    # Getting pods for a different service; since we have kube services, we don't handle it in configgin
    $stderr.puts "Resolving link #{key} via service #{provider}"
    instances = [get_svc_instance_info(provider['role'], provider['job'])]
  else
    # If there's no service associated, check the statefulset instead
    $stderr.puts "Resolving link #{key} via statefulset #{provider}"
    instances = get_statefulset_instance_info(provider['role'], provider['job'])
  end

  @links[key] = {
    'address' => "#{provider['role']}.#{ENV['KUBERNETES_NAMESPACE']}.svc.#{ENV['KUBERNETES_CLUSTER_DOMAIN']}",
    'instance_group' => '',
    'default_network' => '',
    'deployment_name' => @namespace,
    'domain' => "#{ENV['KUBERNETES_NAMESPACE']}.svc.#{ENV['KUBERNETES_CLUSTER_DOMAIN']}",
    'root_domain' => "#{ENV['KUBERNETES_NAMESPACE']}.svc.#{ENV['KUBERNETES_CLUSTER_DOMAIN']}",
    'instances' => instances,
    'properties' => instances.first['properties']
  }
end

#_get_pods_for_role(role_name) ⇒ Object



36
37
38
# File 'lib/kube_link_generator.rb', line 36

def _get_pods_for_role(role_name)
  @client.get_pods(namespace: @namespace, label_selector: "skiff-role-name=#{role_name}")
end

#get_pod_instance_info(pod, job, pods_per_image) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/kube_link_generator.rb', line 63

def get_pod_instance_info(pod, job, pods_per_image)
  index = pod_index(pod..name)
  properties = JSON.parse(pod..annotations['skiff-exported-properties'])
  {
    'name' => pod..name,
    'index' => index,
    'id' => pod..name,
    'az' => pod..annotations['failure-domain.beta.kubernetes.io/zone'] || 'az0',
    'address' => pod.status.podIP,
    'properties' => properties.fetch(job, {}),
    'bootstrap' => pods_per_image[pod..uid] < 2
  }
end

#get_pods_for_role(role_name, wait_for_ip) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/kube_link_generator.rb', line 40

def get_pods_for_role(role_name, wait_for_ip)
  loop do
    # The 30.times loop exists to print out status messages
    30.times do
      1.times do
        pods = _get_pods_for_role(role_name)
        if wait_for_ip
          # Wait until all pods have IP addresses and properties
          break unless pods.all? { |pod| pod.status.podIP }
          break unless pods.all? { |pod| pod..annotations['skiff-exported-properties'] }
        else
          # We just need one pod with exported properties
          pods.select! { |pod| pod.status.podIP }
          pods.select! { |pod| pod..annotations['skiff-exported-properties'] }
        end
        return pods unless pods.empty?
      end
      sleep 1
    end
    $stdout.puts "Waiting for pods for role #{role_name} (at #{Time.now})..."
  end
end

#get_pods_per_image(pods) ⇒ Object

Return the number of pods for each image



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/kube_link_generator.rb', line 78

def get_pods_per_image(pods)
  result = {}
  sets = Hash.new(0)
  keys = {}
  pods.each do |pod|
    key = pod.status.containerStatuses.map(&:imageID).sort.join("\n")
    sets[key] += 1
    keys[pod..uid] = key
  end
  pods.each do |pod|
    result[pod..uid] = sets[keys[pod..uid]]
  end
  result
end

#get_statefulset_instance_info(role_name, job) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/kube_link_generator.rb', line 108

def get_statefulset_instance_info(role_name, job)
  ss = @client_stateful_set.get_stateful_set(role_name, @namespace)
  pod = get_pods_for_role(role_name, false).first
  properties = JSON.parse(pod..annotations['skiff-exported-properties'])

  Array.new(ss.spec.replicas) do |i|
    {
      'name' => ss..name,
      'index' => i,
      'id' => ss..name,
      'az' => pod..annotations['failure-domain.beta.kubernetes.io/zone'] || 'az0',
      'address' => "#{ss.metadata.name}-#{i}.#{ss.spec.serviceName}",
      'properties' => properties.fetch(job, {}),
      'bootstrap' => i.zero?
    }
  end
end

#get_svc_instance_info(role_name, job) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/kube_link_generator.rb', line 93

def get_svc_instance_info(role_name, job)
  svc = @client.get_service(role_name, @namespace)
  pod = get_pods_for_role(role_name, false).first
  properties = JSON.parse(pod..annotations['skiff-exported-properties'])
  {
    'name' => svc..name,
    'index' => 0, # Completely made up index; there is only ever one service
    'id' => svc..name,
    'az' => pod..annotations['failure-domain.beta.kubernetes.io/zone'] || 'az0',
    'address' => svc.spec.clusterIP,
    'properties' => properties.fetch(job, {}),
    'bootstrap' => true
  }
end

#pod_index(name) ⇒ Object

pod_index returns a number for the given pod name. The number is expected to be unique across all pods for the role.



25
26
27
28
29
30
31
32
33
34
# File 'lib/kube_link_generator.rb', line 25

def pod_index(name)
  index = name.rpartition('-').last
  return index.to_i if /^\d+$/ =~ index
  # The pod name is something like role-abcxyz
  # Derive the index from the randomness that went into the suffix.
  # chars are the characters kubernetes might use to generate names
  # Copied from https://github.com/kubernetes/kubernetes/blob/52a6ad0acb26/staging/src/k8s.io/client-go/pkg/util/rand/rand.go#L73
  chars = 'bcdfghjklmnpqrstvwxz0123456789'
  index.chars.map { |c| chars.index(c) }.reduce(0) { |v, c| v * chars.length + c }
end

#service?(role_name) ⇒ Boolean

Returns:

  • (Boolean)


126
127
128
129
130
131
# File 'lib/kube_link_generator.rb', line 126

def service?(role_name)
  @client.get_service(role_name, @namespace)
  true
rescue KubeException
  false
end

#this_nameObject



19
20
21
# File 'lib/kube_link_generator.rb', line 19

def this_name
  @spec['job']['name']
end