Class: HybridPlatformsConductor::Topographer

Inherits:
Object
  • Object
show all
Includes:
LoggerHelpers
Defined in:
lib/hybrid_platforms_conductor/topographer.rb,
lib/hybrid_platforms_conductor/topographer/plugin.rb,
lib/hybrid_platforms_conductor/topographer/plugins/svg.rb,
lib/hybrid_platforms_conductor/topographer/plugins/json.rb,
lib/hybrid_platforms_conductor/topographer/plugins/graphviz.rb

Overview

Class giving an API to parse the graph of the TI network

Defined Under Namespace

Modules: Plugins Classes: Plugin

Constant Summary

Constants included from LoggerHelpers

LoggerHelpers::LEVELS_MODIFIERS, LoggerHelpers::LEVELS_TO_STDERR

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from LoggerHelpers

#err, #init_loggers, #log_component=, #log_debug?, #log_level=, #out, #section, #set_loggers_format, #stderr_device, #stderr_device=, #stderr_displayed?, #stdout_device, #stdout_device=, #stdout_displayed?, #stdouts_to_s, #with_progress_bar

Constructor Details

#initialize(logger: Logger.new(STDOUT), logger_stderr: Logger.new(STDERR), nodes_handler: NodesHandler.new, json_dumper: JsonDumper.new, config: {}) ⇒ Topographer

Constructor

Parameters
  • logger (Logger): Logger to be used [default = Logger.new(STDOUT)]

  • logger_stderr (Logger): Logger to be used for stderr [default = Logger.new(STDERR)]

  • nodes_handler (NodesHandler): The nodes handler to be used [default = NodesHandler.new]

  • json_dumper (JsonDumper): The JSON Dumper to be used [default = JsonDumper.new]

  • config (Hash<Symbol,Object>): Some configuration parameters that can override defaults. [default = {}] Here are the possible keys:

    • json_files_dir (String): Directory from which JSON files are taken. [default = nodes_json]

    • connections_max_level (Integer or nil): Number maximal of recursive passes to get hostname connections (nil means no limit). [default = nil]



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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 81

def initialize(logger: Logger.new(STDOUT), logger_stderr: Logger.new(STDERR), nodes_handler: NodesHandler.new, json_dumper: JsonDumper.new, config: {})
  init_loggers(logger, logger_stderr)
  @nodes_handler = nodes_handler
  @json_dumper = json_dumper
  @config = Topographer.default_config.merge(config)
  # Get the metadata of each node, per hostname
  # Hash<String,Hash>
  @node_metadata = {}
  # Know for each IP what is the hostname it belongs to
  # Hash<String,String>
  @ips_to_host = {}
  # Get the connection information per node name. A node reprensents 1 element that can be connected to other elements in the graph.
  # Hash< String, Hash<Symbol,Object> >
  # Here are the possible information keys:
  # * *type* (Symbol): Type of the node. Can be one of: :node, :cluster, :unknown.
  # * *connections* (Hash< String, Array<String> >): List of labels per connected node.
  # * *includes* (Array<String>): List of nodes included in this one.
  # * *includes_proc* (Proc): Proc called to know if a node belongs to this cluster [only if type == :cluster]:
  #   * Parameters::
  #     * *node_name* (String): Name of the node for the inclusion test
  #   * Result::
  #     * Boolean: Does the node belongs to this cluster?
  # * *ipv4* (IPAddress::IPv4): Corresponding IPv4 object [only if type == :node and a private IP exists, or type == :unknown, or type == :cluster and the cluster name is an IP range]
  @nodes_graph = {}

  # Default values
  @from_hosts = []
  @to_hosts = []
  @outputs = []
  @skip_run = false

  # Parse plugins
  @plugins = Hash[Dir.
    glob("#{File.dirname(__FILE__)}/topographer/plugins/*.rb").
    map do |file_name|
      plugin_name = File.basename(file_name)[0..-4].to_sym
      require file_name
      [
        plugin_name,
        Topographer::Plugins.const_get(plugin_name.to_s.split('_').collect(&:capitalize).join.to_sym)
      ]
    end]

  @ips_to_host = known_ips.clone

  # Fill info from the metadata
   = %i[
    description
    physical_node
    private_ips
  ]
  @nodes_handler. @nodes_handler.known_nodes, 
  @nodes_handler.known_nodes.each do |hostname|
    @node_metadata[hostname] = Hash[.map { |property| [property, @nodes_handler.(hostname, property)] }]
  end

  # Small cache of hostnames used a lot to parse JSON
  @known_nodes = Hash[@nodes_handler.known_nodes.map { |hostname| [hostname, nil] }]
  # Cache of objects being used a lot in parsing for performance
  @non_word_regexp = /\W+/
  @ip_regexp = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/(\d{1,2})|[^\d\/]|$)/
  # Cache of ignored IPs
  @ips_ignored = {}
