shopify-junos-ez-stdlib 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +91 -0
- data/LICENSE +26 -0
- data/README.md +199 -0
- data/docs/Facts.md +192 -0
- data/docs/Providers/Group.md +61 -0
- data/docs/Providers/IPports.md +61 -0
- data/docs/Providers/L1ports.md +29 -0
- data/docs/Providers/L2ports.md +43 -0
- data/docs/Providers/LAGports.md +57 -0
- data/docs/Providers/StaticHosts.md +26 -0
- data/docs/Providers/StaticRoutes.md +37 -0
- data/docs/Providers/UserAuths.md +32 -0
- data/docs/Providers/Users.md +122 -0
- data/docs/Providers/Vlans.md +43 -0
- data/docs/Providers_Resources.md +353 -0
- data/docs/README_FIRST.md +27 -0
- data/docs/Utils/Config.md +160 -0
- data/docs/Utils/Filesystem.md +360 -0
- data/docs/Utils/Routing-Engine.md +379 -0
- data/docs/Utils/SCP.md +24 -0
- data/examples/config/config_file.rb +72 -0
- data/examples/config/config_template_object.rb +81 -0
- data/examples/config/config_template_simple.rb +76 -0
- data/examples/config/multi_config.rb +60 -0
- data/examples/fs_utils.rb +31 -0
- data/examples/lag_port.rb +27 -0
- data/examples/re_upgrade.rb +99 -0
- data/examples/re_utils.rb +33 -0
- data/examples/simple.rb +46 -0
- data/examples/st_hosts.rb +33 -0
- data/examples/user.rb +32 -0
- data/examples/vlans.rb +31 -0
- data/junos-ez-stdlib.gemspec +15 -0
- data/lib/junos-ez/exceptions.rb +3 -0
- data/lib/junos-ez/facts.rb +83 -0
- data/lib/junos-ez/facts/chassis.rb +51 -0
- data/lib/junos-ez/facts/ifd_style.rb +17 -0
- data/lib/junos-ez/facts/personality.rb +25 -0
- data/lib/junos-ez/facts/switch_style.rb +31 -0
- data/lib/junos-ez/facts/version.rb +58 -0
- data/lib/junos-ez/group.rb +206 -0
- data/lib/junos-ez/ip_ports.rb +30 -0
- data/lib/junos-ez/ip_ports/classic.rb +188 -0
- data/lib/junos-ez/l1_ports.rb +121 -0
- data/lib/junos-ez/l1_ports/classic.rb +87 -0
- data/lib/junos-ez/l1_ports/switch.rb +134 -0
- data/lib/junos-ez/l2_ports.rb +66 -0
- data/lib/junos-ez/l2_ports/bridge_domain.rb +499 -0
- data/lib/junos-ez/l2_ports/vlan.rb +433 -0
- data/lib/junos-ez/l2_ports/vlan_l2ng.rb +502 -0
- data/lib/junos-ez/lag_ports.rb +268 -0
- data/lib/junos-ez/provider.rb +619 -0
- data/lib/junos-ez/stdlib.rb +18 -0
- data/lib/junos-ez/system.rb +48 -0
- data/lib/junos-ez/system/st_hosts.rb +92 -0
- data/lib/junos-ez/system/st_routes.rb +159 -0
- data/lib/junos-ez/system/syscfg.rb +103 -0
- data/lib/junos-ez/system/userauths.rb +84 -0
- data/lib/junos-ez/system/users.rb +217 -0
- data/lib/junos-ez/utils/config.rb +236 -0
- data/lib/junos-ez/utils/fs.rb +385 -0
- data/lib/junos-ez/utils/re.rb +558 -0
- data/lib/junos-ez/version.rb +6 -0
- data/lib/junos-ez/vlans.rb +38 -0
- data/lib/junos-ez/vlans/bridge_domain.rb +89 -0
- data/lib/junos-ez/vlans/vlan.rb +119 -0
- data/lib/junos-ez/vlans/vlan_l2ng.rb +126 -0
- data/shipit.yml +4 -0
- data/tmp +7 -0
- 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
|
+
|