junos-ez-stdlib 0.0.12 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG.md +17 -3
  2. data/README.md +10 -0
  3. data/docs/Providers/IPports.md +61 -0
  4. data/docs/Providers/L1ports.md +29 -0
  5. data/docs/Providers/L2ports.md +43 -0
  6. data/docs/Providers/StaticHosts.md +26 -0
  7. data/docs/Providers/StaticRoutes.md +37 -0
  8. data/docs/Providers/UserAuths.md +32 -0
  9. data/docs/{Users.md → Providers/Users.md} +17 -0
  10. data/docs/Providers/Vlans.md +43 -0
  11. data/docs/Providers_Resources.md +24 -0
  12. data/docs/README_FIRST.md +27 -0
  13. data/docs/Utils/Config.md +161 -0
  14. data/docs/Utils/Filesystem.md +78 -0
  15. data/docs/Utils/Routing-Engine.md +248 -0
  16. data/examples/re_upgrade.rb +1 -1
  17. data/examples/re_utils.rb +1 -0
  18. data/examples/vlans.rb +7 -1
  19. data/lib/junos-ez/ip_ports.rb +3 -1
  20. data/lib/junos-ez/ip_ports/classic.rb +49 -14
  21. data/lib/junos-ez/l1_ports.rb +51 -11
  22. data/lib/junos-ez/l2_ports.rb +1 -1
  23. data/lib/junos-ez/l2_ports/vlan.rb +176 -60
  24. data/lib/junos-ez/provider.rb +4 -1
  25. data/lib/junos-ez/system/st_hosts.rb +0 -0
  26. data/lib/junos-ez/system/st_routes.rb +0 -0
  27. data/lib/junos-ez/system/userauths.rb +0 -0
  28. data/lib/junos-ez/system/users.rb +0 -0
  29. data/lib/junos-ez/utils/config.rb +33 -2
  30. data/lib/junos-ez/utils/re.rb +22 -3
  31. data/lib/junos-ez/vlans.rb +5 -3
  32. data/lib/junos-ez/vlans/vlan.rb +6 -0
  33. metadata +14 -13
  34. data/docs/Config_Utils.md +0 -3
  35. data/docs/Filesys_Utils.md +0 -3
  36. data/docs/IPports.md +0 -3
  37. data/docs/L1ports.md +0 -3
  38. data/docs/L2ports.md +0 -3
  39. data/docs/RE_utils.md +0 -3
  40. data/docs/StaticHosts.md +0 -3
  41. data/docs/StaticRoutes.md +0 -3
  42. data/docs/Vlans.md +0 -3
  43. data/lib/junos-ez/system/user_ssh_keys.rb +0 -113