end

Instance Attribute Details

#configObject (readonly)

Some getters that can be useful for clients of the Topographer



69
70
71
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 69

def config
  @config
end

#node_metadataObject (readonly)

Some getters that can be useful for clients of the Topographer



69
70
71
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 69

def 
  @node_metadata
end

#nodes_graphObject (readonly)

Some getters that can be useful for clients of the Topographer



69
70
71
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 69

def nodes_graph
  @nodes_graph
end

Class Method Details

.default_configObject

Give a default configuration

Result
  • Hash<Symbol,Object>: Default configuration



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 19

def self.default_config
  {
    # Directory from which the complete JSON files are to be read
    json_files_dir: 'nodes_json',
    # JSON keys to ignore when reading complete JSON files. Only leafs of this tree structure are ignored.
    ignore_json_keys: {
      # This should only duplicate the real configuration from the recipes, and it adds a lot of IP ranges that can be ignored.
      'network' => nil,
      # Contains simple network definition. Not a connection in itself.
      'policy_xae_outproxy' => { 'local_network' => nil },
      # Contains DNS entries. Not a connection in itself.
      'policy_xae_xx_cdh' => { 'dns' => nil },
      # This contains firewall rules, therefore representing who connects on the host, and not who the host connects to.
      'policy_xae_xx_iptables' => nil,
      # Contains the allowed network range. Not a connection in itself.
      'postfix' => { 'main' => { 'mynetworks' => nil } },
      # This contains sometime IP addresses in the key comments
      'site_directory' => nil,
      # This contains firewall rules, therefore representing who connects on the host, and not who the host connects to.
      'site_iptables' => nil,
      # This contains some user names having IP addresses inside
      'site_xx_roles' => nil,
      # This stores routes for all Proxmox instances.
      'pve' => { 'vlan' => { 'routes' => nil } }
    },
    # JSON keys to ignore when reading complete JSON files, whatever their position
    ignore_any_json_keys: [
      # Those contain cache of MAC addresses to IP addresses
      'arp',
      # Those contain broadcast IP addresses
      'broadcast',
      # Those contain firewall rules, therefore representing who connects on the host, and not who the host connects to.
      'firewall',
      # Those contain version numbers with same format as IP addresses
      'version'
    ],
    # IPs to ignore while parsing complete JSON files
    ignore_ips: [
      /^0\./,
      /^127\./,
      /^255\./
    ],
    # Maximum level of recursion while building the graph of connected nodes (nil = no limit).
    connections_max_level: nil,
    # Maximum label length for a link
    max_link_label_length: 128
  }
end

Instance Method Details

#ancestor_nodes(nodes_list) ⇒ Object

Return the list of nodes and ancestors of a given list of nodes, recursively. An ancestor of a node is another node connected to it, or to a group including it. An ancestor of a node can be:

  • Another node connected to it.

  • Another node including it.

Parameters
  • nodes_list (Array<String>): List of nodes for which we look for ancestors.

Result
  • Array<String>: List of ancestor nodes.



321
322
323
324
325
326
327
328
329
330
331
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 321

def ancestor_nodes(nodes_list)
  ancestor_nodes_list = []
  @nodes_graph.each do |node_name, node_info|
    ancestor_nodes_list << node_name if !nodes_list.include?(node_name) && (!(node_info[:connections].keys & nodes_list).empty? || !(node_info[:includes] & nodes_list).empty?)
  end
  if ancestor_nodes_list.empty?
    nodes_list
  else
    ancestor_nodes(nodes_list + ancestor_nodes_list)
  end
end

#available_pluginsObject

Get the list of available plugins

Result
  • Array<Symbol>: List of plugins



221
222
223
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 221

def available_plugins
  @plugins.keys
end

#children_nodes(nodes_list) ⇒ Object

