Class: Cisco::Node

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

Overview

class Cisco::Node Singleton representing the network node (switch/router) that is running this code. The singleton is lazily instantiated, meaning that it doesn't exist until some client requests it (with Node.instance())

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeNode


245
246
247
248
249
250
251
252
# File 'lib/cisco_node_utils/node.rb', line 245

def initialize
  @client = Cisco::Client.create
  @cmd_ref = nil
  @cmd_ref = CommandReference.new(product:      product_id,
                                  platform:     @client.platform,
                                  data_formats: @client.data_formats)
  cache_flush
end

Instance Attribute Details

#clientObject (readonly)

Here and below are implementation details and private APIs that most providers shouldn't need to know about or use.


234
235
236
# File 'lib/cisco_node_utils/node.rb', line 234

def client
  @client
end

#cmd_refObject (readonly)

Here and below are implementation details and private APIs that most providers shouldn't need to know about or use.


234
235
236
# File 'lib/cisco_node_utils/node.rb', line 234

def cmd_ref
  @cmd_ref
end

Class Method Details

.instanceObject


236
237
238
# File 'lib/cisco_node_utils/node.rb', line 236

def self.instance
  @instance ||= new
end

.reset_instanceObject

Allow instance cache to be reset


241
242
243
# File 'lib/cisco_node_utils/node.rb', line 241

def self.reset_instance
  @instance = nil
end

Instance Method Details

#bootString


430
431
432
# File 'lib/cisco_node_utils/node.rb', line 430

def boot
  config_get('show_version', 'boot_image')
end

#cache_auto=(enable) ⇒ Object


274
275
276
# File 'lib/cisco_node_utils/node.rb', line 274

def cache_auto=(enable)
  @client.cache_auto = enable
end

#cache_auto?Boolean


270
271
272
# File 'lib/cisco_node_utils/node.rb', line 270

def cache_auto?
  @client.cache_auto?
end

#cache_enable=(enable) ⇒ Object


266
267
268
# File 'lib/cisco_node_utils/node.rb', line 266

def cache_enable=(enable)
  @client.cache_enable = enable
end

#cache_enable?Boolean


262
263
264
# File 'lib/cisco_node_utils/node.rb', line 262

def cache_enable?
  @client.cache_enable?
end

#cache_flushObject

Clear the cache of CLI output results.

If cache_auto is true (default) then this will be performed automatically whenever a config_set() is called, but providers may also call this to explicitly force the cache to be cleared.


227
228
229
# File 'lib/cisco_node_utils/node.rb', line 227

def cache_flush
  @client.cache_flush
end

#config_get(feature, property, *args) ⇒ String, ...

Convenience wrapper for get() Uses CommandReference to look up the given show command and key of interest, executes that command, and returns the value corresponding to that key.

Examples:

config_get(“show_version”, “system_image”)

config_get(“ospf”, “router_id”,

{name: "green", vrf: "one"})

Raises:

  • (IndexError)

    if the given (feature, name) pair is not in the CommandReference data or if the data doesn't have values defined for the 'get_command' and (optional) 'get_value' fields.

  • (Cisco::UnsupportedError)

    if the (feature, name) pair is flagged in the YAML as unsupported on this device.

  • (Cisco::RequestFailed)

    if the command is rejected by the device.


53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/cisco_node_utils/node.rb', line 53

def config_get(feature, property, *args)
  ref = @cmd_ref.lookup(feature, property)

  # If we have a default value but no getter, just return the default
  return ref.default_value if ref.default_value? && !ref.getter?

  get_args, ref = massage_structured(ref.getter(*args).clone, ref)
  data = get(command:     get_args[:command],
             data_format: get_args[:data_format],
             context:     get_args[:context],
             value:       get_args[:value])
  massage(data, ref)
end

#config_get_default(feature, property) ⇒ String?

Uses CommandReference to lookup the default value for a given feature and feature property.

Examples:

config_get_default(“vtp”, “file”)

Raises:

  • (IndexError)

    if the given (feature, name) pair is not in the CommandReference data or if the data doesn't have values defined for the 'default_value' field.