@@ -0,0 +1,78 @@
1
+ # Filesystem Utilities
2
+
3
+ A collection of methods to access filesystem specific functions and information. These methods return data in
4
+ Hash / Array structures so the information can be programmatically accessible, rather than scraping CLI or navigating
5
+ Junos XML.
6
+
7
+ # USAGE
8
+ ```ruby
9
+
10
+ # bind :fs to access the file-system utilities
11
+
12
+ Junos::Ez::FS::Utils( ndev, :fs )
13
+
14
+ # get a listing of my home directory files:
15
+
16
+ pp ndev.fs.ls '/var/home/jeremy', :detail => true
17
+ ->
18
+ {"/var/home/jeremy"=>
19
+ {:fileblocks=>11244,
20
+ :files=>
21
+ "key1.pub"=>
22
+ {:owner=>"jeremy",
23
+ :group=>"staff",
24
+ :links=>1,
25
+ :size=>405,
26
+ :permissions_text=>"-rw-r--r--",
27
+ :permissions=>644,
28
+ :date=>"Apr 27 15:00",
29
+ :date_epoc=>1367074832},
30
+ "template-policy-options.conf"=>
31
+ {:owner=>"jeremy",
32
+ :group=>"staff",
33
+ :links=>1,
34
+ :size=>4320,
35
+ :permissions_text=>"-rw-r--r--",
36
+ :permissions=>644,
37
+ :date=>"Nov 6 2011",
38
+ :date_epoc=>1320564278}},
39
+ :dirs=>
40
+ {".ssh"=>
41
+ {:owner=>"jeremy",
42
+ :group=>"staff",
43
+ :links=>2,
44
+ :size=>512,
45
+ :permissions_text=>"drwxr-xr-x",
46
+ :permissions=>755,
47
+ :date=>"Apr 27 19:48",
48
+ :date_epoc=>1367092112},
49
+ "bak"=>
50
+ {:owner=>"jeremy",
51
+ :group=>"staff",
52
+ :links=>2,
53
+ :size=>512,
54
+ :permissions_text=>"drwxr-xr-x",
55
+ :permissions=>755,
56
+ :date=>"Apr 16 2010",
57
+ :date_epoc=>1271441068}}}}
58
+ ```
59
+
60
+ # METHODS
61
+
62
+ - `cat` - returns the String contents of a file
63
+ - `checksum` - returns the checksum of a file (MD5, SHA1, SHA256 options)
64
+ - `cleanup?` - returns a Hash of files that *would be* removed from "request system storage cleanup"
65
+ - `cleanup!` - "request system storage cleanup" (!! NO CONFIRM !!)
66
+ - `cp!` - copies a file relative on the device filesystem
67
+ - `cwd` - changes the current working directory
68
+ - `pwd` - returns a String of the current working directory
69
+ - `df` - "show system storage"
70
+ - `ls` - "file list", i.e. get a file / directory listing, returns a Hash
71
+ - `mv!` - "file move", i.e. move / rename files
72
+ - `rm!` - "file delete", i.e. deletes files
73
+
74
+ # GORY DETAILS
75
+
76
+ ... more docs comming ...
77
+
78
+
@@ -0,0 +1,248 @@
1
+ # Junos::Ez::RE::Utils
2
+
3
+ A collection of methods to access routing-engine specific functions and information. These methods return data in Hash / Array structures so the information can be programmatically accessible, rather than scraping CLI or navigating Junos XML.
4
+
5
+ # USAGE
6
+
7
+ ```ruby
8
+
9
+ # bind :re to access the routing-engine utitities
10
+ Junos::Ez::RE::Utils( ndev, :re )
11
+
12
+ # show the uptime information on this device
13
+ pp ndev.re.uptime
14
+ ->
15
+ {"re0"=>
16
+ {:time_now=>"2013-04-27 22:28:24 UTC",
17
+ :active_users=>1,
18
+ :load_avg=>[0.08, 0.05, 0.01],
19
+ :uptime=>{:at=>"10:28PM", :ago=>"27 days, 2:58"},
20
+ :time_boot=>{:at=>"2013-03-31 19:30:47 UTC", :ago=>"3w6d 02:57"},
21
+ :protocols_started=>{:at=>"2013-03-31 19:34:53 UTC", :ago=>"3w6d 02:53"},
22
+ :last_config=>
23
+ {:at=>"2013-04-27 19:48:42 UTC", :ago=>"02:39:42", :by=>"jeremy"}}}
24
+ ```
25
+
26
+ # METHODS
27
+
28
+ ## Information
29
+
30
+ - `status` - "show chassis routing-engine" information
31
+ - `uptime` - "show system uptime" information
32
+ - `system_alarms` - "show system alarms" information
33
+ - `chassis_alarms` - "show chassis alarms" information
34
+ - `memory` - "show system memory" information
35
+ - `users` - "show system users" information
36
+
37
+ ## Software Image
38
+
39
+ - `validate_software?` - "request system software validate..."
40
+ - `install_software!` - "request system software add ..."
41
+ - `rollback_software!` - "request system software rollback"
42
+
43
+ ## System Controls
44
+
45
+ - `reboot!` - "request system reboot" (!! NO CONFIRM !!)
46
+ - `shutdown!` - "request system power-off" (!! NO CONFIRM !!)
47
+
48
+ # GORY DETAILS
49
+
50
+ ## status
51
+
52
+ Returns a Hash structure of "show chassis routing-engine" information. Each Hash key is the RE identifier. For example, on a target with a single RE:
53
+ ```ruby
54
+ pp ndev.re.status
55
+ ->
56
+ {"re0"=>
57
+ {:model=>"JUNOSV-FIREFLY RE",
58
+ :serialnumber=>"",
59
+ :temperature=>{:system=>"", :cpu=>""},
60
+ :memory=>{:total_size=>0, :buffer_util=>0},
61
+ :cpu_util=>{:user=>0, :background=>0, :system=>2, :interrupt=>0, :idle=>98},
62
+ :uptime=>
63
+ {:at=>"2013-05-02 17:37:51 UTC",
64
+ :ago=>"3 minutes, 4 seconds",
65
+ :reboot_reason=>"Router rebooted after a normal shutdown."},
66
+ :load_avg=>[0.06, 0.13, 0.07]}}
67
+ ```
68
+
69
+ ## uptime
70
+
71
+ Returns a Hash structure of "show system uptime" information. Each Hash key is the RE identifier. For example, on a target with a single RE:
72
+ ```ruby
73
+ pp ndev.re.uptime
74
+ ->
75
+ {"re0"=>
76
+ {:time_now=>"2013-05-02 17:42:09 UTC",
77
+ :active_users=>0,
78
+ :load_avg=>[0.02, 0.1, 0.06],
79
+ :uptime=>{:at=>"5:42PM", :ago=>"4 mins"},
80
+ :time_boot=>{:at=>"2013-05-02 17:37:51 UTC", :ago=>"00:04:18"},
81
+ :protocols_started=>{:at=>"2013-05-02 17:38:08 UTC", :ago=>"00:04:01"},
82
+ :last_config=>
83
+ {:at=>"2013-04-27 15:00:55 UTC", :ago=>"5d 02:41", :by=>"root"}}}
84
+ ```
85
+ ## system_alarms
86
+
87
+ Returns an Array of Hash structure of "show system alarms" information. If there are no alarms, this method returns `nil`. For example, a target with a single alarm:
88
+ ```ruby
89
+ pp ndev.re.system_alarms
90
+ ->
91
+ [{:at=>"2013-05-02 17:38:03 UTC",
92
+ :class=>"Minor",
93
+ :description=>"Rescue configuration is not set",
94
+ :type=>"Configuration"}]
95
+ ```
96
+
97
+ ## chassis_alarms
98
+
99
+ Returns an Array Hash structure of "show chassis alarms" information. If there are no alarms, this method returns `nil`. For example, a target with no chassis alarms:
100
+ ```ruby
101
+ pp ndev.re.chassis_alarms
102
+ ->
103
+ nil
104
+ ```
105
+
106
+ ## memory
107
+
108
+ Returns a Hash structure of "show system memory" information. Each key is the RE indentifier. A target with a single RE would look like the following. Note that the `:procs` Array is the process array, with each element as a Hash of process specific information.
109
+ ```ruby
110
+ pp ndev.re.memory
111
+ ->
112
+ {"re0"=>
113
+ {:memory_summary=>
114
+ {:total=>{:size=>1035668, :percentage=>100},
115
+ :reserved=>{:size=>18688, :percentage=>1},
116
+ :wired=>{:size=>492936, :percentage=>47},
117
+ :active=>{:size=>184152, :percentage=>17},
118
+ :inactive=>{:size=>65192, :percentage=>6},
119
+ :cache=>{:size=>261140, :percentage=>25},
120
+ :free=>{:size=>12660, :percentage=>1}},
121
+ :procs=>
122
+ [{:name=>"kernel",
123
+ :pid=>0,
124
+ :size=>569704,
125
+ :size_pct=>54.49,
126
+ :resident=>90304,
127
+ :resident_pct=>8.71},
128
+ {:name=>"/sbin/pmap",
129
+ :pid=>2768,
130
+ :size=>4764,
131
+ :size_pct=>0.15,
132
+ :resident=>1000,
133
+ :resident_pct=>0.09},
134
+ {:name=>"file: (mgd) /proc/2766/file (jeremy)",
135
+ :pid=>2765,
136
+ :size=>727896,
137
+ :size_pct=>23.16,
138
+ :resident=>18904,
139
+ :resident_pct=>1.82},
140
+ #
141
+ # snip, omitted full array for sake of sanity ...
142
+ #
143
+ ]}}
144
+ ```
145
+
146
+ ## users
147
+
148
+ Returns a Array structure of "show system users" information. Each Array item is a Hash structure of user information. A target with a single user logged in would look like:
149
+ ```ruby
150
+ pp ndev.re.users
151
+ ->
152
+ [{:name=>"jeremy",
153
+ :tty=>"p0",
154
+ :from=>"192.168.56.1",
155
+ :login_time=>"5:45PM",
156
+ :idle_time=>"",
157
+ :command=>"-cli (cli)"}]
158
+ ```
159
+
160
+ ## validate_software?
161
+
162
+ Performs the equivalent of "request system software validate..." and returns `true` if the software passes validation or a String indicating the error message. The following is an example that simply checks for true:
163
+ ```ruby
164
+ unless ndev.re.validate_software?( file_on_junos )
165
+ puts "The softare does not validate!"
166
+ ndev.close
167
+ exit 1
168
+ end
169
+ ```
170
+
171
+ ## install_software!
172
+
173
+ Performs the equivalent of "request system software add ..." and returns `true` if the operation was successful or a String indicating the error message. The following example illustrates an error message:
174
+
175
+ ```ruby
176
+ puts "Installing image ... please wait ..."
177
+ rc = ndev.re.install_software!( :package => file_on_junos, :no_validate => true )
178
+ if rc != true
179
+ puts rc
180
+ end
181
+ ```
182
+ With the results of the `rc` String:
183
+ ```
184
+ Verified junos-boot-vsrx-12.1I20130415_junos_121_x44_d15.0-576602.tgz signed by PackageDevelopment_12_1_0
185
+ Verified junos-vsrx-12.1I20130415_junos_121_x44_d15.0-576602-domestic signed by PackageDevelopment_12_1_0
186
+
187
+ WARNING: The software that is being installed has limited support.
188
+ WARNING: Run 'file show /etc/notices/unsupported.txt' for details.
189
+
190
+ Available space: -49868 require: 4641
191
+
192
+ WARNING: The /cf filesystem is low on free disk space.
193
+ WARNING: This package requires 4641k free, but there
194
+ WARNING: is only -49868k available.
195
+
196
+ WARNING: This installation attempt will be aborted.
197
+ WARNING: If you wish to force the installation despite these warnings
198
+ WARNING: you may use the 'force' option on the command line.
199
+ ERROR: junos-12.1I20130415_junos_121_x44_d15.0-576602-domestic fails requirements check
200
+ Installation failed for package '/var/tmp/junos-vsrx-domestic.tgz'
201
+ WARNING: Not enough space in /var/tmp to unpack junos-12.1I20130415_junos_121_x44_d15.0-576602.tgz
202
+ WARNING: Use 'request system storage cleanup' and
203
+ WARNING: the 'unlink' option to improve the chances of success
204
+ ```
205
+
206
+ ## rollback_software!
207
+
208
+ Performs the equivalent of "request system software rollback". The result of the operation is returned as a String. For example, a successful rollback would look like this:
209
+ ```ruby
210
+ pp ndev.re.rollback_software!
211
+ ->
212
+ "Restoring boot file package\njunos-12.1I20130422_2129_jni-domestic will become active at next reboot\nWARNING: A reboot is required to load this software correctly\nWARNING: Use the 'request system reboot' command\nWARNING: when software installation is complete"
213
+ ```
214
+ An unsuccessful rollback would look like this:
215
+ ```ruby
216
+ pp ndev.re.rollback_software!
217
+ ->
218
+ "WARNING: Cannot rollback, /packages/junos is not valid"
219
+ ```
220
+
221
+ ## reboot!( opts = {} )
222
+
223
+ Performs the "request system reboot" action. There is **NO** confirmation prompt, so once you've executed this method, the action begins. Once this command executes the NETCONF session to the target will eventually terminate. You can trap the `Net::SSH::Disconnect` exception to detect this event.
224
+
225
+ The option Hash provides for the following controls:
226
+ ```
227
+ :in => Fixnum
228
+ ```
229
+ Instructs Junos to reboot after `:in` minutes from the time of calling `reboot!`
230
+ ```
231
+ :at => String
232
+ ```
233
+ Instructs Junos to reboot at a specific date and time. The format of `:at` is YYYYMMDDHHMM, where HH is the 24-hour (military) time. For example HH = 01 is 1am and HH=13 is 1pm. If you omit the YYYY, MM, or DD options the current values apply. For example `:at => 1730` is 1:30pm today.
234
+
235
+ ## shutdown!( opts = {} )
236
+
237
+ Performs the "request system power-off" action. There is **NO** confirmation prompt, so once you've executed this method, the action begins. Once this command executes the NETCONF session to the target will eventually terminate. You can trap the `Net::SSH::Disconnect` exception to detect this event.
238
+
239
+ The option Hash provides for the following controls:
240
+ ```
241
+ :in => Fixnum
242
+ ```
243
+ Instructs Junos to reboot after `:in` minutes from the time of calling `reboot!`
244
+ ```
245
+ :at => String
246
+ ```
247
+ Instructs Junos to reboot at a specific date and time. The format of `:at` is YYYYMMDDHHMM, where HH is the 24-hour (military) time. For example HH = 01 is 1am and HH=13 is 1pm. If you omit the YYYY, MM, or DD options the current values apply. For example `:at => 1730` is 1:30pm today.
248
+
@@ -73,7 +73,7 @@ unless ndev.re.validate_software?( file_on_junos )
73
73
  exit 1