Return the list of nodes and children of a given list of nodes, recursively. A child of a node is another node connected to it, or to a group including it. A child of a node can be:

  • Another node that it connects to.

  • Another node that it includes.

Parameters
  • nodes_list (Array<String>): List of nodes for which we look for children.

Result
  • Array<String>: List of children nodes.



343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 343

def children_nodes(nodes_list)
  children_nodes_list = []
  nodes_list.each do |node_name|
    children_nodes_list.concat(@nodes_graph[node_name][:connections].keys + @nodes_graph[node_name][:includes])
  end
  children_nodes_list.uniq!
  new_children_nodes = children_nodes_list - nodes_list
  if new_children_nodes.empty?
    children_nodes_list
  else
    children_nodes(children_nodes_list)
  end
end

#cluster_nodesObject

Return the list of nodes that are clusters

Result
  • Array<String>: List of cluster nodes



361
362
363
364
365
366
367
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 361

def cluster_nodes
  cluster_nodes_list = []
  @nodes_graph.each do |node_name, node_info|
    cluster_nodes_list << node_name if node_info[:type] == :cluster
  end
  cluster_nodes_list
end

#collapse_nodes(nodes_list) ⇒ Object

Collapse a given list of nodes.

Parameters
  • nodes_list (Array<String>): List of nodes to collapse



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 268

def collapse_nodes(nodes_list)
  nodes_list.each do |node_name_to_collapse|
    included_nodes = @nodes_graph[node_name_to_collapse][:includes]
    # First collapse its included nodes if any
    collapse_nodes(included_nodes)
    # Then collapse this one
    collapsed_connections = {}
    included_nodes.each do |included_node_name|
      collapsed_connections.merge!(@nodes_graph[included_node_name][:connections]) { |_connected_node, labels1, labels2| (labels1 + labels2).uniq }
    end
    @nodes_graph[node_name_to_collapse][:connections] = collapsed_connections
    @nodes_graph[node_name_to_collapse][:includes] = []
    replace_nodes(included_nodes, node_name_to_collapse)
  end
end

#define_clusters_ip_24Object

Define clusters of ips with 24 bits ranges.



301
302
303
304
305
306
307
308
309
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 301

def define_clusters_ip_24
  @nodes_graph.keys.each do |node_name|
    if @nodes_graph[node_name][:type] == :node && !@node_metadata[node_name][:private_ips].nil? && !@node_metadata[node_name][:private_ips].empty?
      ip_24 = "#{@node_metadata[node_name][:private_ips].first.split('.')[0..2].join('.')}.0/24"
      @nodes_graph[ip_24] = ip_range_graph_info(ip_24) unless @nodes_graph.key?(ip_24)
      @nodes_graph[ip_24][:includes] << node_name unless @nodes_graph[ip_24][:includes].include?(node_name)
    end
  end
end

#description_for(node_name) ⇒ Object

Get the description of a given node

Parameters
  • node_name (String): Node name

Result
  • String: Node description, or nil if none



545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 545

def description_for(node_name)
  require 'byebug'
  byebug if node_name == 'xaesbghad51'
  case @nodes_graph[node_name][:type]
  when :node
    @node_metadata[node_name][:description]
  when :cluster
    nil
  when :unknown
    nil
  end
end

#dump_outputsObject

Dump the graph in the desired outputs



209
210
211
212
213
214
215
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 209

def dump_outputs
  @outputs.each do |(format, file_name)|
    section "Write #{format} file #{file_name}" do
      write_graph(file_name, format)
    end
  end
end

#filter_in_nodes(nodes_list) ⇒ Object

Remove from the graph any node that is not part of a given list

Parameters
  • nodes_list (Array<String>): List of nodes to keep



373
374
375
376
377
378
379
380
381
382
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 373

def filter_in_nodes(nodes_list)
  new_nodes_graph = {}
  @nodes_graph.each do |node_name, node_info|
    new_nodes_graph[node_name] = node_info.merge(
      connections: node_info[:connections].select { |connected_hostname, _labels| nodes_list.include?(connected_hostname) },
      includes: node_info[:includes] & nodes_list
    ) if nodes_list.include?(node_name)
  end
  @nodes_graph = new_nodes_graph
end

#filter_out_nodes(nodes_list) ⇒ Object

Remove from the graph any node that is part of a given list

