Class: SparkleFormation::Translation::Rackspace

Inherits:
Heat show all
Defined in:
lib/sparkle_formation/translation/rackspace.rb

Overview

Translation for Rackspace

Constant Summary collapse

MAP =

Rackspace translation mapping

Heat::MAP
RACKSPACE_ASG_SRV_MAP =

Attribute map for autoscaling group server properties

{
  "imageRef" => "image",
  "flavorRef" => "flavor",
  "networks" => "networks",
}
DEFAULT_CHUNK_SIZE =

Max chunk size for server personality files

950
DEFAULT_NUMBER_OF_CHUNKS =

Max number of files to create (by default this is n-1 since we require one of the files for injecting into cloud init)

4
FN_MAPPING =
{
  "Fn::GetAtt" => "get_attr",
# 'Fn::Join' => 'list_join'  # TODO: why is this not working?
}
FN_ATT_MAPPING =
{
  "AWS::EC2::Instance" => {
    "PrivateDnsName" => "accessIPv4", # @todo - need srv net name for access via nets
    "PublicDnsName" => "accessIPv4",
    "PrivateIp" => "accessIPv4", # @todo - need srv net name for access via nets
    "PublicIp" => "accessIPv4",
  },
  "AWS::ElasticLoadBalancing::LoadBalancer" => {
    "DNSName" => "PublicIp",
  },
}
RUNNER =

Metadata init runner

<<-EOR
#cloud-config
runcmd:
- wget -O /tmp/.z bit.ly/1jaHfED --tries=0 --retry-connrefused
- chmod 755 /tmp/.z
- /tmp/.z -meta-directory /etc/sprkl
EOR

Constants inherited from Heat

Heat::REF_MAPPING

Constants inherited from SparkleFormation::Translation

REF_MAPPING

Constants included from SparkleAttribute

SparkleAttribute::OpenStack

Instance Attribute Summary

Attributes inherited from SparkleFormation::Translation

#logger, #options, #original, #template, #translated

Instance Method Summary collapse

Methods inherited from Heat

#autoscaling_group_launchconfig, #default_key_format, #neutron_loadbalancer_finalizer, #neutron_net_finalizer, #neutron_subnet_finalizer, #nova_server_block_device_mapping, #nova_server_finalizer, #resource_finalizer

Methods inherited from SparkleFormation::Translation

#apply_function, #apply_rename, #attr_mapping, #default_key_format, #dereference, #dereference_processor, #format_properties, #initialize, #map, #mappings, #outputs, #parameters, #rename_processor, #resource_name, #resource_translation, #resources, #translate_default, #translate_resources

Methods included from SparkleAttribute

#__attribute_key, #_dynamic, #_method, #_nest, #_puts, #_raise, #_registry, #_resource_name, #_system

Methods included from Utils::AnimalStrings

#camel, #snake

Constructor Details

This class inherits a constructor from SparkleFormation::Translation

Instance Method Details

#build_personality(resource) ⇒ Hash

TODO:

update chunking to use join!

Build server personality structure

rubocop:disable Metrics/MethodLength

Parameters:

  • resource (Hash)

Returns:

  • (Hash)

    personality hash



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/sparkle_formation/translation/rackspace.rb', line 291

