Module: Scooter::HttpDispatchers::Classifier

Includes:
V1, Utilities
Included in:
ConsoleDispatcher
Defined in:
lib/scooter/httpdispatchers/classifier.rb,
lib/scooter/httpdispatchers/classifier/v1/v1.rb

Overview

Methods added here are not representative of endpoints, but are more generalized to be helpers to to acquire/transform data, such as getting the uuid of a node group based on the name. Be cautious about using these methods if you are utilizing a dispatcher with credentials; the user is not guaranteed to have privileges for all the methods defined here, or the user may not be signed in. If you have a method defined here that is using the connection object directly, you should probably be using a method defined in the version module instead.

Defined Under Namespace

Modules: V1

Constant Summary collapse

Rootuuid =
'00000000-0000-4000-8000-000000000000'

Instance Method Summary collapse

Methods included from V1

#create_node_group, #delete_node_group, #get_list_of_classes, #get_list_of_environments, #get_list_of_node_groups, #get_list_of_nodes, #get_node_group, #import_hierarchy, #pin_nodes, #replace_node_group, #unpin_nodes, #update_classes, #update_node_group

Instance Method Details

#classes_match?(other_classes, self_classes = nil) ⇒ Boolean

Check to see if all classes match between two query responses

Parameters:

  • other_classes (Object)
    • response from get_list_of_classes

  • self_classes (Object) (defaults to: nil)
    • response from get_list_of_classes

Returns:

  • (Boolean)


355
356
357
358
# File 'lib/scooter/httpdispatchers/classifier.rb', line 355

def classes_match?(other_classes, self_classes=nil)
  self_classes = get_list_of_classes if self_classes.nil?
  return other_classes == self_classes
end

#classifier_database_matches_self?(replica_host) ⇒ Boolean

Used to compare replica classifier to master. Raises exception if it does not match.

Parameters:

  • host_name (String)

Returns:

  • (Boolean)


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
# File 'lib/scooter/httpdispatchers/classifier.rb', line 309

def classifier_database_matches_self?(replica_host)
  original_host_name = host.host_hash[:vmhostname]
  begin
    host.host_hash[:vmhostname] = replica_host.hostname

    other_nodes        = get_list_of_nodes
    other_classes      = get_list_of_classes
    other_environments = get_list_of_environments
    other_groups       = get_list_of_node_groups
  ensure
    host.host_hash[:vmhostname] = original_host_name
  end

  self_nodes        = get_list_of_nodes
  self_classes      = get_list_of_classes
  self_environments = get_list_of_environments
  self_groups       = get_list_of_node_groups

  nodes_match        = nodes_match?(other_nodes, self_nodes)
  classes_match      = classes_match?(other_classes, self_classes)
  environments_match = environments_match?(other_environments, self_environments)
  groups_match       = groups_match?(other_groups, self_groups)

  errors = ''
  errors << "Nodes do not match\r\n" unless nodes_match
  errors << "Classes do not match\r\n" unless classes_match
  errors << "Environments do not match\r\n" unless environments_match
  errors << "Groups do not match\r\n" unless groups_match

  host.logger.warn(errors.chomp) unless errors.empty?
  errors.empty?
end

#create_new_node_group_model(options = {}) ⇒ Object Also known as: generate_node_group

This takes an optional hash of node group parameters, and auto-fills any required keys for node group generation. It returns the response body from the server.



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
# File 'lib/scooter/httpdispatchers/classifier.rb', line 87

def create_new_node_group_model(options={})
  # name, classes, parent are the only required keys
  name               = options['name'] || RandomString.generate
  classes            = options['classes'] || {}
  parent             = options['parent'] || Rootuuid
  rule               = options['rule']
  id                 = options['id']
  environment        = options['environment']
  variables          = options['variables']
  description        = options['description']
  environment_trumps = options['environment_trumps']

  hash = { "name"    => name,
           "parent"  => parent,
           "classes" => classes }

  if environment_trumps
    hash['environment_trumps'] = environment_trumps
  end
  if rule
    hash['rule'] = rule
  end
  if environment
    hash['environment'] = environment
  end
  if variables
    hash['variables'] = variables
  end
  if description
    hash['description'] = description
  end
  if id
    hash['id'] = id
  end

  create_node_group(hash).env.body