196
197
198
199
# File 'lib/cisco_node_utils/node.rb', line 196

def config_get_default(feature, property)
  ref = @cmd_ref.lookup(feature, property)
  ref.default_value
end

#config_set(feature, property, *args) ⇒ Object

Uses CommandReference to look up the given config command(s) of interest and then applies the configuration.

Examples:

config_set(“vtp”, “domain”, “example.com”)

config_set(“ospf”, “router_id”,

{:name => "green", :vrf => "one", :state => "",
 :router_id => "192.0.0.1"})

Raises:

  • (IndexError)

    if no relevant cmd_ref config_set exists

  • (ArgumentError)

    if too many or too few args are provided.

  • (Cisco::UnsupportedError)

    if this feature/name is unsupported

  • (Cisco::RequestFailed)

    if any command is rejected by the device.


216
217
218
219
220
# File 'lib/cisco_node_utils/node.rb', line 216

def config_set(feature, property, *args)
  ref = @cmd_ref.lookup(feature, property)
  set_args = ref.setter(*args)
  set(**set_args)
end

#delete_yang(yang) ⇒ Object

Delete the specified JSON YANG config from the device.


310
311
312
# File 'lib/cisco_node_utils/node.rb', line 310

def delete_yang(yang)
  @client.set(data_format: :yang_json, values: [yang], mode: :delete_config)
end

#domain_nameString


396
397
398
# File 'lib/cisco_node_utils/node.rb', line 396

def domain_name
  config_get('dnsclient', 'domain_name')
end

#drill_down_structured(value, ref) ⇒ Object

Drill down into structured nxapi table data and return value from the row specified by a two part key.

Example: Get vlanshowbr-vlanname in the row that contains vlan id 1000 “get_value”=>[“vlanshowbr-vlanid-utf 1000”, “vlanshowbr-vlanname”] Example with optional regexp match “get_value”=>[“vlanshowbr-vlanid-utf 1000”, “vlanshowbr-vlanname”,

'/^shutdown$/']

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
# File 'lib/cisco_node_utils/node.rb', line 105

