shopify-junos-ez-stdlib 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +91 -0
  3. data/LICENSE +26 -0
  4. data/README.md +199 -0
  5. data/docs/Facts.md +192 -0
  6. data/docs/Providers/Group.md +61 -0
  7. data/docs/Providers/IPports.md +61 -0
  8. data/docs/Providers/L1ports.md +29 -0
  9. data/docs/Providers/L2ports.md +43 -0
  10. data/docs/Providers/LAGports.md +57 -0
  11. data/docs/Providers/StaticHosts.md +26 -0
  12. data/docs/Providers/StaticRoutes.md +37 -0
  13. data/docs/Providers/UserAuths.md +32 -0
  14. data/docs/Providers/Users.md +122 -0
  15. data/docs/Providers/Vlans.md +43 -0
  16. data/docs/Providers_Resources.md +353 -0
  17. data/docs/README_FIRST.md +27 -0
  18. data/docs/Utils/Config.md +160 -0
  19. data/docs/Utils/Filesystem.md +360 -0
  20. data/docs/Utils/Routing-Engine.md +379 -0
  21. data/docs/Utils/SCP.md +24 -0
  22. data/examples/config/config_file.rb +72 -0
  23. data/examples/config/config_template_object.rb +81 -0
  24. data/examples/config/config_template_simple.rb +76 -0
  25. data/examples/config/multi_config.rb +60 -0
  26. data/examples/fs_utils.rb +31 -0
  27. data/examples/lag_port.rb +27 -0
  28. data/examples/re_upgrade.rb +99 -0
  29. data/examples/re_utils.rb +33 -0
  30. data/examples/simple.rb +46 -0
  31. data/examples/st_hosts.rb +33 -0
  32. data/examples/user.rb +32 -0
  33. data/examples/vlans.rb +31 -0
  34. data/junos-ez-stdlib.gemspec +15 -0
  35. data/lib/junos-ez/exceptions.rb +3 -0
  36. data/lib/junos-ez/facts.rb +83 -0
  37. data/lib/junos-ez/facts/chassis.rb +51 -0
  38. data/lib/junos-ez/facts/ifd_style.rb +17 -0
  39. data/lib/junos-ez/facts/personality.rb +25 -0
  40. data/lib/junos-ez/facts/switch_style.rb +31 -0
  41. data/lib/junos-ez/facts/version.rb +58 -0
  42. data/lib/junos-ez/group.rb +206 -0
  43. data/lib/junos-ez/ip_ports.rb +30 -0
  44. data/lib/junos-ez/ip_ports/classic.rb +188 -0
  45. data/lib/junos-ez/l1_ports.rb +121 -0
  46. data/lib/junos-ez/l1_ports/classic.rb +87 -0
  47. data/lib/junos-ez/l1_ports/switch.rb +134 -0
  48. data/lib/junos-ez/l2_ports.rb +66 -0
  49. data/lib/junos-ez/l2_ports/bridge_domain.rb +499 -0
  50. data/lib/junos-ez/l2_ports/vlan.rb +433 -0
  51. data/lib/junos-ez/l2_ports/vlan_l2ng.rb +502 -0
  52. data/lib/junos-ez/lag_ports.rb +268 -0
  53. data/lib/junos-ez/provider.rb +619 -0
  54. data/lib/junos-ez/stdlib.rb +18 -0
  55. data/lib/junos-ez/system.rb +48 -0
  56. data/lib/junos-ez/system/st_hosts.rb +92 -0
  57. data/lib/junos-ez/system/st_routes.rb +159 -0
  58. data/lib/junos-ez/system/syscfg.rb +103 -0
  59. data/lib/junos-ez/system/userauths.rb +84 -0
  60. data/lib/junos-ez/system/users.rb +217 -0
  61. data/lib/junos-ez/utils/config.rb +236 -0
  62. data/lib/junos-ez/utils/fs.rb +385 -0
  63. data/lib/junos-ez/utils/re.rb +558 -0
  64. data/lib/junos-ez/version.rb +6 -0
  65. data/lib/junos-ez/vlans.rb +38 -0
  66. data/lib/junos-ez/vlans/bridge_domain.rb +89 -0
  67. data/lib/junos-ez/vlans/vlan.rb +119 -0
  68. data/lib/junos-ez/vlans/vlan_l2ng.rb +126 -0
  69. data/shipit.yml +4 -0
  70. data/tmp +7 -0
  71. metadata +129 -0