74
74
  end
75
75
 
76
- puts "Installing image ... place wait ..."
76
+ puts "Installing image ... please wait ..."
77
77
  rc = ndev.re.install_software!( :package => file_on_junos, :no_validate => true )
78
78
  if rc != true
79
79
  puts rc
data/examples/re_utils.rb CHANGED
@@ -24,6 +24,7 @@ puts "OK!"
24
24
  Junos::Ez::Provider( ndev )
25
25
  Junos::Ez::RE::Utils( ndev, :re )
26
26
  Junos::Ez::FS::Utils( ndev, :fs )
27
+ Junos::Ez::Config::Utils( ndev, :cu )
27
28
 
28
29
  binding.pry
29
30
 
data/examples/vlans.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  require 'net/netconf/jnpr'
2
2
  require 'junos-ez/stdlib'
3
3
 
4
- # login information for NETCONF session
4
+ unless ARGV[0]
5
+ puts "You must specify a target"
6
+ exit 1
7
+ end
5
8
 
9
+ # login information for NETCONF session
6
10
  login = { :target => ARGV[0], :username => 'jeremy', :password => 'jeremy1', }
7
11
 
8
12
  ## create a NETCONF object to manage the device and open the connection ...
@@ -15,7 +19,9 @@ $stdout.puts "OK!"
15
19
  Junos::Ez::Provider( ndev )
