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.
- 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
|
+
|