@@ -0,0 +1,268 @@
1
+ require "junos-ez/provider"
2
+
3
+ module Junos::Ez::LAGports
4
+
5
+ PROPERTIES = [
6
+ :links, # Set of interface names
7
+ :minimum_links, # nil or Number > 0 # optional
8
+ :lacp, # [ :active, :passive, :disabled ] # optional
9
+ ]
10
+
11
+ def self.Provider( ndev, varsym )
12
+ newbie = Junos::Ez::LAGports::Provider::new( ndev )
13
+ newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES
14
+ Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie )
15
+ end
16
+
17
+ class Provider < Junos::Ez::Provider::Parent
18
+ # common parenting goes here ... if we were to
19
+ # subclass the objects ... not doing that now
20
+ end
21
+
22
+ end
23
+
24
+ class Junos::Ez::LAGports::Provider
25
+
26
+ ### ---------------------------------------------------------------
27
+ ### XML top placement
28
+ ### ---------------------------------------------------------------
29
+
30
+ # LAG ports sit at the toplevel interface
31
+
32
+ def xml_at_top
33
+ Nokogiri::XML::Builder.new {|xml| xml.configuration {
34
+ xml.interfaces { xml.interface {
35
+ xml.name @name
36
+ return xml
37
+ }}
38
+ }}
39
+ end
40
+
41
+ ###-----------------------------------------------------------
42
+ ###-----------------------------------------------------------
43
+ ### utilities
44
+ ###-----------------------------------------------------------
45
+
46
+ def get_cookie_links( cfg )
47
+ cfg.xpath( "apply-macro[name = 'netdev_lag[:links]']/data/name" ).collect { |n| n.text }
48
+ end
49
+
50
+ def set_cookie_links( cfg )
51
+ cfg.send(:'apply-macro', Netconf::JunosConfig::REPLACE ) {
52
+ cfg.name 'netdev_lag[:links]'
53
+ should[:links].each{ |ifd|
54
+ cfg.data { cfg.name ifd }
55
+ }
56
+ }
57
+ end
58
+
59
+ ### ---------------------------------------------------------------
60
+ ### XML property readers
61
+ ### ---------------------------------------------------------------
62
+
63
+ def xml_config_read!
64
+ database = {'database' => 'committed'}
65
+ @ndev.rpc.get_configuration(xml_at_top, database)
66
+ end
67
+
68
+ def xml_get_has_xml( xml )
69
+ if ndev.facts[:ifd_style] == "CLASSIC"
70
+ @ifd_ether_options = 'gigether-options'
71
+ else
72
+ @ifd_ether_options = 'ether-options'
73
+ end
74
+ xml.xpath('//interface')[0]
75
+ end
76
+
77
+ def xml_read_parser( as_xml, as_hash )
78
+ set_has_status( as_xml, as_hash )
79
+
80
+ # property :links
81
+ ae_name = as_xml.xpath('name').text
82
+ as_hash[:links] = Set.new(get_cookie_links(as_xml))
83
+
84
+ # property :lacp
85
+ ae_opts = as_xml.xpath('aggregated-ether-options')
86
+ if (lacp = ae_opts.xpath('lacp')[0])
87
+ as_hash[:lacp] = (lacp.xpath('active')[0]) ? :active : :passive
88
+ else
89
+ as_hash[:lacp] = :disabled
90
+ end
91
+
92
+ # property :minimum_links
93
+ as_hash[:minimum_links] = (min_links = ae_opts.xpath('minimum-links')[0]) ? min_links.text.to_i : 1
94
+ end
95
+
96
+ ### ---------------------------------------------------------------
97
+ ### XML property writers
98
+ ### ---------------------------------------------------------------
99
+ def update_ifd_should()
100
+ if @should[:links].empty?
101
+ raise Junos::Ez::NoProviderError, "\n *links* are compulsory for creating lag interface!!! \n"
102
+ else
103
+ ether_option = @should[:links][0].to_s
104
+ @ifd_ether_options = (ether_option.start_with? 'fe-') ? 'fastether-options' : 'gigether-options'
105
+ end
106
+ end
107
+
108
+ def update_ifd_has()
109
+ @has[:links] = @has[:links].to_a
110
+ if @has[:links].empty?
111
+ raise Junos::Ez::NoProviderError, "\n Either lag interface is not created or links associated with given lag interface is not supported \n"
112
+ else
113
+ ether_option = @has[:links][0].to_s
114
+ @ifd_ether_options = (ether_option.start_with? 'fe-') ? 'fastether-options' : 'gigether-options'
115
+ end
116
+ end
117
+
118
+ def xml_change_links( xml )
119
+ update_ifd_should()
120
+ @should[:links] = @should[:links].to_set if @should[:links].kind_of? Array
121
+
122
+ has = @has[:links] || Set.new
123
+ should = @should[:links] || Set.new
124
+
125
+ set_cookie_links( xml )
126
+
127
+ del = has - should
128
+ add = should - has
129
+
130
+ par = xml.instance_variable_get(:@parent)
131
+ dot_ifd = par.at_xpath('ancestor::interfaces')
132
+
133
+ add.each{ |new_ifd| Nokogiri::XML::Builder.with( dot_ifd ) {|dot|
134
+ dot.interface { dot.name new_ifd
135
+ dot.send(@ifd_ether_options.to_sym) {
136
+ dot.send(:'ieee-802.3ad') {
137
+ dot.bundle @name
138
+ }
139
+ }
140
+ }}}
141
+
142
+ del.each{ |new_ifd| Nokogiri::XML::Builder.with( dot_ifd ) {|dot|
143
+ dot.interface { dot.name new_ifd
144
+ dot.send(@ifd_ether_options) {
145
+ dot.send( :'ieee-802.3ad', Netconf::JunosConfig::DELETE )
146
+ }
147
+ }}}
148
+ end
149
+
150
+ def xml_change_lacp( xml )
151
+ if @should[:lacp] == :disabled or @should[:lacp].nil?
152
+ xml.send(:'aggregated-ether-options') {
153
+ xml.lacp( Netconf::JunosConfig::DELETE )
154
+ }
155
+ else
156
+ xml.send(:'aggregated-ether-options') {
157
+ xml.lacp { xml.send @should[:lacp] } # @@@ should validate :lacp value before doing this...
158
+ }
159
+ end
160
+ end
161
+
162
+ def xml_change_minimum_links( xml )
163
+ if @should[:minimum_links]
164
+ xml.send(:'aggregated-ether-options') {
165
+ xml.send( :'minimum-links', @should[:minimum_links] )
166
+ }
167
+ else
168
+ xml.send(:'aggregated-ether-options') {
169
+ xml.send(:'minimum-links', Netconf::JunosConfig::DELETE )
170
+ }
171
+ end
172
+ end
173
+
174
+ ### ---------------------------------------------------------------
175
+ ### XML on-create
176
+ ### ---------------------------------------------------------------
177
+
178
+ def xml_on_create( xml )
179
+ # make sure there is a 'unit 0' on the AE port
180
+ par = xml.instance_variable_get(:@parent)
181
+ Nokogiri::XML::Builder.with(par) do |dot|
182
+ dot.unit {
183
+ dot.name '0'
184
+ }
185
+ end
186
+ end
187
+
188
+ ### ---------------------------------------------------------------
189
+ ### XML on-delete
190
+ ### ---------------------------------------------------------------
191
+
192
+ def xml_on_delete( xml )
193
+ update_ifd_has()
194
+ par = xml.instance_variable_get(:@parent)
195
+ dot_ifd = par.at_xpath('ancestor::interfaces')
196
+
197
+ # remove the bindings from each of the physical interfaces
198
+ #
199
+ @has[:links].each do |new_ifd| Nokogiri::XML::Builder.with( dot_ifd ) do |dot|
200
+ dot.interface { dot.name new_ifd
201
+ dot.send(@ifd_ether_options) {
202
+ dot.send( :'ieee-802.3ad', Netconf::JunosConfig::DELETE )
203
+ }
204
+ }
205
+ end
206
+ end
207
+
208
+ # now remove the LAG interface
209
+ #
210
+ Nokogiri::XML::Builder.with( dot_ifd ) do |dot|
211
+ dot.interface( Netconf::JunosConfig::DELETE ) {
212
+ dot.name @name
213
+ }
214
+ end
215
+ end
216
+
217
+ end
218
+
219
+
220
+ ##### ---------------------------------------------------------------
221
+ ##### Provider collection methods
222
+ ##### ---------------------------------------------------------------
223
+
224
+ class Junos::Ez::LAGports::Provider
225
+
226
+ def build_list
227
+ @ndev.rpc.get_interface_information(
228
+ :terse => true,
229
+ :interface_name => 'ae*'
230
+ ).xpath('physical-interface/name').collect{ |name| name.text.strip }
231
+ end
232
+
233
+ def build_catalog
234
+ return @catalog if list!.empty?
235
+
236
+ list.each do |ae_name|
237
+ @ndev.rpc.get_configuration{ |xml|
238
+ xml.interfaces {
239
+ xml.interface {
240
+ xml.name ae_name
241
+ }
242
+ }
243
+ }.xpath('interfaces/interface').each do |as_xml|
244
+ @catalog[ae_name] = {}
245
+ xml_read_parser( as_xml, @catalog[ae_name] )
246
+ end
247
+ end
248
+
249
+ @catalog
250
+ end
251
+
252
+ end
253
+
254
+ ##### ---------------------------------------------------------------
255
+ ##### _PRIVATE methods
256
+ ##### ---------------------------------------------------------------
257
+
258
+ class Junos::Ez::LAGports::Provider
259
+ def _get_port_list( name )
260
+ @ndev.rpc.get_interface_information(
261
+ :detail => true,
262
+ :interface_name => name + '.0'
263
+ ).xpath('//lag-link/name').collect{ |name|
264
+ name.text.strip.split('.',2).first
265
+ }
266
+ end
267
+ end
268
+
@@ -0,0 +1,619 @@
1
+ ##### ---------------------------------------------------------------
2
+ ##### The Junos::Ez::Provider and associated Parent class make up
3
+ ##### the main 'framework' of the "EZ" library system. Please
4
+ ##### consider your changes carefully as it will have a large
5
+ ##### scope of impact. Thank you.
6
+ ##### ---------------------------------------------------------------
7
+
8
+ require 'set'
9
+ require 'junos-ez/version.rb'
10
+ require 'junos-ez/exceptions.rb'
11
+
12
+ module Junos::Ez
13
+
14
+ ### ---------------------------------------------------------------
15
+ ### rpc_errors - decodes the XML into an array of error/Hash
16
+ ### @@@ TBD: this should be moved into the 'netconf' gem
17
+ ### ---------------------------------------------------------------
18
+
19
+ def self.rpc_errors( as_xml )
20
+ errs = as_xml.xpath('//rpc-error')
21
+ return nil if errs.count == 0 # safety check
22
+
23
+ retval = []
24
+ errs.each do |err|
25
+ err_h = {}
26
+ # every error has a severity and message
27
+ err_h[:severity] = err.xpath('error-severity').text.strip
28
+ err_h[:message] = err.xpath('error-message').text.strip
29
+
30
+ # some have an edit path error
31
+ unless ( err_path = err.xpath('error-path')).empty?
32
+ err_h[:edit_path] = err_path.text.strip
33
+ end
34
+
35
+ # some have addition error-info/bad-element ...
36
+ unless ( bad_i = err.xpath('error-info/bad-element')).empty?
37
+ err_h[:bad_identifier] = bad_i.text.strip
38
+ end
39
+
40
+ retval << err_h
41
+ end
42
+ retval
43
+ end
44
+
45
+ end
46
+
47
+ module Junos::Ez::Provider
48
+
49
+ ## all managed objects have the following properties:
50
+
51
+ PROPERTIES = [
52
+ :_exist, # exists in configuration (or should)
53
+ :_active # active in configuration (or should)
54
+ ]
55
+
56
+ ## 'attach_instance_variable' is the way to dynamically
57
+ ## add an instance variable to the on_obj and "publish"
58
+ ## it in the same way attr_accessor would.
59
+
60
+ def self.attach_instance_variable( on_obj, varsname, new_obj )
61
+ ivar = ("@" + varsname.to_s).to_sym
62
+ on_obj.instance_variable_set( ivar, new_obj )
63
+ on_obj.define_singleton_method( varsname ) do
64
+ on_obj.instance_variable_get( ivar )
65
+ end
66
+ on_obj.providers << varsname
67
+ end
68
+
69
+ end
70
+
71
+ class Junos::Ez::Provider::Parent
72
+
73
+ attr_reader :ndev, :parent, :name
74
+ attr_accessor :providers
75
+ attr_accessor :has, :should, :properties
76
+ attr_accessor :list, :catalog
77
+
78
+ # p_obj - the parent object
79
+ # name - the name of the resource, or nil if this is a provider
80
+ # opts - options to the provider/resource. :parent is reserved
81
+
82
+ def initialize( p_obj, name = nil, opts = {} )
83
+
84
+ @providers = []
85
+ @parent = opts[:parent] || nil
86
+ @ndev = p_obj.instance_variable_get(:@ndev) || p_obj
87
+ @name = name
88
+ @opts = opts
89
+
90
+ @list = [] # array list of item names
91
+ @catalog = {} # hash catalog of named items
92
+
93
+ return unless @name
94
+ # resources only from here ...
95
+ @has = {} # properties read-from Junos
96
+ @should = {} # properties to write-back to Junos
97
+ end
98
+
99
+ ### ---------------------------------------------------------------
100
+ ### 'is_provider?' - indicates if this object instance is a
101
+ ### provider object, rather than a specific instance of the object
102
+ ### ---------------------------------------------------------------
103
+
104
+ def is_provider?; @name.nil? end
105
+
106
+ ### ---------------------------------------------------------------
107
+ ### is_new? - indicates if this is a new resource
108
+ ### ---------------------------------------------------------------
109
+
110
+ def is_new?; (@has[:_exist] == false) || false end
111
+
112
+ ### ---------------------------------------------------------------
113
+ ### [property] resource property reader or
114
+ ### ["name"] resource selector from provider
115
+ ### ---------------------------------------------------------------
116
+
117
+ def []( property )
118
+ return self.select( property ) if is_provider?
119
+
120
+ # if there is already something in the write-back, then use
121
+ # it before using from the read-cache
122
+
123
+ return @should[property] if @should[property]
124
+ return @has[property] if @has
125
+ end
126
+
127
+ ### ---------------------------------------------------------------
128
+ ### []= property writer (@should)
129
+ ### ---------------------------------------------------------------
130
+
131
+ def []=( property, rval )
132
+ raise ArgumentError, "This is not a provider instance" if is_provider?
133
+ raise ArgumentError, "Invalid property['#{property.to_s}']" unless properties.include? property
134
+
135
+ @should[property] = rval
136
+ end
137
+
138
+ ### ---------------------------------------------------------------
139
+ ### 'select' a resource from a provider
140
+ ### ---------------------------------------------------------------
141
+
142
+ def select( name )
143
+ raise ArgumentError, "This is not a provider instance" unless is_provider?
144
+ this = self.class.new( @ndev, name, @opts )
145
+ this.properties = self.properties
146
+ this.read!
147
+ this
148
+ end
149
+
150
+ ### ---------------------------------------------------------------
151
+ ### 'exists?' - does the resource exist in the Juos config
152
+ ### ---------------------------------------------------------------
153
+
154
+ def exists?; @has[:_exist]; end
155
+
156
+ ### ---------------------------------------------------------------
157
+ ### 'active?' - is the resource config active in Junos
158
+ ### ---------------------------------------------------------------
159
+
160
+ def active?
161
+ false unless exists?
162
+ @has[:_active]
163
+ end
164
+
165
+ ### @@@ helper method, probably needs to go into 'private section
166
+ ### @@@ TBD
167
+
168
+ def name_decorated( name = @name )
169
+ self.class.to_s + "['" + name + "']"
170
+ end
171
+
172
+ ### ---------------------------------------------------------------
173
+ ### Provider methods to obtain collection information as
174
+ ### 'list' - array of named items
175
+ ### 'catalog' - hash of all items with properties
176
+ ### ---------------------------------------------------------------
177
+
178
+ def list
179
+ @list.empty? ? list! : @list
180
+ end
181
+
182
+ def list!
183
+ @list.clear
184
+ @list = build_list
185
+ end
186
+
187
+ def catalog
188
+ @catalog.empty? ? catalog! : @catalog
189
+ end
190
+
191
+ def catalog!
192
+ @catalog.clear
193
+ @catalog = build_catalog
194
+ end
195
+
196
+ ### ---------------------------------------------------------------
197
+ ### CREATE methods
198
+ ### ---------------------------------------------------------------
199
+
200
+ ## ----------------------------------------------------------------
201
+ ## 'create' will build a new object, but does not write the
202
+ ## contents back to the device. The caller can chain the
203
+ ## write! method if desired Alternative, the caller
204
+ ## can use 'create!' which does write to the device.
205
+ ## ----------------------------------------------------------------
206
+
207
+ def create( name = nil, prop_hash = {} )
208
+
209
+ ## if this is an existing object, then we shouldn't
210
+ ## allow the caller to create something.
211
+
212
+ raise ArgumentError, "Not called by provider!" unless is_provider?
213
+
214
+ ## if we're here, then we're creating an entirely new
215
+ ## instance of this object. We should check to see if
216
+ ## it first exists, eh? So allow the caller to specify
217
+ ## if they want an exception if it already exists; overloading
218
+ ## the use of the prop_hash[:_exist], yo!
219
+
220
+ newbie = self.select( name )
221
+ if prop_hash[:_exist]
222
+ raise ArgumentError, name_decorated(name) + " already exists" if newbie.exists?
223
+ end
224
+
225
+ prop_hash.each{ |k,v| newbie[k] = v } unless prop_hash.empty?
226
+
227
+ ## default mark the newly created object as should exist and should
228
+ ## be active (if not already set)
229
+
230
+ newbie[:_exist] = true
231
+ newbie[:_active] ||= true
232
+
233
+ ## if a block is provided, then pass the block the new object
234
+ ## the caller is then expected to set the properies
235
+
236
+ yield( newbie ) if block_given?
237
+
238
+ ## return the new object
239
+ return newbie
240
+ end
241
+
242
+ ## ----------------------------------------------------------------
243
+ ## 'create!' is just a helper to call create and then write
244
+ ## the config assuming create returns ok.
245
+ ## ----------------------------------------------------------------
246
+
247
+ def create!( *args )
248
+ newbie = create( *args )
249
+ return nil unless newbie
250
+ newbie.write!
251
+ newbie
252
+ end
253
+
254
+ ## ----------------------------------------------------------------
255
+ ## YAML / HASH methods
256
+ ## ----------------------------------------------------------------
257
+
258
+ def create_from_yaml!( opts = {} )
259
+ raise ArgumentError "Missing :filename param" unless opts[:filename]
260
+ as_hash = YAML.load_file( opts[:filename] )
261
+ write_xml_config! xml_from_h_expanded( as_hash, opts )
262
+ end
263
+
264
+ def create_from_hash!( as_hash, opts = {} )
265
+ write_xml_config! xml_from_h_expanded( as_hash, opts )
266
+ end
267
+
268
+ def to_h_expanded( opts = {} )
269
+ to_h( opts )
270
+ end
271
+
272
+ def to_yaml( opts = {} )
273
+ out_hash = to_h_expanded( opts )
274
+ out_yaml = out_hash.to_yaml
275
+ File.open( opts[:filename], "w" ){|f| f.puts out_hash.to_yaml } if opts[:filename]
276
+ out_yaml
277
+ end
278
+
279
+ ### ---------------------------------------------------------------
280
+ ### 'delete!' will cause the item to be removed from the Junos
281
+ ### configuration
282
+ ### ---------------------------------------------------------------
283
+
284
+ def delete!
285
+ return nil unless exists?
286
+ xml = xml_at_top
287
+ par = xml.instance_variable_get(:@parent)
288
+ par['delete'] = 'delete'
289
+ xml_on_delete( xml )
290
+ rsp = write_xml_config!( xml.doc.root )
291
+ @has[:_exist] = false
292
+ true # rsp ... don't return XML, but let's hear from the community...
293
+ end
294
+
295
+ ### ---------------------------------------------------------------
296
+ ### Junos activation controls
297
+ ### ---------------------------------------------------------------
298
+
299
+ def activate!
300
+ return nil if @should[:_active] == true
301
+ @should[:_active] = true
302
+ write!
303
+ end
304
+
305
+ def deactivate!
306
+ return nil if @should[:_active] == false
307
+ @should[:_active] = false
308
+ write!
309
+ end
310
+
311
+ ### ---------------------------------------------------------------
312
+ ### Junos rename element
313
+ ### ---------------------------------------------------------------
314
+
315
+ ## by default, simply allow the new name
316
+ def xml_element_newname( new_name); new_name end
317
+
318
+ def rename!( new_name )
319
+ return nil unless exists?
320
+
321
+ xml = xml_at_top
322
+ par = xml.instance_variable_get(:@parent)
323
+ new_ele_name = xml_element_newname( new_name )
324
+
325
+ return nil unless new_ele_name
326
+
327
+ par['rename'] = 'rename'
328
+ par['name'] = new_ele_name
329
+
330
+ rsp = write_xml_config!( xml.doc.root )
331
+ @name = new_name
332
+ rsp
333
+ end
334
+
335
+ ### ---------------------------------------------------------------
336
+ ### Junos reorder method
337
+ ###
338
+ ### opts[:before] = item-name,
339
+ ### opts[:after] = item-name
340
+ ### ---------------------------------------------------------------
341
+
342
+ def reorder!( opts )
343
+ return nil unless exists?
344
+
345
+ ## validate opts hash
346
+ ctrl, name = opts.first
347
+ raise ArgumentError, "Invalid operation #{ctrl}" unless [:before,:after].include? ctrl
348
+
349
+ xml = xml_at_top
350
+ par = xml.instance_variable_get(:@parent)
351
+ par['insert'] = ctrl.to_s
352
+ par['name'] = name
353
+ rsp = write_xml_config! ( xml.doc.root )
354
+
355
+ return rsp
356
+ end
357
+
358
+ ### ---------------------------------------------------------------
359
+ ### Provider `each` - iterate through each managed resource
360
+ ### as obtained from the `list` instance variable. select the
361
+ ### object and pass it to the provided block
362
+ ### ---------------------------------------------------------------
363
+
364
+ def each( &block )
365
+ raise ArgumentError, "not a provider" unless is_provider?
366
+ list.each{ |name| yield select(name ) }
367
+ end
368
+
369
+ ### ---------------------------------------------------------------
370
+ ### Provider `with` - iterate through each managed resource
371
+ ### as obtained from the `given_list` instance variable.
372
+ ### select the object and pass it to the provided block
373
+ ### ---------------------------------------------------------------
374
+
375
+ def with( given_list, &block )
376
+ raise ArgumentError, "not a provider" unless is_provider?
377
+ given_list.each{ |name| yield select( name ) }
378
+ end
379
+
380
+ ### ---------------------------------------------------------------
381
+ ### Provider reader methods
382
+ ### ---------------------------------------------------------------
383
+
384
+ ## 'init_has' is called when creating a new managed object
385
+ ## or when a caller attempts to retrieve a non-existing one
386
+
387
+ def init_has; nil end
388
+
389
+ ## 'xml_get_has_xml' - used to retrieve the starting location of the
390
+ ## actual XML data for the managed object (as compared to the top
391
+ ## of the configuration document
392
+
393
+ def xml_get_has_xml( xml ); nil end
394
+
395
+ ## 'xml_config_read!' is ued to retrieve the configuration
396
+ ## from the Junos device
397
+
398
+ def xml_config_read!
399
+ @ndev.rpc.get_configuration( xml_at_top )
400
+ end
401
+
402
+ def read!
403
+ @has.clear
404
+ cfg_xml = xml_config_read!
405
+ @has_xml = xml_get_has_xml( cfg_xml )
406
+
407
+ ## if the thing doesn't exist in Junos, then mark the @has
408
+ ## structure accordingly and call the object init_has for
409
+ ## any defaults
410
+
411
+ unless @has_xml
412
+ @has[:_exist] ||= false
413
+ @has[:_active] ||= true
414
+ init_has
415
+ return nil
416
+ end
417
+
418
+ ## xml_read_parser *MUST* be implmented by the provider class
419
+ ## it is used to parse the XML into the HASH structure. It
420
+ ## returns true/false
421
+
422
+ xml_read_parser( @has_xml, @has )
423
+
424
+ ## return the Hash representation
425
+ self.has
426
+ end
427
+
428
+ ### ---------------------------------------------------------------
429
+ ### Provider writer methods
430
+ ### ---------------------------------------------------------------
431
+
432
+ def need_write?; not @should.empty? end
433
+
434
+ def write!
435
+ return nil if @should.empty?
436
+
437
+ @should[:_exist] ||= true
438
+
439
+ # create the necessary chagnes and push them to the Junos
440
+ # device. If an error occurs, it will be raised
441
+
442
+ xml_change = xml_build_change
443
+ return nil unless xml_change
444
+ rsp = write_xml_config!( xml_change )
445
+
446
+ # copy the 'should' values into the 'has' values now that
447
+ # they've been written back to Junos
448
+
449
+ @has.merge! @should
450
+ @should.clear
451
+
452
+ # returning 'true' for now. might need to change this back
453
+ # to 'rsp' depending on the community feedback. general approach is to not have to
454
+ # deal with XML, unless it's an exception case. the only time rsp is really
455
+ # needed is to look at warnings; i.e. not-errors. errors will generate an exception, yo!
456
+
457
+ return true
458
+ end
459
+
460
+ ### ---------------------------------------------------------------
461
+ ### XML writer methods
462
+ ### ---------------------------------------------------------------
463
+
464
+ def xml_at_edit; nil; end
465
+ def xml_at_top; nil; end
466
+ def xml_on_create( xml ); nil; end
467
+ def xml_on_delete( xml ); nil; end
468
+
469
+ def xml_change__exist( xml )
470
+ return xml_on_create( xml ) if @should[:_exist]
471
+
472
+ par = xml.instance_variable_get(:@parent)
473
+ par['delete'] = 'delete'
474
+
475
+ return xml_on_delete( xml )
476
+ end
477
+
478
+ ## 'xml_build_change' is used to create the Junos XML
479
+ ## configuration structure. Generally speaking it
480
+ ## should not be called by code outside the providers,
481
+ ## but sometimes we might want to, so don't make it private
482
+
483
+ def xml_build_change( xml_at_here = nil )
484
+ edit_at = xml_at_here || xml_at_edit || xml_at_top
485
+
486
+ if @should[:_exist] == false
487
+ xml_change__exist( edit_at )
488
+ return edit_at.doc.root
489
+ end
490
+
491
+ changed = false
492
+ @should.keys.each do |prop|
493
+ changed = true if self.send( "xml_change_#{prop}", edit_at )
494
+ end
495
+ (changed) ? edit_at.doc.root : nil
496
+ end
497
+
498
+ ### ---------------------------------------------------------------
499
+ ### XML common write "change" methods
500
+ ### ---------------------------------------------------------------
501
+
502
+ def xml_change_admin( xml )
503
+ xml.disable (@should[:admin] == :up ) ? Netconf::JunosConfig::DELETE : nil
504
+ end
505
+
506
+ def xml_change_description( xml )
507
+ xml_set_or_delete( xml, 'description', @should[:description] )
508
+ end
509
+
510
+ def xml_change__active( xml )
511
+ par = xml.instance_variable_get(:@parent)
512
+ value = @should[:_active] ? 'active' : 'inactive'
513
+ par[value] = value # attribute name is same as value
514
+ end
515
+
516
+ ### ---------------------------------------------------------------
517
+ ### 'to_h' lets us look at the read/write hash structures
518
+ ### ---------------------------------------------------------------
519
+
520
+ def to_h( which = :read )
521
+ { @name => (which == :read) ? @has : @should }
522
+ end
523
+
524
+ ##### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
525
+ ##### !!!!! PRIVATE METHODS !!!!!
526
+ ##### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
527
+
528
+ private
529
+
530
+ def set_has_status( xml, has )
531
+ has[:_active] = xml['inactive'] ? false : true
532
+ has[:_exist] = true
533
+ end
534
+
535
+ ### ---------------------------------------------------------------
536
+ ### write configuration to Junos. Check for errors vs. warnings.
537
+ ### if there are warnings then return the result. If there are
538
+ ### errors, re-throw the exception object. If everything was
539
+ ### OK, simply return the result
540
+ ### ---------------------------------------------------------------
541
+
542
+ def write_xml_config!( xml, opts = {} )
543
+ begin
544
+ action = {'action' => 'replace' }
545
+ result = @ndev.rpc.load_configuration( xml, action )
546
+ rescue Netconf::RpcError => e
547
+ errs = e.rsp.xpath('//rpc-error')
548
+ raise e unless errs.empty?
549
+ e.rsp
550
+ else
551
+ result
552
+ end
553
+ end
554
+
555
+ def oh_no!
556
+ return if @opts[:ignore_raise]
557
+ yield if block_given? # should always be a block given ...
558
+ end
559
+
560
+ ### ---------------------------------------------------------------
561
+ ### XML property reader/writer for elements that can be present,
562
+ ### or existing with a "no-" prepended. For example "retain" or
563
+ ### "no-retain"
564
+ ### ---------------------------------------------------------------
565
+
566
+ def xml_read_parse_noele( as_xml, ele_name, as_hash, prop )
567
+ unless (ele = as_xml.xpath("#{ele_name} | no-#{ele_name}")).empty?
568
+ as_hash[prop] = (ele[0].name =~ /^no-/) ? false : true
569
+ end
570
+ end
571
+
572
+ def xml_set_or_delete_noele( xml, ele_name, prop = ele_name.to_sym )
573
+
574
+ # delete what was there
575
+ unless @has[prop].nil?
576
+ value_prop = @has[prop]
577
+ wr_ele_name = value_prop ? ele_name : 'no-' + ele_name
578
+ xml.send(wr_ele_name.to_sym, Netconf::JunosConfig::DELETE)
579
+ end
580
+
581
+ # if we're not adding anything back, signal that we've done
582
+ # something, and we're done, yo!
583
+ return true if @should[prop].nil?
584
+
585
+ # add new value
586
+ value_prop = @should[prop]
587
+ ele_name = 'no-' + ele_name if value_prop == false
588
+ xml.send( ele_name.to_sym )
589
+
590
+ end
591
+
592
+ def xml_when_item( xml_item, &block )
593
+ raise ArgumentError, "no block given" unless block_given?
594
+ return unless xml_item[0]
595
+ return yield(xml_item[0]) if block.arity == 1
596
+ yield
597
+ end
598
+
599
+ ### ---------------------------------------------------------------
600
+ ### XML property writer utilities
601
+ ### ---------------------------------------------------------------
602
+
603
+ def xml_set_or_delete( xml, ele_name, value )
604
+ xml.send( ele_name.to_sym, (value ? value : Netconf::JunosConfig::DELETE) )
605
+ end
606
+
607
+ def xml_set_or_delete_element( xml, ele_name, should )
608
+ xml.send( ele_name.to_sym, (should) ? nil : Netconf::JunosConfig::DELETE )
609
+ end
610
+
611
+ def diff_property_array( prop )
612
+ should = @should[prop] || []
613
+ has = @has[prop] || []
614
+ [ should - has, has - should ]
615
+ end
616
+
617
+ end
618
+
619
+