Parameters
  • nodes_list (Array<String>): List of nodes to remove



388
389
390
391
392
393
394
395
396
397
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 388

def filter_out_nodes(nodes_list)
  new_nodes_graph = {}
  @nodes_graph.each do |node_name, node_info|
    new_nodes_graph[node_name] = node_info.merge(
      connections: node_info[:connections].select { |connected_hostname, _labels| !nodes_list.include?(connected_hostname) },
      includes: node_info[:includes] - nodes_list
    ) unless nodes_list.include?(node_name)
  end
  @nodes_graph = new_nodes_graph
end

#force_cluster_strict_hierarchyObject

Make sure clusters follow a strict hierarchy and that 1 node belongs to at most 1 cluster.



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 425

def force_cluster_strict_hierarchy
  # Find the nodes belonging to several clusters.
  loop do
    # First cluster found each node name
    # Hash<String, String >
    cluster_per_node = {}
    conflicting_clusters = nil
    @nodes_graph.each do |node_name, node_info|
      node_info[:includes].each do |included_node_name|
        if cluster_per_node.key?(included_node_name)
          # Found a conflict between 2 clusters
          conflicting_clusters = [node_name, cluster_per_node[included_node_name]]
          log_error "Node #{included_node_name} found in both clusters #{node_name} and #{cluster_per_node[included_node_name]}"
          break
        else
          cluster_per_node[included_node_name] = node_name
        end
      end
      break unless conflicting_clusters.nil?
    end
    if conflicting_clusters.nil?
      break
    else
      # We have conflicting clusters to resolve
      cluster_1, cluster_2 = conflicting_clusters
      c1_belongs_to_c2 = @nodes_graph[cluster_1][:includes].all? { |cluster_1_node_name| @nodes_graph[cluster_2][:includes_proc].call(cluster_1_node_name) }
      c2_belongs_to_c1 = @nodes_graph[cluster_2][:includes].all? { |cluster_2_node_name| @nodes_graph[cluster_1][:includes_proc].call(cluster_2_node_name) }
      if c1_belongs_to_c2
        if c2_belongs_to_c1
          # Both clusters have the same nodes
          if @nodes_graph[cluster_1][:includes_proc].call(cluster_2)
            @nodes_graph[cluster_2][:includes] = (@nodes_graph[cluster_1][:includes] + @nodes_graph[cluster_2][:includes]).uniq
            @nodes_graph[cluster_1][:includes] = [cluster_2]
          else
            @nodes_graph[cluster_1][:includes] = (@nodes_graph[cluster_1][:includes] + @nodes_graph[cluster_2][:includes]).uniq
            @nodes_graph[cluster_2][:includes] = [cluster_1]
          end
        else
          # All nodes of cluster_1 belong to cluster_2, but some nodes of cluster_2 don't belong to cluster_1
          @nodes_graph[cluster_2][:includes] = @nodes_graph[cluster_2][:includes] - @nodes_graph[cluster_1][:includes] + [cluster_1]
        end
      elsif c2_belongs_to_c1
        # All nodes of cluster_2 belong to cluster_1, but some nodes of cluster_1 don't belong to cluster_2
        @nodes_graph[cluster_1][:includes] = @nodes_graph[cluster_1][:includes] - @nodes_graph[cluster_2][:includes] + [cluster_2]
      else
        # cluster_1 and cluster_2 have to be merged
        new_cluster_name = "#{cluster_1}_&_#{cluster_2}"
        # Store thos proc in those variables as the cluster_1 and cluster_2 references are going to be removed
        includes_proc_1 = @nodes_graph[cluster_1][:includes_proc]
        includes_proc_2 = @nodes_graph[cluster_2][:includes_proc]
        @nodes_graph[new_cluster_name] = {
          type: :cluster,
          includes: (@nodes_graph[cluster_1][:includes] + @nodes_graph[cluster_2][:includes]).uniq,
          connections: @nodes_graph[cluster_1][:connections].merge!(@nodes_graph[cluster_2][:connections]) { |_connected_node, labels1, labels2| (labels1 + labels2).uniq },
          includes_proc: proc do |hostname|
            includes_proc_1.call(hostname) || includes_proc_2.call(hostname)
          end
        }
        replace_nodes([cluster_1, cluster_2], new_cluster_name)
      end
    end
  end
end

#get_json_filesObject