16
20
  Junos::Ez::Config::Utils( ndev, :cu )
17
21
  Junos::Ez::Vlans::Provider( ndev, :vlans )
22
+ Junos::Ez::L1ports::Provider( ndev, :l1_ports )
18
23
  Junos::Ez::L2ports::Provider( ndev, :l2_ports )
24
+ Junos::Ez::IPports::Provider( ndev, :ip_ports )
19
25
 
20
26
  pp ndev.vlans.list
21
27
  pp ndev.vlans.catalog
@@ -8,7 +8,9 @@ module Junos::Ez::IPports
8
8
  :description, # general description text
9
9
  :tag_id, # VLAN tag-id for vlan-tag enabled ports
10
10
  :mtu, # MTU value as number
11
- :address # ip/prefix as text, e.g. "192.168.10.22/24"
11
+ :address, # ip/prefix as text, e.g. "192.168.10.22/24"
12
+ :acl_in, # input ACL name
13
+ :acl_out, # output ACL name
12
14
  ]
13
15
 
14
16
  def self.Provider( ndev, varsym )
@@ -7,13 +7,13 @@ class Junos::Ez::IPports::Provider::CLASSIC < Junos::Ez::IPports::Provider
7
7
  def xml_at_top
8
8
 
9
9
  # if just the IFD is given as the name, default to unit "0"
