junos-ez-stdlib 0.1.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +6 -14
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +8 -0
  5. data/.travis.yml +18 -0
  6. data/CHANGELOG.md +60 -19
  7. data/Gemfile +7 -0
  8. data/README.md +41 -30
  9. data/Rakefile +6 -0
  10. data/SUGGESTION-BOX/README.md +32 -0
  11. data/docs/Providers/Group.md +61 -0
  12. data/docs/Providers/L2ports.md +1 -1
  13. data/docs/Providers/LAGports.md +57 -0
  14. data/docs/Providers/Vlans.md +1 -1
  15. data/examples/config/config_file.rb +0 -0
  16. data/examples/config/config_template_object.rb +0 -0
  17. data/examples/config/config_template_simple.rb +0 -0
  18. data/examples/config/load_sample.conf +129 -0
  19. data/examples/config/load_sample.set +3 -0
  20. data/examples/config/load_template_main.conf +7 -0
  21. data/examples/config/load_template_object.conf +7 -0
  22. data/examples/config/multi_config.rb +0 -0
  23. data/examples/fs_utils.rb +0 -0
  24. data/examples/lag_port.rb +27 -0
  25. data/examples/re_upgrade.rb +0 -0
  26. data/examples/re_utils.rb +0 -0
  27. data/examples/simple.rb +0 -1
  28. data/examples/st_hosts.rb +0 -0
  29. data/examples/user.rb +0 -0
  30. data/examples/vlans.rb +4 -4
  31. data/junos-ez-stdlib.gemspec +25 -14
  32. data/lib/junos-ez/exceptions.rb +0 -0
  33. data/lib/junos-ez/facts.rb +5 -7
  34. data/lib/junos-ez/facts/chassis.rb +6 -0
  35. data/lib/junos-ez/facts/ifd_style.rb +6 -3
  36. data/lib/junos-ez/facts/personality.rb +6 -6
  37. data/lib/junos-ez/facts/switch_style.rb +11 -2
  38. data/lib/junos-ez/facts/version.rb +24 -9
  39. data/lib/junos-ez/group.rb +206 -0
  40. data/lib/junos-ez/ip_ports.rb +0 -0
  41. data/lib/junos-ez/ip_ports/classic.rb +2 -2
  42. data/lib/junos-ez/l1_ports.rb +0 -0
  43. data/lib/junos-ez/l1_ports/classic.rb +0 -0
  44. data/lib/junos-ez/l1_ports/switch.rb +0 -0
  45. data/lib/junos-ez/l2_ports.rb +18 -9
  46. data/lib/junos-ez/l2_ports/bridge_domain.rb +499 -0
  47. data/lib/junos-ez/l2_ports/vlan.rb +3 -3
  48. data/lib/junos-ez/l2_ports/vlan_l2ng.rb +502 -0
  49. data/lib/junos-ez/lag_ports.rb +268 -0
  50. data/lib/junos-ez/provider.rb +4 -8
  51. data/lib/junos-ez/stdlib.rb +2 -0
  52. data/lib/junos-ez/system.rb +0 -0
  53. data/lib/junos-ez/system/users.rb +5 -7
  54. data/lib/junos-ez/utils/config.rb +0 -0
  55. data/lib/junos-ez/utils/fs.rb +0 -0
  56. data/lib/junos-ez/utils/re.rb +0 -0
  57. data/lib/junos-ez/version.rb +4 -1
  58. data/lib/junos-ez/vlans.rb +4 -1
  59. data/lib/junos-ez/vlans/bridge_domain.rb +7 -3
  60. data/lib/junos-ez/vlans/vlan.rb +4 -3
  61. data/lib/junos-ez/vlans/vlan_l2ng.rb +126 -0
  62. metadata +142 -64
@@ -4,7 +4,11 @@ Junos::Ez::Facts::Keeper.define( :version ) do |ndev, facts|
4
4
 
5
5
  case f_persona
6
6
  when :MX
7
- swver = ndev.rpc.command "show version invoke-on all-routing-engines"
7
+ begin
8
+ swver = ndev.rpc.command "show version invoke-on all-routing-engines"
9
+ rescue Netconf::RpcError
10
+ swver = ndev.rpc.command "show version"
11
+ end
8
12
  when :SWITCH
9
13
  ## most EX switches support the virtual-chassis feature, so the 'all-members' option would be valid
10
14
  ## in some products, this options is not valid (i.e. not vc-capable. so we're going to try for vc, and if that
@@ -26,18 +30,29 @@ Junos::Ez::Facts::Keeper.define( :version ) do |ndev, facts|
26
30
  swver_infos = swver.xpath('//software-information')
