shopify-junos-ez-stdlib 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+