junos-ez-stdlib 0.1.2 → 1.0.3

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