27
31
  swver_infos.each do |re_sw|
28
32
  re_name = re_sw.xpath('preceding-sibling::re-name').text.upcase
29
- re_sw.xpath('package-information[1]/comment').text =~ /\[(.*)\]/
30
33
  ver_key = ('version_' + re_name).to_sym
31
- facts[ver_key] = $1
34
+
35
+ if re_sw.at_xpath('//junos-version')
36
+ facts[ver_key] = re_sw.xpath('//junos-version').text
37
+ else
38
+ re_sw.xpath('package-information[1]/comment').text =~ /\[(.*)\]/
39
+ facts[ver_key] = $1
40
+ end
32
41
  end
33
42
  master_id = f_master
34
- facts[:version] =
35
- facts[("version_" + "RE" + master_id).to_sym] ||
36
- facts[('version_' + "FPC" + master_id).to_sym]
43
+ unless master_id.nil?
44
+ facts[:version] =
45
+ facts[("version_" + "RE" + master_id).to_sym] ||
46
+ facts[('version_' + "FPC" + master_id).to_sym]
47
+ end
37
48
  else
38
- junos = swver.xpath('//package-information[name = "junos"]/comment').text
39
- junos =~ /\[(.*)\]/
40
- facts[:version] = $1
49
+ if swver.at_xpath('//junos-version')
50
+ facts[:version] = swver.xpath('//junos-version').text
51
+ else
52
+ junos = swver.xpath('//package-information[name = "junos"]/comment').text
53
+ junos =~ /\[(.*)\]/
54
+ facts[:version] = $1
55
+ end
41
56
  end
42
57
 
43
58
  end