end

#delete_all_node_groupsObject

This method deletes all the descendents of the Rootuuid; the root group can never be deleted. If you are looking to clean out a system entirely, consider using import_baseline_hierarchy instead, as this method doesn’t clean out any classes or other settings the root group might have.



80
81
82
# File 'lib/scooter/httpdispatchers/classifier.rb', line 80

def delete_all_node_groups
  delete_node_group_descendents(get_node_group(Rootuuid))
end

#delete_node_group_descendents(node_group_model) ⇒ Object

This will delete anything that inherits from the node group specified, but not the actual node group itself.



155
156
157
158
159
160
161
# File 'lib/scooter/httpdispatchers/classifier.rb', line 155

def delete_node_group_descendents(node_group_model)
  id   = node_group_model['id']
  tree = get_node_group_trees_of_direct_descendents
  tree[id].each do |childid|
    delete_tree_recursion(tree, childid)
  end
end

#delete_tree_recursion(tree, node_group_id) ⇒ Object

The tree parameter required here is generated from the method get_node_group_trees_with_direct_descendents. This method will also delete the node_group_id as well.



192
193
194
195
196
197
198
# File 'lib/scooter/httpdispatchers/classifier.rb', line 192

def delete_tree_recursion(tree, node_group_id)
  tree[node_group_id].each do |childid|
    delete_tree_recursion(tree, childid)
  end
  #protect against trying to delete the Rootuuid
  delete_node_group(node_group_id) if node_group_id != Rootuuid
end

#environments_match?(other_environments, self_environments = nil) ⇒ Boolean

Check to see if all environments match between two query responses

Parameters:

  • other_environments (Object)
    • response from get_list_of_environments

  • self_environments (Object) (defaults to: nil)
    • response from get_list_of_environments

Returns:

  • (Boolean)


373
374
375
376
# File 'lib/scooter/httpdispatchers/classifier.rb', line 373

def environments_match?(other_environments, self_environments=nil)
  self_environments = get_list_of_environments if self_environments.nil?
  return other_environments == self_environments
end

#find_or_create_node_group_model(options = {}) ⇒ Object

This takes an optional hash of node group parameters. If a “name” option is provided it will attempt to find an existing node group with that name in the environment specified in the “environment” option (default: “production”).