Generate the JSON files to be used



200
201
202
203
204
205
206
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 200

def get_json_files
  unless @skip_run
    @json_dumper.dump_dir = @config[:json_files_dir]
    # Generate all the jsons, even if 1 hostname is given, as it might be useful for the rest of the graph.
    @json_dumper.dump_json_for(@nodes_handler.known_nodes)
  end
end

#graph_for(hostnames) ⇒ Object

Add to the graph a given set of hostnames and their connected nodes.

Parameters
  • hostnames (Array<String>): List of hostnames



229
230
231
232
233
234
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 229

def graph_for(hostnames)
  # Parse connections from JSON files
  hostnames.each do |hostname|
    parse_connections_for(hostname, @config[:connections_max_level])
  end
end

#graph_for_nodes_lists(nodes_lists, only_add_cluster: false) ⇒ Object

Add to the graph a given set of nodes lists and their connected nodes.

Parameters
  • nodes_lists (Array<String>): List of nodes lists

  • only_add_cluster (Boolean): If true, then don’t add missing nodes from this graph to the graph [default = false]



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 241

def graph_for_nodes_lists(nodes_lists, only_add_cluster: false)
  nodes_lists.each do |nodes_list|
    hosts_list = @nodes_handler.select_nodes(@nodes_handler.nodes_from_list(nodes_list))
    if only_add_cluster
      # Select only the hosts list we know about
      hosts_list.select! { |hostname| @nodes_graph.key?(hostname) }
    else
      # Parse JSON for all the hosts of this cluster
      hosts_list.each do |hostname|
        parse_connections_for(hostname, @config[:connections_max_level])
      end
    end
    @nodes_graph[nodes_list] = {
      type: :cluster,
      connections: {},
      includes: [],
      includes_proc: proc { |node_name| hosts_list.include?(node_name) }
    } unless @nodes_graph.key?(nodes_list)
    @nodes_graph[nodes_list][:includes].concat(hosts_list)
    @nodes_graph[nodes_list][:includes].uniq!
  end
end

#is_node_cluster?(node_name) ⇒ Boolean

Is the node represented as a cluster?

Parameters
  • node_name (String): Node name

Result
  • Boolean: Is the node represented as a cluster?

Returns:

  • (Boolean)


495
496
497
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 495

def is_node_cluster?(node_name)
  @nodes_graph[node_name][:type] == :cluster || !@nodes_graph[node_name][:includes].empty?
end

#is_node_physical?(node_name) ⇒ Boolean

Is the node a physical node?

Parameters
  • node_name (String): Node name

Result
  • Boolean: Is the node a physical node?

Returns:

  • (Boolean)


505
506
507
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 505

def is_node_physical?(node_name)
  @nodes_graph[node_name][:type] == :node && @node_metadata[node_name][:physical_node]
end

#options_parse(options_parser) ⇒ Object

Complete an option parser with ways to tune the topographer

Parameters
  • options_parser (OptionParser): The option parser to complete



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 150

def options_parse(options_parser)
  from_hosts_opts_parser = OptionParser.new do |opts|
    @nodes_handler.options_parse_nodes_selectors(opts, @from_hosts)
  end
  to_hosts_opts_parser = OptionParser.new do |opts|
    @nodes_handler.options_parse_nodes_selectors(opts, @to_hosts)
  end
  options_parser.separator ''
  options_parser.separator 'Topographer options:'
  options_parser.on('-F', '--from HOSTS_OPTIONS', 'Specify options for the set of nodes to start from (enclose them with ""). Default: all nodes. HOSTS_OPTIONS follows the following:', *from_hosts_opts_parser.to_s.split("\n")[3..-1]) do |hosts_options|
    args = hosts_options.split(' ')
    from_hosts_opts_parser.parse!(args)
    raise "Unknown --from options: #{args.join(' ')}" unless args.empty?
  end
  options_parser.on('-k', '--skip-run', "Skip the actual gathering of JSON node files. If set, the current files in #{@config[:json_files_dir]} will be used.") do
    @skip_run = true
  end
  options_parser.on('-p', '--output FORMAT:FILE_NAME', "Specify a format and file name. Can be used several times. FORMAT can be one of #{available_plugins.sort.join(', ')}. Ex.: graphviz:graph.gv") do |output|
    format_str, file_name = output.split(':')
    format = format_str.to_sym
    raise "Unknown format: #{format}." unless available_plugins.include?(format)
    @outputs << [format, file_name]
  end
  options_parser.on('-T', '--to HOSTS_OPTIONS', 'Specify options for the set of nodes to get to (enclose them with ""). Default: all nodes. HOSTS_OPTIONS follows the following:', *to_hosts_opts_parser.to_s.split("\n")[3..-1]) do |hosts_options|
    args = hosts_options.split(' ')
    to_hosts_opts_parser.parse!(args)
    raise "Unknown --to options: #{args.join(' ')}" unless args.empty?
  end