@@ -0,0 +1,206 @@
1
+ require "junos-ez/provider"
2
+
3
+ module Junos::Ez::Group
4
+
5
+ PROPERTIES = [
6
+ :format, # [:set, :text, :xml]
7
+ :path, # Configuration file path
8
+ ]
9
+
10
+ def self.Provider( ndev, varsym )
11
+ newbie = Junos::Ez::Group::Provider::new( ndev )
12
+ newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES
13
+ Junos::Ez::Provider.attach_instance_variable( ndev, varsym, newbie )
14
+ end
15
+
16
+ class Provider < Junos::Ez::Provider::Parent
17
+ # common parenting goes here ... if we were to
18
+ # subclass the objects ... not doing that now
19
+ end
20
+
21
+ end
22
+
23
+ class Junos::Ez::Group::Provider
24
+
25
+ ### ---------------------------------------------------------------
26
+ ### XML top placement
27
+ ### ---------------------------------------------------------------
28
+
29
+ def xml_at_top
30
+ xml = Nokogiri::XML::Builder.new {|xml| xml.configuration {
31
+ xml.groups {
32
+ xml.name @name
33
+ return xml
34
+ }
35
+ }}
36
+ end
37
+
38
+ ### ---------------------------------------------------------------
39
+ ### XML property readers
40
+ ### ---------------------------------------------------------------
41
+
42
+ def xml_get_has_xml( xml )
43
+ xml.xpath('//groups')[0]
44
+ end
45
+
46
+ def xml_read_parser( as_xml, as_hash )
47
+ set_has_status( as_xml, as_hash )
48
+
49
+ grp = as_xml.xpath('name').text
50
+ as_hash[:name] = grp unless grp.empty?
51
+
52
+ end
53
+
54
+
55
+ ### ---------------------------------------------------------------
56
+ ### XML property writers
57
+ ### ---------------------------------------------------------------
58
+
59
+ def xml_change_path( xml )
60
+ end
61
+
62
+ def xml_change_format( xml )
63
+ end
64
+
65
+ ### ---------------------------------------------------------------
66
+ ### XML on-create
67
+ ### ---------------------------------------------------------------
68
+
69
+ def xml_on_create( xml )
70
+ end
71
+
72
+ ### ---------------------------------------------------------------
73
+ ### XML on-delete
74
+ ### ---------------------------------------------------------------
75
+ def xml_on_delete( xml )
76
+ end
77
+
78
+ def write_xml_config!( xml, opts = {} )
79
+ if (@should[:_exist] == true)
80
+ _load ( xml )
81
+ @should[:format] = 'xml' unless @should[:format]
82
+ begin
83
+ attr = {}
84
+ attr[:action] = 'replace'
85
+ attr[:format] = @should[:format].to_s
86
+ result = @ndev.rpc.load_configuration( @config.to_s, attr )
87
+ rescue Netconf::RpcError => e
88
+ errs = e.rsp.xpath('//rpc-error[error-severity = "error"]')
89
+ raise e unless errs.empty?
90
+ e.rsp
91
+ else
92
+ result
93
+ end
94
+ else
95
+ super(xml)
96
+ end
97
+ _apply_group
98
+ end
99
+
100
+ def write!
101
+ return nil if @should.empty?
102
+
103
+ @should[:_exist] ||= true
104
+ @should[:_active] ||= :true
105
+ # load the conifguration from file and apply under group
106
+ # hirerachy
107
+ rsp = write_xml_config!( xml_at_top.doc.root )
108
+
109
+ # copy the 'should' values into the 'has' values now that
110
+ # they've been written back to Junos
111
+
112
+ @has.merge! @should
113
+ @should.clear
114
+
115
+ return true
116
+ end
117
+
118
+ end
119
+
120
+
121
+ ##### ---------------------------------------------------------------
122
+ ##### Provider collection methods
123
+ ##### ---------------------------------------------------------------
124
+
125
+ class Junos::Ez::Group::Provider
126
+
127
+ def build_list
128
+ grp_cfgs = @ndev.rpc.get_configuration{|xml|
129
+ xml.send(:'groups')
130
+ }.xpath('groups/name').collect do |item|
131
+ item.text
132
+ end
133
+ return grp_cfgs
134
+ end
135
+
136
+ def build_catalog
137
+ return @catalog if list!.empty?
138
+ list.each do |grp_name|
139
+ @ndev.rpc.get_configuration{ |xml|
140
+ xml.groups {
141
+ xml.name grp_name
142
+ }
143
+ }.xpath('groups').each do |as_xml|
144
+ @catalog[grp_name] = {}
145
+ xml_read_parser( as_xml, @catalog[grp_name] )
146
+ end
147
+ end
148
+ @catalog
149
+ end
150
+ end
151
+
152
+ ##### ---------------------------------------------------------------
153
+ ##### _PRIVATE methods
154
+ ##### ---------------------------------------------------------------
155
+
156
+ class Junos::Ez::Group::Provider
157
+
158
+ def _load ( xml )
159
+ return @config = nil if ( @should[:_exist] == false )
160
+ admin = ''
161
+ if @should[:format].to_s == 'set'
162
+ @config = "\ndelete groups #{@name}\n" +
163
+ "edit groups #{@name}\n" +
164
+ File.read( @should[:path] )
165
+ admin = @should[:_active] == :false ? 'deactivate' : 'activate'
166
+ @config += "\nquit\n"
167
+ @config += "\n#{admin} groups #{@name}"
168
+
169
+ elsif @should[:format].to_s == 'text'
170
+ admin = @should[:_active] == :false ? 'inactive' : 'active'
171
+ admin += ": " unless admin.empty?
172
+ @config = "groups {\n#{admin} replace: #{@name} {\n" +
173
+ File.read( @should[:path] ) + "\n}\n}"
174
+
175
+ elsif @should[:format].to_s == 'xml'
176
+ xml.at_xpath('groups') << File.read( @should[:path])
177
+ @config = xml
178
+ end
179
+ return @config
180
+ end
181
+
182
+ def _apply_group
183
+ cfg = Netconf::JunosConfig.new(:TOP)
184
+ xml = cfg.doc
185
+ Nokogiri::XML::Builder.with( xml.at_xpath( 'configuration' )) do |dot|
186
+ if @config and @should[:_active] == :true
187
+ dot.send :'apply-groups', @name
188
+ else
189
+ dot.send :'apply-groups', @name, Netconf::JunosConfig::DELETE
190
+ end
191
+ end
192
+ begin
193
+ attr = {}
194
+ attr[:action] = 'replace'
195
+ attr[:format] = 'xml'
196
+ result = @ndev.rpc.load_configuration( xml, attr )
197
+ rescue Netconf::RpcError => e
198
+ errs = e.rsp.xpath('//rpc-error[error-severity = "error"]')
199
+ raise e unless errs.empty?
200
+ e.rsp
201
+ else
202
+ result
203
+ end
204
+ end
205
+ end
206
+
File without changes
@@ -175,9 +175,9 @@ class Junos::Ez::IPports::Provider::CLASSIC
175
175
 
176
176
  xml_data = @ndev.rpc.get_interface_information(
177
177
  :terse => true,
178
- :interface_name => '[xgf]e-*/*/*.*' )
178
+ )
179
179
 
180
- ifa_list = xml_data.xpath('logical-interface[normalize-space(address-family/address-family-name) = "inet"]')
180
+ ifa_list = xml_data.xpath('interface-information/logical-interface[normalize-space(address-family/address-family-name) = "inet"]')
181
181
 
182
182
  end
183
183
 