If an existing node group is found, it will be updated according to the provided options (via ‘#replace_node_group_with_update_hash`).

If no existing node group is found, the options hash will be used to create a new node group (via ‘#create_new_node_group_model`).



137
138
139
140
141
142
143
144
# File 'lib/scooter/httpdispatchers/classifier.rb', line 137

def find_or_create_node_group_model(options={})
  options["environment"] ||= "production"
  if options["name"] && existing = get_node_group_by_name(options["name"], options["environment"])
    replace_node_group_with_update_hash(existing, options)
  else
    create_new_node_group_model(options)
  end
end

#get_node_group_by_name(name, environment = 'production') ⇒ Object

This will return a node group hash given the name of the node group. It defaults to production for the environment, since node names can be the same for different environments.



166
167
168
169
170
171
172
173
174
# File 'lib/scooter/httpdispatchers/classifier.rb', line 166

def get_node_group_by_name(name, environment='production')
  nodes = get_list_of_node_groups
  nodes.each do |node|
    if node['name'] == name && node['environment'] == environment
      return node
    end
  end
  nil # return nil if no matching name found
end

#get_node_group_id_by_name(name, environment = 'production') ⇒ Object

This will return the node group id given the name of the node group. It defaults to production for the environment, since node names can be the same for different environments.



179
180
181
182
183
184
185
186
187
# File 'lib/scooter/httpdispatchers/classifier.rb', line 179

def get_node_group_id_by_name(name, environment='production')
  nodes = get_list_of_node_groups
  nodes.each do |node|
    if node['name'] == name && node['environment'] == environment
      return node['id']
    end
  end
  nil # return nil if no matching name found
end

#get_node_group_trees_of_direct_descendentsObject

This returns a tree-like hash of all node groups in the classifier; each key is a uuid, each value is an array of direct children. If no direct children are found, the value is an empty array. This representation is useful for iterating over specific children of known node groups, and is using primarily in delete_node_group_descendents and delete_tree_recursion.

Example return hash for default PE Installation

{ "00000000-0000-4000-8000-000000000000" => # root group with one child
   ["6d37be98-42ee-400d-a66e-ebced989546c"],
  "6d37be98-42ee-400d-a66e-ebced989546c" => # node group with 5 kids
   ["42e385ca-8fb2-442d-a0af-3b14c86d321b",
   "32e6a36d-59ad-44ea-8708-feb910907058",
   "44ebbb50-501c-45f1-8c92-95d5b0313d24",
   "58448b7d-3175-4695-ac32-31cf4ee25754",
   "00350026-bfb6-4ce7-bd06-1a1cfea445f9",
   "b6400234-2a61-4417-b85e-c2dcc123686b"],
  "42e385ca-8fb2-442d-a0af-3b14c86d321b" => [], # childless node group
  "32e6a36d-59ad-44ea-8708-feb910907058" => [],
  "44ebbb50-501c-45f1-8c92-95d5b0313d24" => [],
  "58448b7d-3175-4695-ac32-31cf4ee25754" => [],
  "00350026-bfb6-4ce7-bd06-1a1cfea445f9" => [],
  "b6400234-2a61-4417-b85e-c2dcc123686b" => [] }


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
# File 'lib/scooter/httpdispatchers/classifier.rb', line 47

def get_node_group_trees_of_direct_descendents
  node_groups  = get_list_of_node_groups
  groups       = node_groups.map { |each| [each['id'], each['parent']] }

  # Constants for the array of tuples just created.
  id           = 0
  parent       = 1

  # Create root node and insert it into the tree hash.
  rootindex    = groups.find_index { |e| e[id] == e[parent] }
  rootid       = (groups.delete_at(rootindex))[id]
  tree         = Object::Hash.new
  tree[rootid] = Object::Array.new

  # Construct the rest of the tree as a hash of
  # id => [ child1, child2,...] nodes.
  groups.each do |g|
    tree[g[id]] = Object::Array.new
    if tree.has_key?(g[parent]) then
      tree[g[parent]] << g[id]
    else
      tree[g[parent]] = Object::Array.new
      tree[g[parent]] << g[id]
    end
  end
  tree
end

#groups_match?(other_groups, self_groups = nil) ⇒ Boolean

Check to see if all groups match between two query responses

Parameters:

  • other_groups (Object)
    • response from get_list_of_node_groups

  • self_groups (Object) (defaults to: nil)
    • response from get_list_of_node_groups

Returns:

  • (Boolean)


364
365
366
367
# File 'lib/scooter/httpdispatchers/classifier.rb', line 364

def groups_match?(other_groups, self_groups=nil)
  self_groups = get_list_of_node_groups if self_groups.nil?
  return other_groups == self_groups
end

#import_baseline_hierarchyObject

This method imports a bare root group into the NC, cleaning out and deleting any node groups that might have been available. Consider using this or delete_all_node_groups at the beginning of your test, depending on requirements of the test.



204
205
206
207
208
209
210
211
212
213
214
# File 'lib/scooter/httpdispatchers/classifier.rb', line 204

def import_baseline_hierarchy
  hierarchy = [{ "environment_trumps" => false,
                 "parent"             => Rootuuid,
                 "name"               => "default",
                 "rule"               => ["and", ["~", "name", ".*"]],
                 "variables"          => {},
                 "id"                 => Rootuuid,
                 "environment"        => "production",
                 "classes"            => {} }]
  import_hierarchy(hierarchy)
end

#nodes_match?(other_nodes, self_nodes = nil) ⇒ Boolean

Check to see if all nodes match between two query responses

Parameters:

  • other_nodes (Object)
    • response from get_list_of_nodes

  • self_nodes (Object) (defaults to: nil)
    • response from get_list_of_nodes

Returns:

  • (Boolean)


346
347
348
349
# File 'lib/scooter/httpdispatchers/classifier.rb', line 346

def nodes_match?(other_nodes, self_nodes=nil)
  self_nodes = get_list_of_nodes if self_nodes.nil?
  return other_nodes == self_nodes
end

#refresh_node_group_model(node_group_model) ⇒ Object

If for some reason your node group model is out of sync with the server’s state for that node group, you can use this method to just update your model with the server state.



149
150
151
# File 'lib/scooter/httpdispatchers/classifier.rb', line 149

def refresh_node_group_model(node_group_model)
  get_node_group(node_group_model['id'])
end

#replace_node_group_with_update_hash(node_group_model, update_hash) ⇒ Object

This uses a PUTs instead of a POST to update a node group; when using PUTs, it will delete and replace the entire node group instead of just updating the keys provided.



250
251
252
253
254
255
# File 'lib/scooter/httpdispatchers/classifier.rb', line 250

def replace_node_group_with_update_hash(node_group_model, update_hash)
  merged_model = node_group_model.merge(update_hash)
  replace_node_group(merged_model['id'], merged_model)
  # no verification of the response for now, will need to write some code
  # that verifies this and takes care of array ordering
end

#set_classifier_path(connection = self.connection) ⇒ Object



20
21
22
23
# File 'lib/scooter/httpdispatchers/classifier.rb', line 20

def set_classifier_path(connection=self.connection)
  set_url_prefix
  connection.url_prefix.path = '/classifier-api'
end

#update_node_group_with_node_group_model(node_group_model, update_hash) ⇒ Object

This uses the POST method to update a node group; when using POST, it will only send and update the specified keys.



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/scooter/httpdispatchers/classifier.rb', line 259

def update_node_group_with_node_group_model(node_group_model, update_hash)
  id       = node_group_model['id']
  response = update_node_group(id, update_hash)

  deep_merge(node_group_model, update_hash)
  node_group_model = remove_nil_values(node_group_model)

  # check to see if the update hash had any class changes that require
  # transforms to the groups['deleted'] object
  if node_group_model['deleted'] && node_group_model['classes'] != {} && update_hash['classes']
    update_hash['classes'].each do |classname, parameters|
      if node_group_model['deleted'][classname] == nil
        next
      end
      parameters.each do |parameter, value|
        if value == nil
          node_group_model['deleted'][classname].delete(parameter)
        else
          node_group_model['deleted'][classname][parameter]['value'] = value
        end
      end
    end
  end

  # check to see if we need to delete any classes from the model's
  # node_group_model{'deleted'] key
  if node_group_model['deleted']
    node_group_model['deleted'].each do |classname, parameters|
      if node_group_model['classes'][classname] == nil || node_group_model['deleted'][classname].keys == ['puppetlabs.classifier/deleted']
        node_group_model['deleted'].delete(classname)
      end
    end
    node_group_model.delete('deleted') if node_group_model['deleted'] == {}
  end

  node_group_model.delete('serial_number')
  node_group_model.delete('last_edited')

  response.env.body.delete('serial_number')
  response.env.body.delete('last_edited')
  if node_group_model != response.env.body
    raise "node_group_model did not match the server response:\n#{node_group_model}\n#{response.env.body}"
  end

  # If we got this far, return the "model" hash.
  node_group_model
end