10
- @ifd, @ifl = @name.split '.'
11
- @ifl ||= "0"
10
+ @ifd, @ifd_unit = @name.split '.'
11
+ @ifd_unit ||= "0"
12
12
 
13
13
  Nokogiri::XML::Builder.new{ |x| x.configuration{
14
14
  x.interfaces { x.interface { x.name @ifd
15
15
  x.unit {
16
- x.name @ifl
16
+ x.name @ifd_unit
17
17
  return x
18
18
  }
19
19
  }}
@@ -43,14 +43,22 @@ class Junos::Ez::IPports::Provider::CLASSIC < Junos::Ez::IPports::Provider
43
43
 
44
44
  def xml_read_parser( as_xml, as_hash )
45
45
  set_has_status( as_xml, as_hash )
46
-
46
+
47
+ as_hash[:admin] = as_xml.xpath('disable').empty? ? :up : :down
47
48
  ifa_inet = as_xml.xpath('family/inet')
48
49
 
49
- as_hash[:tag_id] = as_xml.xpath('vlan-id').text.to_i
50
- as_hash[:description] = as_xml.xpath('description').text
51
- as_hash[:mtu] = ifa_inet.xpath('mtu').text.to_i || nil
50
+ xml_when_item(as_xml.xpath('vlan-id')){ |i| as_hash[:tag_id] = i.text.to_i }
51
+ xml_when_item(as_xml.xpath('description')){ |i| as_hash[:description] = i.text }
52
+ xml_when_item(ifa_inet.xpath('mtu')){ |i| as_hash[:mtu] = i.text.to_i }
53
+
54
+ # @@@ assuming a single IP address; prolly need to be more specific ...
52
55
  as_hash[:address] = ifa_inet.xpath('address/name').text || nil
53
- as_hash[:admin] = as_xml.xpath('disable').empty? ? :up : :down
56
+
57
+ # check for firewall-filters (aka ACLs)
58
+ if (fw_acl = ifa_inet.xpath('filter')[0])
59
+ xml_when_item( fw_acl.xpath('input/filter-name')){ |i| as_hash[:acl_in] = i.text.strip }
60
+ xml_when_item( fw_acl.xpath('output/filter-name')){ |i| as_hash[:acl_out] = i.text.strip }
61
+ end
54
62
 
55
63
  return true
56
64
  end
@@ -61,14 +69,11 @@ class Junos::Ez::IPports::Provider::CLASSIC < Junos::Ez::IPports::Provider
61
69
 
62
70
  def xml_change_address( xml )
63
71
  xml.family { xml.inet {
72
+ # delete the old address and replace it with the new one ...
64
73
  if @has[:address]
65
- xml.address( Netconf::JunosConfig::DELETE ) {
66
- xml.name @has[:address]
67
- }
74
+ xml.address( Netconf::JunosConfig::DELETE ) { xml.name @has[:address] }
68
75
  end
69
- xml.address {
70
- xml.name @should[:address]
71
- }
76
+ xml.address { xml.name @should[:address] }
72
77
  }}
73
78
  end
74
79
 
@@ -81,9 +86,39 @@ class Junos::Ez::IPports::Provider::CLASSIC < Junos::Ez::IPports::Provider
81
86
  xml_set_or_delete( xml, 'mtu', @should[:mtu] )
82
87
  }}