end

#remove_empty_clustersObject

Remove empty clusters



292
293
294
295
296
297
298
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 292

def remove_empty_clusters
  loop do
    empty_clusters = @nodes_graph.keys.select { |node_name| @nodes_graph[node_name][:type] == :cluster && @nodes_graph[node_name][:includes].empty? }
    break if empty_clusters.empty?
    filter_out_nodes(empty_clusters)
  end
end

#remove_self_connectionsObject

Remove self connections.



285
286
287
288
289
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 285

def remove_self_connections
  @nodes_graph.each do |node_name, node_info|
    node_info[:connections].delete_if { |connected_node_name, _labels| connected_node_name == node_name }
  end
end

#replace_nodes(nodes_to_be_replaced, replacement_node) ⇒ Object

Replace a list of nodes by a given node.

Parameters
  • nodes_to_be_replaced (Array<String>): Nodes to be replaced

  • replacement_node (String): Node that is used for replacement



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 404

def replace_nodes(nodes_to_be_replaced, replacement_node)
  # Delete references to the nodes to be replaced
  @nodes_graph.delete_if { |node_name, _node_info| nodes_to_be_replaced.include?(node_name) }
  # Change any connection or inclusions using nodes to be replaced
  @nodes_graph.each do |node_name, node_info|
    node_info[:includes] = node_info[:includes].map { |included_node_name| nodes_to_be_replaced.include?(included_node_name) ? replacement_node : included_node_name }.uniq
    new_connections = {}
    node_info[:connections].each do |connected_node_name, labels|
      if nodes_to_be_replaced.include?(connected_node_name)
        new_connections[replacement_node] = [] unless new_connections.key?(replacement_node)
        new_connections[replacement_node].concat(labels)
        new_connections[replacement_node].uniq!
      else
        new_connections[connected_node_name] = labels
      end
    end
    node_info[:connections] = new_connections
  end
end

#resolve_from_toObject

Resolve the from and to hosts descriptions

Result
  • Array<String>: The from hostnames

  • Array<String>: The to hostnames



190
191
192
193
194
195
196
197
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 190

def resolve_from_to
  @from_hosts << { all: true } if @from_hosts.empty?
  @to_hosts << { all: true } if @to_hosts.empty?
  [
    @nodes_handler.select_nodes(@from_hosts),
    @nodes_handler.select_nodes(@to_hosts)
  ]
end

#title_for(node_name) ⇒ Object

Get the title of a given node

Parameters
  • node_name (String): Node name

Result
  • String: Node title



528
529
530
531
532
533
534
535
536
537
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 528

def title_for(node_name)
  case @nodes_graph[node_name][:type]
  when :node
    "#{node_name} - #{@node_metadata[node_name][:private_ips].nil? || @node_metadata[node_name][:private_ips].empty? ? 'No IP' : @node_metadata[node_name][:private_ips].first}"
  when :cluster
    "#{node_name} (#{@nodes_graph[node_name][:includes].size} nodes)"
  when :unknown
    "#{node_name} - Unknown node"
  end
end

#validate_paramsObject

Validate that parsed parameters are valid



181
182
183
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 181

def validate_params
  raise 'No output defined. Please use --output option.' if @outputs.empty?
end

#write_graph(file_name, output_format) ⇒ Object

Output the graph to a given file at a given format

Parameters
  • file_name (String): File name to output to.

  • output_format (Symbol): Output format to use (should be part of the plugins).



514
515
516
517
518
519
520
# File 'lib/hybrid_platforms_conductor/topographer.rb', line 514

def write_graph(file_name, output_format)
  if @plugins.key?(output_format)
    @plugins[output_format].new(self).write_graph(file_name)
  else
    raise "Unknown topographer plugin #{output_format}"
  end
end