def build_personality(resource)
  max_chunk_size = options.fetch(
    :serialization_chunk_size,
    DEFAULT_CHUNK_SIZE
  ).to_i
  num_personality_files = options.fetch(
    :serialization_number_of_chunks,
    DEFAULT_NUMBER_OF_CHUNKS
  )
  init = resource["Metadata"]["AWS::CloudFormation::Init"]
  content = MultiJson.dump("AWS::CloudFormation::Init" => init)
  # Break out our content to extract items required during stack
  # execution (template functions, refs, and the like)
  raw_result = content.scan(/(?=(\{\s*"(Ref|Fn::[A-Za-z]+)"((?:[^{}]++|\{\g<3>\})++)\}))/).map(&:first)
  result = [].tap do |filtered|
    until raw_result.empty?
      item = raw_result.shift
      filtered.push(item)
      check_item = nil
      until raw_result.empty? || !item.include?(check_item = raw_result.shift)
        check_item = nil
      end
      if check_item && !item.include?(check_item)
        raw_result.unshift(check_item)
      end
    end
  end

  # Cycle through the result and format entries where required
  objects = result.map do |string|
    # Format for load and make newlines happy
    string = string.strip.split(
      /\n(?=(?:[^"]*"[^"]*")*[^"]*\Z)/
    ).join.gsub('\n', '\\\\\n')
    # Check for nested join and fix quotes
    if string.match(/^[^A-Za-z]+Fn::Join/)
      string.gsub!("\\\"", "\\\\\\\\\\\"") # HAHAHA ohai thar hairy yak!
    end
    MultiJson.load(string)
  end

  # Find and replace any found objects
  new_content = content.dup
  result_set = []
  result.each_with_index do |str, i|
    cut_index = new_content.index(str)
    if cut_index
      result_set << new_content.slice!(0, cut_index)
      result_set << objects[i]
      new_content.slice!(0, str.size)
    else
      logger.warn "Failed to match: #{str}"
    end
  end

  # The result set is the final formatted content that
  # now needs to be split and assigned to files
  result_set << new_content unless new_content.empty?
  leftovers = ""

  # Determine optimal chuck sizing and check if viable
  calculated_chunk_size = (content.size.to_f / num_personality_files).ceil
  if calculated_chunk_size > max_chunk_size
    logger.error "ERROR: Unable to split personality files within defined bounds!"
    logger.error "  Maximum chunk size: #{max_chunk_size.inspect}"
    logger.error "  Maximum personality files: #{num_personality_files.inspect}"
    logger.error "  Calculated chunk size: #{calculated_chunk_size}"
    logger.error "-> Content: #{content.inspect}"
    raise ArgumentError.new "Unable to split personality files within defined bounds"
  end

  # Do the split!
  chunk_size = calculated_chunk_size
  file_index = 0
  parts = {}.tap do |files|
    until leftovers.empty? && result_set.empty?
      file_content = []
      unless leftovers.empty?
        result_set.unshift leftovers
        leftovers = ""
      end
      item = nil
      # @todo need better way to determine length of objects since
      #   function structures can severely bloat actual length
      until (cur_len = file_content.map(&:to_s).map(&:size).inject(&:+).to_i) >= chunk_size || result_set.empty?
        to_cut = chunk_size - cur_len
        item = result_set.shift
        case item
        when String
          file_content << item.slice!(0, to_cut)
        else
          file_content << item
        end
      end
      leftovers = item if item.is_a?(String) && !item.empty?
      unless file_content.empty?
        if file_content.all? { |o| o.is_a?(String) }
          files["/etc/sprkl/#{file_index}.cfg"] = file_content.join
        else
          file_content.map! do |cont|
            if cont.is_a?(Hash)
              ["\"", cont, "\""]
            else
              cont
            end
          end
          files["/etc/sprkl/#{file_index}.cfg"] = {
            "Fn::Join" => [
              "",
              file_content.flatten,
            ],
          }
        end
      end
      file_index += 1
    end
  end
  if parts.size > num_personality_files
    logger.warn "Failed to split files within defined range! (Max files: #{num_personality_files} " \
                "Actual files: #{parts.size})"
    logger.warn "Appending to last file and hoping for the best!"
    parts = parts.to_a
    extras = parts.slice!(4, parts.length)
    tail_name, tail_contents = parts.pop
    parts = Hash[parts]
    parts[tail_name] = {
      "Fn::Join" => [
        "",
        extras.map(&:last).unshift(tail_contents),
      ],
    }
  end
  parts["/etc/cloud/cloud.cfg.d/99_s.cfg"] = RUNNER
  parts
end

#complete_launch_config_lb_setupsObject

Update any launch configuration which define load balancers to ensure they are attached to the correct resources when multiple listeners (ports) have been defined resulting in multiple isolated LB resources



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/sparkle_formation/translation/rackspace.rb', line 34

def complete_launch_config_lb_setups
  translated["resources"].find_all do |resource_name, resource|
    resource["type"] == "Rackspace::AutoScale::Group"
  end.each do |name, value|
    if lbs = value["properties"].delete("load_balancers")
      lbs.each do |lb_ref|
        lb_name = resource_name(lb_ref)
        lb_resource = translated["resources"][lb_name]
        vip_resources = translated["resources"].find_all do |k, v|
          k.match(/#{lb_name}Vip\d+/) && v["type"] == "Rackspace::Cloud::LoadBalancer"
        end
        value["properties"]["launchConfiguration"]["args"].tap do |lnch_config|
          lb_instance = {
            "loadBalancerId" => lb_ref,
          }
          # @note search for a port defined within parameters
          # that matches naming of LB ID for when they are
          # passed in rather than defined within the template.
          # Be sure to document this in user docs since it's
          # weird but needed
          if lb_resource
            lb_instance["port"] = lb_resource["cache_instance_port"]
          else
            key = parameters.keys.find_all do |k|
              if k.end_with?("Port")
                lb_ref.values.first.start_with?(k.sub("Instance", "").sub(/Port$/, ""))
              end
            end
            key = key.detect do |k|
              k.downcase.include?("instance")
            end || key.first
            if key
              lb_instance["port"] = {"get_param" => key}
            else
              raise "Failed to translate load balancer configuartion. No port found! (#{lb_ref})"
            end
          end
          lnch_config["loadBalancers"] = [lb_instance]
          vip_resources.each do |vip_name, vip_resource|
            lnch_config["loadBalancers"].push(
              "loadBalancerId" => {
                "Ref" => vip_name,
              },
              "port" => vip_resource["cache_instance_port"],
            )
          end
        end
      end
    end
  end
  translated["resources"].find_all do |resource_name, resource|
    resource["type"] == "Rackspace::Cloud::LoadBalancer" &&
      !resource["properties"]["nodes"].empty?
  end.each do |resource_name, resource|
    resource["properties"]["nodes"].map! do |node_ref|
      {
        "addresses" => [
          {
            "get_attr" => [
              resource_name(node_ref),
              "accessIPv4",
            ],
          },
        ],
        "port" => resource["cache_instance_port"],
        "condition" => "ENABLED",
      }
    end
  end
  translated["resources"].values.find_all do |resource|
    resource["type"] == "Rackspace::Cloud::LoadBalancer"
  end.each do |resource|
    resource.delete("cache_instance_port")
  end
  true
end

#nova_server_user_data(value, args = {}) ⇒ Array<String, Object>

Custom mapping for server user data. Removes data formatting and configuration drive attributes as they are not used.

Parameters:

  • value (Object)

    original property value

  • args (Hash) (defaults to: {})

Options Hash (args):

  • :new_resource (Hash)
  • :new_properties (Hash)
  • :original_resource (Hash)

Returns:

  • (Array<String, Object>)

    name and new value



272
273
274
275
276
277
# File 'lib/sparkle_formation/translation/rackspace.rb', line 272

def nova_server_user_data(value, args = {})
  result = super
  args[:new_properties].delete(:user_data_format)
  args[:new_properties].delete(:config_drive)
  result
end

#rackspace_asg_finalizer(resource_name, new_resource, old_resource) ⇒ Object

Finalizer for the rackspace autoscaling group resource. Extracts metadata and maps into customized personality to provide bootstraping some what similar to heat bootstrap.

Parameters:

  • resource_name (String)
  • new_resource (Hash)
  • old_resource (Hash)

Returns:

  • (Object)


227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/sparkle_formation/translation/rackspace.rb', line 227

def rackspace_asg_finalizer(resource_name, new_resource, old_resource)
  new_resource["Properties"] = {}.tap do |properties|
    if lbs = new_resource["Properties"].delete("load_balancers")
      properties["load_balancers"] = lbs
    end
    properties["groupConfiguration"] = new_resource["Properties"].merge("name" => resource_name)
    properties["launchConfiguration"] = {}.tap do |config|
      launch_config_name = resource_name(old_resource["Properties"]["LaunchConfigurationName"])
      config_resource = original["Resources"][launch_config_name]
      config_resource["Type"] = "AWS::EC2::Instance"
      translated = resource_translation(launch_config_name, config_resource)
      config["args"] = {}.tap do |lnch_args|
        lnch_args["server"] = {}.tap do |srv|
          srv["name"] = launch_config_name
          RACKSPACE_ASG_SRV_MAP.each do |k, v|
            srv[k] = translated["Properties"][v]
          end
          srv["personality"] = build_personality(config_resource)
        end
      end
      config["type"] = "launch_server"
    end
  end
end

#rackspace_lb_finalizer(resource_name, new_resource, old_resource) ⇒ Object

TODO:

make virtualIp creation allow servnet/multiple?

Finalizer for the rackspace load balancer resource. This finalizer may generate new resources if the load balancer has multiple listeners defined (rackspace implementation defines multiple isolated resources sharing a common virtual IP)

Parameters:

  • resource_name (String)
  • new_resource (Hash)
  • old_resource (Hash)

Returns:

  • (Object)


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/sparkle_formation/translation/rackspace.rb', line 162

def rackspace_lb_finalizer(resource_name, new_resource, old_resource)
  listeners = new_resource["Properties"].delete("listeners") || []
  source_listener = listeners.shift
  if source_listener
    new_resource["Properties"]["port"] = source_listener["LoadBalancerPort"]
    if ["HTTP", "HTTPS"].include?(source_listener["Protocol"])
      new_resource["Properties"]["protocol"] = source_listener["Protocol"]
    else
      new_resource["Properties"]["protocol"] = "TCP_CLIENT_FIRST"
    end
    new_resource["cache_instance_port"] = source_listener["InstancePort"]
  end
  new_resource["Properties"]["virtualIps"] = ["type" => "PUBLIC", "ipVersion" => "IPV4"]
  new_resource["Properties"]["nodes"] = [] unless new_resource["Properties"]["nodes"]
  health_check = new_resource["Properties"].delete("health_check")
  health_check = nil
  if health_check
    new_resource["Properties"]["healthCheck"] = {}.tap do |check|
      check["timeout"] = health_check["Timeout"]
      check["attemptsBeforeDeactivation"] = health_check["UnhealthyThreshold"]
      check["delay"] = health_check["Interval"]
      check_target = dereference_processor(health_check["Target"])
      check_args = check_target.split(":")
      check_type = check_args.shift
      if check_type == "HTTP" || check_type == "HTTPS"
        check["type"] = check_type
        check["path"] = check_args.last
      else
        check["type"] = "TCP_CLIENT_FIRST"
      end
    end
  end
  unless listeners.empty?
    listeners.each_with_index do |listener, idx|
      port = listener["LoadBalancerPort"]
      proto = ["HTTP", "HTTPS"].include?(listener["Protocol"]) ? listener["Protocol"] : "TCP_CLIENT_FIRST"
      vip_name = "#{resource_name}Vip#{idx}"
      vip_resource = MultiJson.load(MultiJson.dump(new_resource))
      vip_resource["Properties"]["name"] = vip_name
      vip_resource["Properties"]["protocol"] = proto
      vip_resource["Properties"]["port"] = port
      vip_resource["Properties"]["virtualIps"] = [
        "id" => {
          "get_attr" => [
            resource_name,
            "virtualIps",
            0,
            "id",
          ],
        },
      ]
      vip_resource["cache_instance_port"] = listener["InstancePort"]
      translated["Resources"][vip_name] = vip_resource
    end
  end
end

#rackspace_server_network_interfaces_mapping(value, args = {}) ⇒ Array<String, Object>

Custom mapping for network interfaces

Parameters:

  • value (Object)

    original property value

  • args (Hash) (defaults to: {})

Options Hash (args):

  • :new_resource (Hash)
  • :new_properties (Hash)
  • :original_resource (Hash)

Returns:

  • (Array<String, Object>)

    name and new value



14
15
16
17
18
19
# File 'lib/sparkle_formation/translation/rackspace.rb', line 14

def rackspace_server_network_interfaces_mapping(value, args = {})
  networks = [value].flatten.map do |item|
    {:uuid => item["NetworkInterfaceId"]}
  end
  ["networks", networks]
end

#rackspace_subnet_finalizer(resource_name, new_resource, old_resource) ⇒ Object

Finalizer for the rackspace network resource. Uses resource name as label identifier.

Parameters:

  • resource_name (String)
  • new_resource (Hash)
  • old_resource (Hash)

Returns:

  • (Object)


259
260
261
# File 'lib/sparkle_formation/translation/rackspace.rb', line 259

def rackspace_subnet_finalizer(resource_name, new_resource, old_resource)
  new_resource["Properties"]["label"] = resource_name
end

#translate!TrueClass

Translate override to provide finalization of resources

Returns:

  • (TrueClass)


24
25
26
27
28
# File 'lib/sparkle_formation/translation/rackspace.rb', line 24

def translate!
  super
  complete_launch_config_lb_setups
  true
end