File without changes
File without changes
File without changes
@@ -15,12 +15,12 @@ module Junos::Ez::L2ports
15
15
  newbie = case ndev.fact( :switch_style )
16
16
  when :VLAN
17
17
  Junos::Ez::L2ports::Provider::VLAN.new( ndev )
18
- when :VLAN_NG
19
- raise ArgumentError, "under development"
20
- Junos::Ez::L2ports::Provider::VLAN_NG.new( ndev )
18
+ when :VLAN_L2NG
19
+ Junos::Ez::L2ports::Provider::VLAN_L2NG.new( ndev )
21
20
  when :BRIDGE_DOMAIN
22
- raise ArgumentError, "under development"
23
- Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN.new( ndev )
21
+ Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN.new(ndev)
22
+ #raise ArgumentError, "under development"
23
+ # Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN.new( ndev )
24
24
  end
25
25
 
26
26
  newbie.properties = Junos::Ez::Provider::PROPERTIES + PROPERTIES
@@ -44,14 +44,23 @@ module Junos::Ez::L2ports
44
44
  @should[:vlan_tagging] != @has[:vlan_tagging]
45
45
  end
46
46
 
47
+ ### ---------------------------------------------------------------
48
+ ### XML overload 'activate/deactivate' since we need to modify
49
+ ### this at the 'unit' level and not at the 'family' level
50
+ ### ---------------------------------------------------------------
51
+
52
+ def xml_change__active( xml )
53
+ par = xml.instance_variable_get(:@parent).at_xpath('ancestor::interface')
54
+ value = @should[:_active] ? 'active' : 'inactive'
55
+ par[value] = value # attribute name is same as value
56
+ end
57
+
47
58
  end
48
59
 
49
60
  end
50
61
 
51
62
  require 'junos-ez/l2_ports/vlan'
52
- =begin
53
- require 'junos-ez/l2ports/vlan_l2ng' ... under development
54
- require 'junos-ez/l2ports/bridge_domain' ... under development
55
- =end
63
+ require 'junos-ez/l2_ports/vlan_l2ng'
64
+ require 'junos-ez/l2_ports/bridge_domain'
56
65
 
57
66
 