def drill_down_structured(value, ref)
  # Nothing to do unless nxapi_structured
  return value unless ref.hash['drill_down']

  row_key = ref.hash['get_value'][0][/^\S+/]

  # Escape special characters if any in row_index and add
  # anchors for exact match.
  row_index = Regexp.escape(ref.hash['get_value'][0][/\S+$/])
  row_index = "^#{row_index}$"

  data_key = ref.hash['get_value'][1]
  regexp_filter = nil
  if ref.hash['get_value'][2]
    regexp_filter = Regexp.new ref.hash['get_value'][2][1..-2]
  end
  # Get the value using the row_key, row_index and data_key
  value = value.is_a?(Hash) ? [value] : value
  data = nil
  value.each do |row|
    if row[row_key].to_s[/#{row_index}/]
      data = row[data_key]
      data = data.nil? ? '' : data
    end
  end
  return value if data.nil?
  if regexp_filter
    filtered = regexp_filter.match(data)
    return filtered.nil? ? filtered : filtered[filtered.size - 1]
  end
  data
end

#get(**kwargs) ⇒ Object

Send a show command to the device. In general, clients should use config_get() rather than calling this function directly.

Raises:


292
293
294
# File 'lib/cisco_node_utils/node.rb', line 292

def get(**kwargs)
  @client.get(**kwargs)
end

#get_yang(yang_path) ⇒ Object

Retrieve JSON YANG config from the device for the specified path.


315
316
317
# File 'lib/cisco_node_utils/node.rb', line 315

def get_yang(yang_path)
  @client.get(data_format: :yang_json, command: yang_path)
end

#get_yang_oper(yang_path) ⇒ Object

Retrieve JSON YANG operational data for the specified path.


320
321
322
# File 'lib/cisco_node_utils/node.rb', line 320

def get_yang_oper(yang_path)
  @client.get(data_format: :yang_json, command: yang_path, mode: :get_oper)
end

#host_nameString


391
392
393
# File 'lib/cisco_node_utils/node.rb', line 391

def host_name
  config_get('show_version', 'host_name')
end

#inspectObject


258
259
260
# File 'lib/cisco_node_utils/node.rb', line 258

def inspect
  "Node: client:'#{client.inspect}' cmd_ref:'#{cmd_ref.inspect}'"
end

#last_reset_reasonString


417
418
419
# File 'lib/cisco_node_utils/node.rb', line 417

def last_reset_reason
  config_get('show_version', 'last_reset_reason')
end

#last_reset_timeString


412
413
414
# File 'lib/cisco_node_utils/node.rb', line 412

def last_reset_time
  config_get('show_version', 'last_reset_time')
end

#massage(value, ref) ⇒ Object

Attempt to massage the given value into the format specified by the given CmdRef object.


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/cisco_node_utils/node.rb', line 140

def massage(value, ref)
  Cisco::Logger.debug "Massaging '#{value}' (#{value.inspect})"
  value = drill_down_structured(value, ref)
  if value.is_a?(Array) && !ref.multiple
    fail "Expected zero/one value but got '#{value}'" if value.length > 1
    value = value[0]
  end
  if (value.nil? || value.to_s.empty?) &&
     ref.default_value? && ref.auto_default
    Cisco::Logger.debug "Default: #{ref.default_value}"
    return ref.default_value
  end
  if ref.multiple && ref.hash['get_data_format'] == :nxapi_structured
    return value if value.nil?
    value = [value.to_s] if value.is_a?(String) || value.is_a?(Fixnum)
  end
  return value unless ref.kind
  value = massage_kind(value, ref)
  Cisco::Logger.debug "Massaged to '#{value}'"
  value
end

#massage_kind(value, ref) ⇒ Object


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/cisco_node_utils/node.rb', line 162

def massage_kind(value, ref)
  case ref.kind
  when :boolean
    if value.nil? || value.empty?
      value = false
    elsif /^no / =~ value
      value = false
    elsif /disable$/ =~ value
      value = false
    else
      value = true
    end
  when :int
    value = value.to_i unless value.nil?
  when :string
    value = '' if value.nil?
    value = value.to_s.strip
  when :symbol
    value = value.to_sym unless value.nil?
  end
  value
end

#massage_structured(get_args, ref) ⇒ Object

The yaml file may specifiy an Array as the get_value to drill down into nxapi_structured table output. The table may contain multiple rows but only one of the rows has the interesting data.


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
# File 'lib/cisco_node_utils/node.rb', line 70

def massage_structured(get_args, ref)
  # Nothing to do unless nxapi_structured.
  return [get_args, ref] unless
    ref.hash['get_data_format'] == :nxapi_structured

  # The CmdRef object will contain a get_value Array with 2 values.
  # The first value is the key to identify the correct row in the table
  # of structured output and the second is the key to identify the data
  # to retrieve.
  #
  # Example: Get vlanshowbr-vlanname in the row that contains a specific
  #  vlan_id.
  # "get_value"=>["vlanshowbr-vlanid-utf <vlan_id>", "vlanshowbr-vlanname"]
  #
  # TBD: Why do we need to check is_a?(Array) here?
  if ref.hash['get_value'].is_a?(Array) && ref.hash['get_value'].size >= 2
    # Replace the get_value hash entry with the value after any tokens
    # specified in the yaml file have been replaced and set get_args[:value]
    # to nil so that the structured table data can be retrieved properly.
    ref.hash['get_value'] = get_args[:value]
    ref.hash['drill_down'] = true
    get_args[:value] = nil
    cache_flush
  end
  [get_args, ref]
end

#merge_yang(yang) ⇒ Object

Merge the specified JSON YANG config with the running config on the device.


298
299
300
# File 'lib/cisco_node_utils/node.rb', line 298

def merge_yang(yang)
  @client.set(data_format: :yang_json, values: [yang], mode: :merge_config)
end

#osString


325
326
327
328
329
# File 'lib/cisco_node_utils/node.rb', line 325

def os
  o = config_get('show_version', 'header')
  fail 'failed to retrieve operating system information' if o.nil?
  o.split("\n")[0]
end

#os_versionString


332
333
334
# File 'lib/cisco_node_utils/node.rb', line 332

def os_version
  config_get('show_version', 'version')
end

#os_version_get(feature, property) ⇒ Object


440
441
442
# File 'lib/cisco_node_utils/node.rb', line 440

def os_version_get(feature, property)
  @cmd_ref.lookup(feature, property).os_version
end

#prod_qualifier(prod, inventory) ⇒ Object


363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/cisco_node_utils/node.rb', line 363

def prod_qualifier(prod, inventory)
  case prod
  when /N(3|9)K/
    # one datapoint is used to determine if the current n9k/n3k
    # platform is a fretta based or non-fretta.
    #
    # Module == *-R
    inventory.each do |row|
      if row['productid'][/-R/]
        # Append -F for fretta platform.
        return prod.concat('-F') unless prod[/-F/]
      end
    end
  end
  prod
end

#product_descriptionString


337
338
339
# File 'lib/cisco_node_utils/node.rb', line 337

def product_description
  config_get('show_version', 'description')
end

#product_idString


342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/cisco_node_utils/node.rb', line 342

def product_id
  if @cmd_ref
    prod = config_get('inventory', 'productid')
    all  = config_get('inventory', 'all')
    prod_qualifier(prod, all)
  else
    # We use this function to *find* the appropriate CommandReference
    if @client.platform == :nexus
      entries = get(command:     'show inventory',
                    data_format: :nxapi_structured)
      prod = entries['TABLE_inv']['ROW_inv'][0]['productid']
      prod_qualifier(prod, entries['TABLE_inv']['ROW_inv'])
    elsif @client.platform == :ios_xr
      # No support for structured output for this command yet
      output = get(command:     'show inventory',
                   data_format: :cli)
      return /NAME: .*\nPID: (\S+)/.match(output)[1]
    end
  end
end

#product_serial_numberString


386
387
388
# File 'lib/cisco_node_utils/node.rb', line 386

def product_serial_number
  config_get('inventory', 'serialnum')
end

#product_version_idString


381
382
383
# File 'lib/cisco_node_utils/node.rb', line 381

def product_version_id
  config_get('inventory', 'versionid')
end

#replace_yang(yang) ⇒ Object

Replace the running config on the device with the specified JSON YANG config.


304
305
306
307
# File 'lib/cisco_node_utils/node.rb', line 304

def replace_yang(yang)
  @client.set(data_format: :yang_json, values: [yang],
              mode: :replace_config)
end

#set(**kwargs) ⇒ Object

Send a config command to the device. In general, clients should use config_set() rather than calling this function directly.

Raises:


283
284
285
# File 'lib/cisco_node_utils/node.rb', line 283

def set(**kwargs)
  @client.set(**kwargs)
end

#systemString


436
437
438
# File 'lib/cisco_node_utils/node.rb', line 436

def system
  config_get('show_version', 'system_image')
end

#system_cpu_utilizationFloat


422
423
424
425
426
# File 'lib/cisco_node_utils/node.rb', line 422

def system_cpu_utilization
  output = config_get('system', 'resources')
  return output if output.nil?
  output['cpu_state_user'].to_f + output['cpu_state_kernel'].to_f
end

#system_uptimeInteger


401
402
403
404
405
406
407
408
409
# File 'lib/cisco_node_utils/node.rb', line 401

def system_uptime
  cache_flush
  t = config_get('show_system', 'uptime')
  fail 'failed to retrieve system uptime' if t.nil?
  # time units: t = ["0", "23", "15", "49"]
  t.map!(&:to_i)
  d, h, m, s = t
  (s + 60 * (m + 60 * (h + 24 * (d))))
end

#to_sObject


254
255
256
# File 'lib/cisco_node_utils/node.rb', line 254

def to_s
  client.to_s
end