Class: Cisco::Yang

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

Overview

The Yang class is a container for the needs_something? logic, which attempts to determine if a given operation and data structures will require setting the configuration of the remote device.

Class Method Summary collapse

Class Method Details

.array_equiv?(op, target, run) ⇒ Boolean

Returns:

  • (Boolean)


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
145
146
147
148
149
150
# File 'lib/util/yang.rb', line 106

def self.array_equiv?(op, target, run)
  n = target.length
  # We need to count the number of times that we've had a miss because
  # the target element was marked delete
  no_op_delete_count = 0
  loop = lambda do |i|
    if i == n
      if op == :replace
        # NB: We could end up with arrays that have only nil in them
        # sometimes, so correct for that, then normalize for the
        # number of elements that are no-ops because they are marked
        # delete
        rl = run.clone.delete_if(&:nil?).length
        tl = target.clone.delete_if(&:nil?).length - no_op_delete_count
        rl == tl
      else
        true
      end
    else
      target_elt = target[i]
      if target_elt.is_a?(Hash) && (target_elt[:delete] == :delete)
        target_elt = target_elt.clone
        target_elt.delete(:delete)
        delete = true
      end
      run_elt = run.find { |re| sub_elt(op, target_elt, re) }

      if run_elt.nil? && !nil_array(target_elt)
        if delete
          no_op_delete_count += 1
          loop.call(i + 1)
        else
          target_elt.nil?
        end
      else
        if delete
          false
        else
          loop.call(i + 1)
        end
      end
    end
  end
  loop.call(0)
end

.empty?(yang) ⇒ Boolean

Is the specified yang string empty?

Returns:

  • (Boolean)


23
24
25
# File 'lib/util/yang.rb', line 23

def self.empty?(yang)
  !yang || yang.empty?
end

.hash_equiv?(op, target, run) ⇒ Boolean

Returns:

  • (Boolean)


152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/util/yang.rb', line 152

def self.hash_equiv?(op, target, run)
  keys = target.keys
  n = keys.length
  loop = lambda do |i|
    if i == n
      if op == :replace
        run.keys.length == target.keys.length
      else
        true
      end
    else
      k = keys[i]
      run_v = run[k]
      target_v = target[k]
      if run_v.nil? && !nil_array(target_v)
        false
      else
        sub_elt(op, target_v, run_v) && loop.call(i + 1)
      end
    end
  end
  loop.call(0)
end

.insync_for_merge?(target, current) ⇒ Boolean

Given a current and target YANG configuration, returns true if the configurations are in-sync, relative to a “merge_config” action

Returns:

  • (Boolean)


29
30
31
32
33
34
# File 'lib/util/yang.rb', line 29

def self.insync_for_merge?(target, current)
  target_hash = self.empty?(target) ? {} : JSON.parse(target)
  current_hash = self.empty?(current) ? {} : JSON.parse(current)

  !needs_something?(:merge, target_hash, current_hash)
end

.insync_for_replace?(target, current) ⇒ Boolean

Given a current and target YANG configuration, returns true if the configuration are in-sync, relative to a “replace_config” action

Returns:

  • (Boolean)


38
39
40
41
42
43
# File 'lib/util/yang.rb', line 38

def self.insync_for_replace?(target, current)
  target_hash = self.empty?(target) ? {} : JSON.parse(target)
  current_hash = self.empty?(current) ? {} : JSON.parse(current)

  !needs_something?(:replace, target_hash, current_hash)
end

.needs_something?(op, target, run) ⇒ Boolean

usage:

needs_something?(op, target, run)

op     - symbol - If value is not :replace, it's assumed to be :merge.
                  Indicates to the function whether to check for a
                  possible merge vs. replace

target - JSON   - JSON tree representing target configuration

run    - JSON   - JSON tree representing target configuration

Needs merge will determine if target and run differ sufficiently to necessitate running the merge command.

The logic here amounts to determining if target is a subtree of run, with a tiny bit of domain trickiness surrounding elements that are arrays that contain a single nil element that is required for “creating” certain configuration elements.

There are ultimately 3 different types of elements in a json tree. Hashes, Arrays, and leaves. While hashes and array values are organized with an order, the logic here ignores the order. In fact, it specifically attempts to match assuming order doesn’t matter. This is largely to allow users some freedom in specifying the config in the manifest. The gRPC interface doesn’t seem to care about order. If that changes, then so should this code.

Arrays and Hashes are compared by iterating over every element in target, and ensuring it is within run.

Leaves are directly compared for equality, excepting the condition that the target leaf is in fact an array with one element that is nil.

Needs replace will determine if target and run differ sufficiently to necessitate running the replace command.

The logic is the same as merge, except when comparing hashes, if the run hash table has elements that are not in target, we ultimately indicate that replace is needed

Returns:

  • (Boolean)


87
88
89
# File 'lib/util/yang.rb', line 87

def self.needs_something?(op, target, run)
  !hash_equiv?(op, target, run)
end

.nil_array(elt) ⇒ Object



91
92
93
# File 'lib/util/yang.rb', line 91

def self.nil_array(elt)
  elt.nil? || (elt.is_a?(Array) && elt.length == 1 && elt[0].nil?)
end

.sub_elt(op, target, run) ⇒ Object



95
96
97
98
99
100
101
102
103
104
# File 'lib/util/yang.rb', line 95

def self.sub_elt(op, target, run)
  if target.is_a?(Hash) && run.is_a?(Hash)
    hash_equiv?(op, target, run)
  elsif target.is_a?(Array) && run.is_a?(Array)
    array_equiv?(op, target, run)
  else
    delete = target.is_a?(Hash) && (target[:delete] == :delete)
    delete || target == run || nil_array(target)
  end
end