@@ -0,0 +1,499 @@
1
+ class Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN< Junos::Ez::L2ports::Provider
2
+
3
+ ### ---------------------------------------------------------------
4
+ ### XML top placement
5
+ ### ---------------------------------------------------------------
6
+
7
+ def xml_at_top
8
+ Nokogiri::XML::Builder.new {|xml| xml.configuration {
9
+ xml.interfaces {
10
+ return xml_at_element_top( xml, @name )
11
+ }
12
+ }}
13
+ end
14
+
15
+ # set the edit anchor inside bridge-domains stanza
16
+ # we will need to 'up-out' when making changes to the
17
+ # unit information, like description
18
+
19
+ def xml_at_element_top( xml, name )
20
+ xml.interface {
21
+ xml.name name
22
+ xml.send(:'native-vlan-id')
23
+ xml.unit {
24
+ xml.name '0'
25
+ return xml
26
+ }
27
+ }
28
+ end
29
+
30
+ ### ---------------------------------------------------------------
31
+ ### XML property readers
32
+ ### ---------------------------------------------------------------
33
+
34
+ def xml_get_has_xml( xml )
35
+ # second unit contains the family/bridge-domains stanza
36
+ got = xml.xpath('//unit')[0]
37
+ # if this resource doesn't exist we need to default some
38
+ # values into has/should variables
39
+ unless got
40
+ @has[:vlan_tagging] = false
41
+ @should = @has.clone
42
+ end
43
+ got
44
+ end
45
+
46
+ def xml_read_parser( as_xml, as_hash )
47
+ ## reading is anchored at the [... unit 0 ...] level
48
+ set_has_status( as_xml, as_hash )
49
+
50
+ xml_when_item(as_xml.xpath('description')){|i| as_hash[:description] = i.text}
51
+
52
+ f_eth = as_xml.xpath('family/bridge')
53
+ as_hash[:vlan_tagging] = f_eth.xpath('interface-mode').text.chomp == 'trunk'
54
+
55
+ # obtain a copy of the running state, this is needed in case the config
56
+ # is located under the [edit vlans] stanza vs. [edit interfaces]
57
+
58
+ ifs_name = @name || as_xml.xpath('ancestor::interface/name').text.strip
59
+ eth_port_vlans = _get_eth_port_vlans_h( ifs_name )
60
+ @under_vlans = []
61
+
62
+ # --- access port
63
+
64
+ if as_hash[:vlan_tagging] == false
65
+ xml_when_item(f_eth.xpath('domain/vlan-id')){ |i| as_hash[:untagged_vlan] = i.text.chomp }
66
+ unless as_hash[:untagged_vlan]
67
+ as_hash[:untagged_vlan] = eth_port_vlans[:untagged]
68
+ @under_vlans << eth_port_vlans[:untagged]
69
+ end
70
+ return
71
+ end
72
+
73
+ # --- trunk port
74
+ as_hash[:untagged_vlan] ||= eth_port_vlans[:untagged]
75
+ as_hash[:tagged_vlans] = f_eth.xpath('//bridge/vlan-id-list').collect { |v| v.text.chomp }.to_set
76
+ (eth_port_vlans[:tagged] - as_hash[:tagged_vlans]).each do |vlan|
77
+ as_hash[:tagged_vlans] << vlan
78
+ @under_vlans << vlan
79
+ end
80
+ # native-vlan-id is set at the interface level, and is the VLAN-ID, not the vlan
81
+ # name. So we need to do a bit of translating here. The *ASSUMPTION* is that the
82
+ # native-vlan-id value is a given VLAN in the tagged_vlan list. So we will use
83
+ # that list to do the reverse lookup on the tag-id => name
84
+ as_hash[:tagged_vlans]= as_hash[:tagged_vlans].collect {|x| _vlan_tag_id_to_name(x)}
85
+ xml_when_item(f_eth.xpath('ancestor::interface/native-vlan-id')){ |i|
86
+ as_hash[:untagged_vlan] = _vlan_tag_id_to_name( i.text.chomp)
87
+ }
88
+ as_hash[:tagged_vlans].delete(as_hash[:untagged_vlan])
89
+ end
90
+
91
+ ### ---------------------------------------------------------------
92
+ ### XML on_create, on_delete handlers
93
+ ### ---------------------------------------------------------------
94
+
95
+ ## overload the xml_on_delete method since we may need
96
+ ## to do some cleanup work in the [edit vlans] stanza
97
+
98
+ def xml_on_delete( xml )
99
+ @ifd = xml.instance_variable_get(:@parent).at_xpath('ancestor::interface')
100
+ @ifd.xpath('//native-vlan-id').remove ## remove the element from the get-config
101
+ ## need to add check if any native-vlan-id is present or not (untagged vlan)#####
102
+ if is_trunk? and @ifd.xpath('//native-vlan-id')
103
+ _delete_native_vlan_id( xml )
104
+ end
105
+
106
+ return unless @under_vlans
107
+ return if @under_vlans.empty?
108
+
109
+ _xml_rm_under_vlans( xml, @under_vlans )
110
+ end
111
+
112
+ ### ---------------------------------------------------------------
113
+ ### XML property writers
114
+ ### ---------------------------------------------------------------
115
+
116
+ def xml_at_here( xml )
117
+ @ifd = xml.instance_variable_get(:@parent).at_xpath('ancestor::interface')
118
+ @ifd.xpath('//native-vlan-id').remove ## remove the element from the get-config
119
+ xml.family {
120
+ xml.send(:'bridge') {
121
+ return xml
122
+ }
123
+ }
124
+ end
125
+
126
+ def xml_build_change( nop = nil )
127
+ @under_vlans ||= [] # handles case for create'd port
128
+ if mode_changed?
129
+ @should[:untagged_vlan] ||= @has[:untagged_vlan]
130
+ end
131
+ super xml_at_here( xml_at_top )
132
+ end
133
+
134
+ ## ----------------------------------------------------------------
135
+ ## :description
136
+ ## ----------------------------------------------------------------
137
+
138
+ ## overload default method since we need to "up-out" of the
139
+
140
+ def xml_change_description( xml )
141
+ unit = xml.parent.xpath('ancestor::unit')[0]
142
+ Nokogiri::XML::Builder.with( unit ){ |x|
143
+ xml_set_or_delete( x, 'description', @should[:description] )
144
+ }
145
+ end
146
+
147
+ ## ----------------------------------------------------------------
148
+ ## :vlan_tagging
149
+ ## ----------------------------------------------------------------
150
+
151
+ def xml_change_vlan_tagging( xml )
152
+ port_mode = should_trunk? ? 'trunk' : 'access'
153
+ xml.send(:'interface-mode', port_mode )
154
+
155
+ # when the vlan_tagging value changes then this method
156
+ # will trigger updates to the untagged_vlan and tagged_vlans
157
+ # resource values as well.
158
+ # !!! DO NOT SWAP THIS ORDER untagged processing *MUST* BE FIRST!
159
+
160
+ upd_untagged_vlan( xml )
161
+ upd_tagged_vlans( xml )
162
+
163
+ return true
164
+ end
165
+
166
+ def set_ifd_trunking( xml, should_trunk )
167
+ par = xml.instance_variable_get(:@parent)
168
+ Nokogiri::XML::Builder.with( par.at_xpath( 'ancestor::interface' )) do |dot|
169
+ if should_trunk
170
+ dot.send( :'flexible-vlan-tagging' )
171
+ dot.send( :'encapsulation', 'flexible-ethernet-services' )
172
+ else
173
+ dot.send( :'flexible-vlan-tagging', Netconf::JunosConfig::DELETE )
174
+ dot.send( :'encapsulation', Netconf::JunosConfig::DELETE )
175
+ end
176
+ end
177
+ end
178
+
179
+ ## ----------------------------------------------------------------
180
+ ## :tagged_vlans
181
+ ## ----------------------------------------------------------------
182
+
183
+ def xml_change_tagged_vlans( xml )
184
+ return false if mode_changed?
185
+ upd_tagged_vlans( xml )
186
+ end
187
+
188
+ def upd_tagged_vlans( xml )
189
+ return false unless should_trunk?
190
+
191
+ @should[:tagged_vlans] = @should[:tagged_vlans].to_set if @should[:tagged_vlans].kind_of? Array
192
+ @has[:tagged_vlans] = @has[:tagged_vlans].to_set if @has[:tagged_vlans].kind_of? Array
193
+
194
+ v_should = @should[:tagged_vlans] || Set.new
195
+ v_has = @has[:tagged_vlans] || Set.new
196
+
197
+ del = v_has - v_should
198
+ add = v_should - v_has
199
+
200
+ del_under_vlans = del & @under_vlans
201
+ unless del_under_vlans.empty?
202
+ del = del ^ @under_vlans
203
+ _xml_rm_under_vlans( xml, del_under_vlans )
204
+ @under_vlans = []
205
+ end
206
+
207
+ if add or del
208
+ del.each{|v| xml.send(:'vlan-id-list', _vlan_name_to_tag_id( v ), Netconf::JunosConfig::DELETE)}
209
+ add.each{|v| xml.send( :'vlan-id-list', _vlan_name_to_tag_id(v) )}
210
+ end
211
+ return true
212
+ end
213
+
214
+ ## ----------------------------------------------------------------
215
+ ## :untagged_vlan
216
+ ## ----------------------------------------------------------------
217
+
218
+ def xml_change_untagged_vlan( xml )
219
+ return false if mode_changed?
220
+ upd_untagged_vlan( xml )
221
+ end
222
+
223
+ def upd_untagged_vlan( xml )
224
+ self.class.change_untagged_vlan( self, xml )
225
+ end
226
+
227
+ end
228
+
229
+ ##### ---------------------------------------------------------------
230
+ ##### Class methods for handling state-transitions between
231
+ ##### configurations (tagged/untagged)
232
+ ##### ---------------------------------------------------------------
233
+
234
+ class Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN
235
+
236
+ # creating some class definitions ...
237
+ # this is a bit complicated because we need to handle port-mode
238
+ # change transitions; basically dealing with the fact that
239
+ # trunk ports use 'native-vlan-id' and access ports have a
240
+ # vlan member definition; i.e. they don't use native-vlan-id, ugh.
241
+ # Rather than doing all this logic as if/then/else statements,
242
+ # I've opted to using a proc jump-table technique. Lessons
243
+ # learned from lots of embedded systems programming :-)
244
+
245
+ def self.init_jump_table
246
+
247
+ # auto-hash table, majik!
248
+ hash = Hash.new(&(p=lambda{|h,k| h[k] = Hash.new(&p)}))
249
+
250
+ # ------------------------------------------------------------------
251
+ # - jump table for handling various untagged vlan change use-cases
252
+ # ------------------------------------------------------------------
253
+ # There are three criteria for selection:
254
+ # | is_trunk | will_trunk | no_untg |
255
+ # ------------------------------------------------------------------
256
+
257
+ # - will not have untagged vlan
258
+ hash[false][false][true] = self.method(:ac_ac_nountg)
259
+ hash[false][true][true] = self.method(:ac_tr_nountg)
260
+ hash[true][false][true] = self.method(:tr_ac_nountg)
261
+ hash[true][true][true] = self.method(:tr_tr_nountg)
262
+
263
+ # - will have untagged vlan
264
+ hash[false][false][false] = self.method(:ac_ac_untg)
265
+ hash[false][true][false] = self.method(:ac_tr_untg)
266
+ hash[true][false][false] = self.method(:tr_ac_untg)
267
+ hash[true][true][false] = self.method(:tr_tr_untg)
268
+
269
+ hash
270
+ end
271
+
272
+ ### invoke the correct method from the jump table
273
+ ### based on the three criteria to select the action
274
+
275
+ def self.change_untagged_vlan( this, xml )
276
+ @@ez_l2_jmptbl ||= init_jump_table
277
+ proc = @@ez_l2_jmptbl[this.is_trunk?][this.should_trunk?][this.should[:untagged_vlan].nil?]
278
+ proc.call( this, xml )
279
+ end
280
+
281
+ ### -------------------------------------------------------------
282
+ ### The following are all the change transition functions for
283
+ ### each of the use-cases
284
+ ### -------------------------------------------------------------
285
+
286
+ def self.ac_ac_nountg( this, xml )
287
+ #NetdevJunos::Log.debug "ac_ac_nountg"
288
+ # @@@ a port *MUST* be assigned to a vlan in access mode on MX.
289
+ # @@@ generate an error!
290
+ raise Junos::Ez::NoProviderError, "a port *MUST* be assigned to a vlan in access mode on MX."
291
+ end
292
+
293
+ def self.ac_tr_nountg( this, xml )
294
+ #no action needed handled already
295
+ end
296
+
297
+ def self.tr_ac_nountg( this, xml )
298
+ # @@@ a port *MUST* be assigned to a vlan in access mode on MX.
299
+ # @@@ generate an error!
300
+ raise Junos::Ez::NoProviderError, "a port *MUST* be assigned to vlan in access mode on MX"
301
+ end
302
+
303
+ def self.tr_tr_nountg( this, xml )
304
+ this._delete_native_vlan_id( xml )
305
+ end
306
+
307
+ ## ----------------------------------------------------------------
308
+ ## transition where port WILL-HAVE untagged-vlan
309
+ ## ----------------------------------------------------------------
310
+
311
+ def self.ac_ac_untg( this, xml )
312
+ vlan_id = this._vlan_name_to_tag_id( this.should[:untagged_vlan] )
313
+ xml.send :'vlan-id', vlan_id
314
+ end
315
+
316
+ def self.ac_tr_untg( this, xml )
317
+ was_untg_vlan = this.has[:untagged_vlan]
318
+ this._set_native_vlan_id( xml, this.should[:untagged_vlan] )
319
+ this._xml_rm_ac_untagged_vlan( xml ) if was_untg_vlan
320
+ end
321
+
322
+ def self.tr_ac_untg( this, xml )
323
+ this._delete_native_vlan_id( xml )
324
+ vlan_id = this._vlan_name_to_tag_id( this.should[:untagged_vlan] )
325
+ xml.send( :'vlan-id', vlan_id )
326
+ end
327
+
328
+ def self.tr_tr_untg( this, xml )
329
+ this._set_native_vlan_id(xml, this.should[:untagged_vlan])
330
+ end
331
+
332
+ end
333
+
334
+ ##### ---------------------------------------------------------------
335
+ ##### Provider collection methods
336
+ ##### ---------------------------------------------------------------
337
+
338
+ class Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN
339
+
340
+ def build_list
341
+ begin
342
+ got = @ndev.rpc.get_bridge_instance_information( :brief => true)
343
+ rescue => e
344
+ # in this case, no ethernet-switching is enabled so return empty list
345
+ return []
346
+ end
347
+ got.xpath('//l2iff-interface-name').collect{ |ifn| ifn.text.split('.')[0] }
348
+ end
349
+
350
+ def build_catalog
351
+ @catalog = {}
352
+ return @catalog if list!.empty?
353
+ list.each do |ifs_name|
354
+ @ndev.rpc.get_configuration{ |xml|
355
+ xml.interfaces {
356
+ xml_at_element_top( xml, ifs_name )
357
+ }
358
+ }.xpath('interfaces/interface').each do |ifs_xml|
359
+ @catalog[ifs_name] = {}
360
+ unit = xml_get_has_xml( ifs_xml )
361
+ xml_read_parser( unit, @catalog[ifs_name] )
362
+ end
363
+ end
364
+
365
+ @catalog
366
+ end
367
+
368
+ end
369
+
370
+ ##### ---------------------------------------------------------------
371
+ ##### !!!!! PRIVATE METHODS !!!!
372
+ ##### ---------------------------------------------------------------
373
+
374
+ class Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN
375
+ private
376
+
377
+ def _get_eth_port_vlans_h( ifs_name )
378
+ got = @ndev.rpc.get_bridge_instance_information(:interface => ifs_name)
379
+ ret_h = {:untagged => nil, :tagged => Set.new }
380
+ got.xpath('//l2ng-l2ald-iff-interface-entry').each do |vlan|
381
+ # one of the node-set elements (the first one?) contains the interface name.
382
+ # this doesn't have any VLAN information, so skip it.
383
+ next if vlan.xpath('l2iff-interface-name')
384
+
385
+ vlan_name = vlan.xpath('//l2rtb-bridge-vlan').text.strip
386
+ if vlan.xpath('//l2rtb-interface-vlan-member-tagness')
387
+ tgdy = vlan.xpath('//l2rtb-interface-vlan-member-tagness').text.strip
388
+ if tgdy == 'untagged'
389
+ ret_h[:untagged] = vlan_name
390
+ else
391
+ ret_h[:tagged] << vlan_name
392
+ end
393
+ else
394
+ ret_h[:tagged]<<vlan_name
395
+ end
396
+ end
397
+ ret_h
398
+ end
399
+ end
400
+
401
+ ### ---------------------------------------------------------------
402
+ ### [edit vlans] - for interfaces configured here ...
403
+ ### ---------------------------------------------------------------
404
+
405
+ class Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN
406
+
407
+ def _xml_edit_under_vlans( xml )
408
+ Nokogiri::XML::Builder.with( xml.doc.root ) do |dot|
409
+ dot.send(:'vlan-id'){
410
+ return dot
411
+ }
412
+ end
413
+ end
414
+
415
+ def _xml_rm_under_vlans( xml, vlans )
416
+ if vlans.any?
417
+ at_vlans = _xml_edit_under_vlans( xml )
418
+ vlans.each do |vlan_id|
419
+ Nokogiri::XML::Builder.with( at_vlans.parent ) do |this|
420
+ this.domain {
421
+ this.vlan_id vlan_id
422
+ this.interface( Netconf::JunosConfig::DELETE ) { this.name @name }
423
+ }
424
+ end
425
+ end
426
+ end
427
+ end
428
+
429
+ def _xml_rm_ac_untagged_vlan( xml )
430
+ if @under_vlans.empty?
431
+ xml.send :'vlan-id', Netconf::JunosConfig::DELETE
432
+ else
433
+ _xml_rm_under_vlans( xml, [ @has[:untagged_vlan ] ] )
434
+ @under_vlans = []
435
+ end
436
+ end
437
+
438
+ def _xml_rm_these_vlans( xml, vlans )
439
+ if @under_vlans.empty?
440
+ xml.send :'vlan-id', ( Netconf::JunosConfig::DELETE )
441
+ else
442
+ # could be a mix between [edit vlans] and [edit interfaces] ...
443
+ v_has = vlans.to_set
444
+ del_under_vlans = v_has & @under_vlans
445
+ _xml_rm_under_vlans( xml, del_under_vlans )
446
+ if v_has ^ @under_vlans
447
+ xml.send :'vlan-id', ( Netconf::JunosConfig::DELETE )
448
+ end
449
+ @under_vlans = []
450
+ end
451
+ end
452
+
453
+ end
454
+
455
+
456
+ class Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN
457
+
458
+ def _vlan_name_to_tag_id( vlan_name )
459
+ tag_id = @ndev.rpc.get_configuration { |xml|
460
+ xml.send(:'bridge-domains') { xml.domain { xml.name vlan_name }}
461
+ }.xpath('//vlan-id').text.chomp
462
+ raise ArgumentError, "VLAN '#{vlan_name}' not found" if tag_id.empty?
463
+ return tag_id
464
+ end
465
+
466
+
467
+ def _vlan_tag_id_to_name( vlan_id )
468
+ tag_name = @ndev.rpc.get_configuration { |xml|
469
+ xml.send(:'bridge-domains') { xml.domain { xml.send(:'vlan-id', vlan_id)}}
470
+ }.xpath('//name').text.chomp
471
+ raise ArgumentError, "VLAN '#{vlan_id}' not found" if tag_name.empty?
472
+ return tag_name
473
+ end
474
+
475
+
476
+ end
477
+
478
+ class Junos::Ez::L2ports::Provider::BRIDGE_DOMAIN
479
+ def _at_native_vlan_id( xml )
480
+ ifd
481
+ end
482
+
483
+ def _delete_native_vlan_id( xml )
484
+ Nokogiri::XML::Builder.with( @ifd ) do |dot|
485
+ dot.send :'native-vlan-id', Netconf::JunosConfig::DELETE
486
+ end
487
+ return true
488
+ end
489
+
490
+ def _set_native_vlan_id( xml, vlan_name )
491
+ Nokogiri::XML::Builder.with( @ifd ) do |dot|
492
+ dot.send :'native-vlan-id', _vlan_name_to_tag_id( vlan_name )
493
+ xml.send( :'vlan-id-list', _vlan_name_to_tag_id( vlan_name) )
494
+ end
495
+ return true
496
+ end
497
+
498
+
499
+ end