83
88
  end
89
+
90
+ def xml_change_acl_in( xml )
91
+ xml.family { xml.inet { xml.filter { xml.input {
92
+ xml_set_or_delete( xml, 'filter-name', @should[:acl_in] )
93
+ }}}}
94
+ end
95
+
96
+ def xml_change_acl_out( xml )
97
+ xml.family { xml.inet { xml.filter { xml.output {
98
+ xml_set_or_delete( xml, 'filter-name', @should[:acl_out] )
99
+ }}}}
100
+ end
84
101
 
85
102
  end
86
103
 
104
+ ##### ---------------------------------------------------------------
105
+ ##### Resource Methods
106
+ ##### ---------------------------------------------------------------
107
+
108
+ class Junos::Ez::IPports::Provider::CLASSIC
109
+ def status
110
+ got = @ndev.rpc.get_interface_information( :interface_name => @ifd+'.'+@ifd_unit )
111
+ ifs = got.xpath('logical-interface')[0]
112
+ ret_h = {}
113
+ ret_h[:l1_oper_status] = (ifs.xpath('if-config-flags/iff-device-down')[0]) ? :down : :up
114
+ ret_h[:oper_status] = (ifs.xpath('address-family//ifaf-down')[0]) ? :down : :up
115
+ ret_h[:snmp_index] = ifs.xpath('snmp-index').text.to_i
116
+ ret_h[:packets_rx] = ifs.xpath('traffic-statistics/input-packets').text.to_i
117
+ ret_h[:packets_tx] = ifs.xpath('traffic-statistics/output-packets').text.to_i
118
+ ret_h
119
+ end
120
+ end
121
+
87
122
  ##### ---------------------------------------------------------------
88
123
  ##### Provider collection methods
89
124
  ##### ---------------------------------------------------------------