Projet

Général

Profil

OpenvSwitch.rb

Camille Jactard, 15/07/2025 17:30

Télécharger (20,6 ko)

 
1
# -------------------------------------------------------------------------- #
2
# Copyright 2002-2024, OpenNebula Project, OpenNebula Systems                #
3
#                                                                            #
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
5
# not use this file except in compliance with the License. You may obtain    #
6
# a copy of the License at                                                   #
7
#                                                                            #
8
# http://www.apache.org/licenses/LICENSE-2.0                                 #
9
#                                                                            #
10
# Unless required by applicable law or agreed to in writing, software        #
11
# distributed under the License is distributed on an "AS IS" BASIS,          #
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
13
# See the License for the specific language governing permissions and        #
14
# limitations under the License.                                             #
15
#--------------------------------------------------------------------------- #
16

    
17
require 'vnmmad'
18

    
19
class OpenvSwitchVLAN < VNMMAD::VNMDriver
20

    
21
    DRIVER       = 'ovswitch'
22
    XPATH_FILTER = "TEMPLATE/NIC[VN_MAD='ovswitch']"
23

    
24
    SUPPORTED_UPDATE = [
25
        :vlan_id,
26
        :mtu,
27
        :vlan_tagged_id,
28
        :cvlans,
29
        :qinq_type,
30
        :inbound_avg_bw,
31
        :inbound_peak_bw,
32
        :inbound_peak_kb,
33
        :outbound_avg_bw,
34
        :outbound_peak_bw,
35
        :outbound_peak_kb,
36
        :outer_vlan_id,
37
        :phydev
38
    ]
39

    
40
    def initialize(vm, xpath_filter = nil, deploy_id = nil)
41
        @locking = false
42

    
43
        xpath_filter ||= XPATH_FILTER
44
        super(vm, xpath_filter, deploy_id)
45
    end
46

    
47
    def activate
48
        lock
49

    
50
        @bridges = list_bridges
51

    
52
        process do |nic|
53
            @nic = nic
54

    
55
            # Get the name of the link vlan device.
56
            gen_vlan_dev_name
57

    
58
            # Create the bridge.
59
            create_bridge
60

    
61
            # Check that no other vlans are connected to this bridge
62
            validate_vlan_id if @nic[:conf][:validate_vlan_id]
63

    
64
            if @nic[:vlan_dev]
65
                unless @bridges[@nic[:bridge]].include? @nic[:vlan_dev]
66
                    create_vlan_dev
67

    
68
                    add_bridge_port(@nic[:vlan_dev], nil)
69
                end
70
            elsif !@nic[:phydev].to_s.empty?
71
                add_bridge_port(@nic[:phydev], nil)
72
            end
73

    
74
            add_bridge_port(nic[:target], dpdk_vm(@nic[:target])) if dpdk?
75

    
76
            if @nic[:tap].nil?
77
                # In net/pre action, we just need to ensure the bridge is
78
                # created so the libvirt/QEMU can add VM interfaces into that.
79
                # Any other driver actions are done in net/post action.
80
                next if VNMMAD.pre_action?
81

    
82
                STDERR.puts "No tap device found for nic #{@nic[:nic_id]}"
83
                unlock
84
                exit 1
85
            end
86

    
87
            if !@nic[:mtu].nil?
88
                cmd = "#{command(:ovs_vsctl)} set int #{@nic[:tap]} "\
89
                      "mtu_request=#{@nic[:mtu]}"
90
                run cmd
91
            end
92

    
93
            # Apply VLAN
94
            if !@nic[:vlan_id].nil?
95
                if !@nic[:cvlans].nil?
96
                    tag_qinq
97
                else
98
                    tag_vlan
99
                    tag_trunk_vlans
100
                end
101
            end
102

    
103
            # Delete any existing flows on port
104
            del_flow "in_port=#{port}"
105

    
106
            # We are using the flow table hierarchy to create a set of rules
107
            # which must be satisfied. The packet flows through the tables,
108
            # from to another. Any rule can stop the flow and drop the packet,
109
            # but if the lucky packet reaches the end, it's accepted.
110
            #
111
            # If the OpenNebula virtual network has any IP/MAC-spoofing filter
112
            # enabled, the additional table rules are generated. Otherwise,
113
            # the tables are left empty and only pass the packet to another
114
            # table, or finally accepts the packet.
115
            #
116
            #   +---------+          +------------+          +-----------+
117
            #   | Table 0 | resubmit |  Table 10  | resubmit |  Table 20 |
118
            #   |  Main   |--------->| MAC-spoof. |--------->| IP-spoof. |--> NORMAL
119
            #   |         |          |   rules    |          |   rules   |
120
            #   +---------+          +------------+          +-----------+
121
            #        |                    |                       |
122
            #        +-> DROP             +-> DROP                +-> DROP
123
            #
124
            # Tables are defined by following base rules:
125
            # in_port=<PORT>,table=0,priority=100,actions=note:VV.VV.VV.VV.NN.NN,resubmit(,10)
126
            # in_port=<PORT>,table=10,priority=100,actions=resubmit(,20)
127
            # in_port=<PORT>,table=20,priority=100,actions=NORMAL
128
            add_flow("table=0,in_port=#{port}", "note:#{port_note},resubmit(,10)", 100)
129
            add_flow("table=10,in_port=#{port}", 'resubmit(,20)', 100)
130
            add_flow("table=20,in_port=#{port}", 'normal', 100)
131

    
132
            # MAC-spoofing
133
            mac_spoofing if nic[:filter_mac_spoofing] =~ /yes/i
134

    
135
            # IP-spoofing
136
            ip_spoofing if nic[:filter_ip_spoofing] =~ /yes/i
137
        end
138

    
139
        # MAC-spoofing & IP-spoofing for NIC ALIAS
140
        process_alias do |nalias|
141
            nparent = @vm.parent(nalias)
142

    
143
            next unless nparent
144

    
145
            nalias[:port] = nparent[:port]
146

    
147
            @nic = nalias
148

    
149
            mac_spoofing if nalias[:filter_mac_spoofing] =~ /yes/i
150

    
151
            ip_spoofing if nalias[:filter_ip_spoofing] =~ /yes/i
152
        end
153

    
154
        unlock
155

    
156
        0
157
    end
158

    
159
    def deactivate
160
        # NIC_ALIAS are  not processed, skip
161
        return 0 if @vm['TEMPLATE/NIC_ALIAS[ATTACH="YES"]/NIC_ID']
162

    
163
        lock
164

    
165
        @bridges = list_bridges
166

    
167
        attach_nic_id = @vm['TEMPLATE/NIC[ATTACH="YES"]/NIC_ID']
168

    
169
        process do |nic|
170
            if attach_nic_id && attach_nic_id != nic[:nic_id]
171
                next
172
            end
173

    
174
            @nic = nic
175

    
176
            # Remove flows
177
            del_flows
178

    
179
            # delete port from bridge if exists. Some virtualization
180
            # technologies might clean the port itselves.
181
            del_bridge_port(@nic[:target])
182

    
183
            next if @nic[:phydev].nil?
184
            next if @bridges[@nic[:bridge]].nil?
185

    
186
            # Get the name of the vlan device.
187
            gen_vlan_dev_name
188

    
189
            # Return if the bridge doesn't exist because it was already deleted
190
            # (handles last vm with multiple nics on the same vlan)
191
            next unless @bridges.include?(@nic[:bridge])
192

    
193
            # Return if we want to keep the empty bridge
194
            next if @nic[:conf][:keep_empty_bridge]
195

    
196
            # Return if the vlan device is not the only left device in the bridge.
197
            next if @bridges[@nic[:bridge]].length > 1 ||
198
                (@nic[:vlan_dev] && !@bridges[@nic[:bridge]].include?(@nic[:vlan_dev]))
199

    
200
            if @nic[:vlan_dev]
201
                delete_vlan_dev
202
                @bridges[@nic[:bridge]].delete(@nic[:vlan_dev])
203
            end
204

    
205
            delete_bridge
206
        end
207

    
208
        unlock
209

    
210
        0
211
    end
212

    
213
    def update(vnet_id)
214
        lock
215

    
216
        begin
217
            changes = @vm.changes.select {|k, _| SUPPORTED_UPDATE.include?(k) }
218

    
219
            return 0 if changes.empty?
220

    
221
            process do |nic|
222
                next unless Integer(nic[:network_id]) == vnet_id
223

    
224
                @nic = nic
225

    
226
                # Get the name of the link vlan device.
227
                gen_vlan_dev_name
228

    
229
                if changes[:vlan_id] && !@nic[:vlan_id].nil? && !@nic[:vlan_id].empty?
230
                    tag_vlan
231
                end
232

    
233
                if changes[:mtu]
234
                    cmd = "#{command(:ovs_vsctl)} set int #{@nic[:tap]} "\
235
                          "mtu_request=#{@nic[:mtu]}"
236
                    run cmd
237
                end
238

    
239
                if changes[:vlan_tagged_id]
240
                    tag_trunk_vlans
241
                end
242

    
243
                if !changes[:cvlans].nil? || !changes[:qinq_type].nil?
244
                    tag_qinq
245
                end
246

    
247
                qos = changes.each do |c, _|
248
                    break true if c.to_s.match?(/inbound/) || c.match?(/outbound/)
249
                end
250

    
251
                if qos
252
                    if @vm.deploy_id
253
                        deploy_id = @vm.deploy_id
254
                    else
255
                        deploy_id = @vm['DEPLOY_ID']
256
                    end
257

    
258
                    @nic.set_qos(deploy_id)
259
                end
260

    
261
                @bridges = list_bridges
262
                phydev   = @nic[:vlan_dev] || @nic[:phydev]
263

    
264
                next if @bridges[@nic[:bridge]].include? phydev
265

    
266
                if (!changes[:outer_vlan_id].nil? || !changes[:phydev].nil?) &&
267
                      !@nic[:vlan_dev].nil?
268
                    ####################################################
269
                    # Remove old VXLAN device
270
                    ####################################################
271
                    @nic = nic.merge(changes)
272
                    gen_vlan_dev_name
273

    
274
                    if @bridges[@nic[:bridge]].include? @nic[:vlan_dev]
275
                        del_bridge_port(@nic[:vlan_dev])
276
                        delete_vlan_dev
277
                    end
278

    
279
                    ####################################################
280
                    # Add new link to the BRIDGE
281
                    ####################################################
282
                    @nic = nic
283
                    gen_vlan_dev_name
284

    
285
                    create_vlan_dev
286
                    add_bridge_port(@nic[:vlan_dev], nil)
287
                elsif !changes[:phydev].nil?
288
                    if !@bridges[@nic[:bridge]].include?(@nic[:phydev])
289
                        del_bridge_port(changes[:phydev])
290
                        add_bridge_port(@nic[:phydev])
291
                    end
292
                end
293
            end
294
        rescue StandardError => e
295
            raise e
296
        ensure
297
            unlock
298
        end
299

    
300
        0
301
    end
302

    
303
    def tag_vlan
304
        cmd =  "#{command(:ovs_vsctl)} set Port #{@nic[:tap]} "
305
        cmd << "tag=#{@nic[:vlan_id]}"
306

    
307
        run cmd
308
    end
309

    
310
    def tag_trunk_vlans
311
        range = @nic[:vlan_tagged_id]
312

    
313
        return unless range?(range)
314

    
315
        ovs_vsctl_cmd = "#{command(:ovs_vsctl)} set Port #{@nic[:tap]}"
316

    
317
        # Open vSwitch 2.7.0+ allows range intervals (x-y), but
318
        # we need to support even older versions. We expand the
319
        # intervals into the list of values [x,x+1,...,y-1,y],
320
        # which should work for all.
321
        cmd = "#{ovs_vsctl_cmd} trunks='#{expand_range(range)}'"
322
        run cmd
323

    
324
        cmd = "#{ovs_vsctl_cmd} vlan_mode=native-untagged"
325
        run cmd
326
    end
327

    
328
    def tag_qinq
329
        range = @nic[:cvlans]
330

    
331
        set_vlan_limit(2)
332

    
333
        cmd =  "#{command(:ovs_vsctl)} set Port #{@nic[:tap]} "
334
        cmd << "vlan_mode=dot1q-tunnel tag=#{@nic[:vlan_id]} "
335
        cmd << "cvlans=#{expand_range(range)}"
336

    
337
        run cmd
338

    
339
        qinq_type = @nic[:qinq_type]
340
        qinq_type ||= '802.1q'
341

    
342
        cmd =  "#{command(:ovs_vsctl)} set Port #{@nic[:tap]} "
343
        cmd << "other_config:qinq-ethtype=#{qinq_type}"
344

    
345
        run cmd
346
    end
347

    
348
    # Following IP-spoofing rules may be created:
349
    # (if ARP Cache Poisoning) in_port=<PORT>,table=20,arp,arp_spa=<IP>,priority=50000,actions=NORMAL
350
    # (if ARP Cache Poisoning) in_port=<PORT>,table=20,arp,priority=49000,actions=drop
351
    # in_port=<PORT>,table=20,ip,nw_src=<IP>,priority=45000,actions=NORMAL
352
    # in_port=<PORT>,table=20,ipv6,ipv6_src=<IP6>,priority=45000,actions=NORMAL
353
    # in_port=<PORT>,table=20,udp,nw_src=0.0.0.0,nw_dst=255.255.255.255,tp_src=68,tp_dst=67,priority=44000,actions=NORMAL
354
    # in_port=<PORT>,table=20,icmp6,ipv6_src=::,icmp_type=133,priority=44000,actions=NORMAL
355
    # in_port=<PORT>,table=20,icmp6,ipv6_src=::,icmp_type=135,priority=44000,actions=NORMAL
356
    # in_port=<PORT>,table=20,ip,priority=40000,actions=drop
357
    # in_port=<PORT>,table=20,ipv6,priority=40000,actions=drop
358
    #
359
    # The particular table also contains the base rule created before:
360
    # in_port=<PORT>,table=20,priority=100,actions=NORMAL
361
    def ip_spoofing
362
        base = "table=20,in_port=#{port}"
363
        pass = 'normal'
364

    
365
        ipv4s = []
366

    
367
        [:ip, :vrouter_ip].each do |key|
368
            ipv4s << @nic[key] if !@nic[key].nil? && !@nic[key].empty?
369
        end
370

    
371
        if !ipv4s.empty?
372
            ipv4s.each do |ip|
373
                if @nic[:conf][:arp_cache_poisoning]
374
                    add_flow("#{base},arp,nw_src=#{ip}", pass, 50000)
375
                end
376

    
377
                add_flow("#{base},ip,nw_src=#{ip}", pass, 45000)
378
            end
379
        end
380

    
381
        if @nic[:conf][:arp_cache_poisoning]
382
            add_flow("#{base},arp", :drop, 49000)
383
        end
384

    
385
        # BOOTP
386
        add_flow("#{base},udp,nw_src=0.0.0.0/32,tp_src=68,nw_dst=255.255.255.255/32,tp_dst=67",
387
                 pass, 44000)
388

    
389
        ipv6s = []
390

    
391
        [:ip6, :ip6_global, :ip6_link, :ip6_ula].each do |key|
392
            ipv6s << @nic[key] if !@nic[key].nil? && !@nic[key].empty?
393
        end
394

    
395
        if !ipv6s.empty?
396
            ipv6s.each do |ip|
397
                add_flow("#{base},ipv6,ipv6_src=#{ip}", pass, 45000)
398
            end
399
        end
400

    
401
        # ICMPv6 Neighbor Discovery Protocol (ARP replacement for IPv6)
402
        add_flow("#{base},icmp6,icmp_type=133,ipv6_src=::", pass, 44000)
403
        add_flow("#{base},icmp6,icmp_type=135,ipv6_src=::", pass, 44000)
404

    
405
        add_flow("#{base},ip", :drop, 40000)
406
        add_flow("#{base},ipv6", :drop, 40000)
407
    end
408

    
409
    # Following MAC-spoofing rules may be created:
410
    # (if ARP Cache Poisoning) in_port=<PORT>,table=10,arp,dl_src=<MAC>,priority=50000,actions=resubmit(,20)
411
    # in_port=<PORT>,table=10,dl_src=<MAC>,priority=45000,actions=resubmit(,20)
412
    # in_port=<PORT>,table=10,priority=40000,actions=drop
413
    #
414
    # The particular table also contains the base rule created before:
415
    # in_port=<PORT>,table=10,priority=100,actions=resubmit(,20)
416
    def mac_spoofing
417
        base = "table=10,in_port=#{port}"
418
        pass = 'resubmit(,20)'
419

    
420
        if @nic[:conf][:arp_cache_poisoning]
421
            add_flow("#{base},arp,dl_src=#{@nic[:mac]}", pass, 50000)
422
        end
423

    
424
        add_flow("#{base},dl_src=#{@nic[:mac]}", pass, 45000)
425
        add_flow(base, :drop, 40000)
426
    end
427

    
428
    def del_flows
429
        the_ports = ports
430
        in_port   = ''
431

    
432
        cmd_flows = "#{command(:ovs_ofctl)} dump-flows #{@nic[:bridge]}"
433
        out_flows = `#{cmd_flows}`
434

    
435
        # searching for flow just by MAC address is legacy,
436
        # we preferably look for a flow port with our note
437
        ["note:#{port_note}", @nic[:mac]].each do |flow_matching|
438
            out_flows.lines do |flow|
439
                next unless flow.match(flow_matching)
440

    
441
                if (port_match = flow.match(/in_port=(\d+)/))
442
                    in_port_tmp = port_match[1]
443

    
444
                    if !the_ports.include?(in_port_tmp)
445
                        in_port = in_port_tmp
446
                        break
447
                    end
448
                end
449
            end
450

    
451
            break unless in_port.empty?
452
        end
453

    
454
        del_flow "in_port=#{in_port}" unless in_port.empty?
455
    end
456

    
457
    def add_flow(filter, action, priority = nil)
458
        priority = (priority.to_s.empty? ? '' : "priority=#{priority},")
459

    
460
        run "#{command(:ovs_ofctl)} add-flow " <<
461
            "#{@nic[:bridge]} '#{filter},#{priority}actions=#{action}'"
462
    end
463

    
464
    def del_flow(filter)
465
        filter.gsub!(/priority=(\d+)/, '')
466
        run "#{command(:ovs_ofctl)} del-flows #{@nic[:bridge]} #{filter}"
467
    end
468

    
469
    def run(cmd)
470
        OpenNebula.exec_and_log(cmd)
471
    end
472

    
473
    def ports
474
        dump_ports = `#{command(:ovs_ofctl)} \
475
                      dump-ports #{@nic[:bridge]} #{@nic[:tap]}`
476

    
477
        dump_ports.scan(/^\s*port\s*(\d+):/).flatten
478
    end
479

    
480
    def port
481
        if @nic[:port]
482
            @nic[:port]
483
        else
484
            @nic[:port] = ports.first
485
        end
486
    end
487

    
488
    def port_note
489
        # dot separated hexadecimal VM_ID, NIC_ID twins,
490
        # e.g. for VM_ID=1, NIC_ID=1: "00.00.00.01.00.01"
491
        ("%08x%04x" % [@vm['ID'], @nic[:nic_id]]).gsub(/(..)(?=.)/, '\1.')
492
    end
493

    
494
    def range?(range)
495
        !range.to_s.match(/^\d+([,-]\d+)*$/).nil?
496
    end
497

    
498
    def expand_range(range)
499
        items = []
500

    
501
        range.split(',').each do |i|
502
            l, r = i.split('-')
503

    
504
            l = l.to_i
505
            r = r.to_i unless r.nil?
506

    
507
            if r.nil?
508
                items << l
509
            elsif r >= l
510
                items.concat((l..r).to_a)
511
            else
512
                items.concat((r..l).to_a)
513
            end
514
        end
515

    
516
        items.uniq.join(',')
517
    end
518

    
519
    private
520

    
521
    # Generate the name of the vlan device which will be added to the bridge.
522
    def gen_vlan_dev_name
523
        nil
524
    end
525

    
526
    # Create a VLAN device.
527
    # NEEDS to be implemented by the subclass.
528
    def create_vlan_dev
529
        nil
530
    end
531

    
532
    # Delete a VLAN device
533
    # NEEDS to be implemented by the subclass.
534
    def delete_vlan_dev
535
        nil
536
    end
537

    
538
    # Return true when usgin dpdk
539
    def dpdk?
540
        @nic[:bridge_type] == 'openvswitch_dpdk'
541
    end
542

    
543
    # Path to the vm port socket /var/lib/one/datastores/0/23/one-23-0
544
    def dpdk_vm(port)
545
        "#{@vm.system_dir(@nic[:conf][:datastore_location])}/#{port}"
546
    end
547

    
548
    # Path to  bridge folder for non VM links
549
    def dpdk_br
550
        "#{@nic[:conf][:datastore_location]}/ovs-#{@nic[:bridge]}"
551
    end
552

    
553
    # Creates an OvS bridge if it does not exists, and brings it up.
554
    # This function IS FINAL, exits if action cannot be completed
555
    def create_bridge
556
        return if @bridges.keys.include? @nic[:bridge]
557

    
558
        if @nic[:bridge_type] == 'openvswitch_dpdk'
559
            @nic[:ovs_bridge_conf] = {} unless @nic[:ovs_bridge_conf]
560
            @nic[:ovs_bridge_conf]['datapath_type'] = 'netdev'
561
        end
562

    
563
        OpenNebula.exec_and_log("#{command(:ovs_vsctl)} --may-exist add-br #{@nic[:bridge]}")
564

    
565
        set_bridge_options
566

    
567
        @bridges[@nic[:bridge]] = []
568

    
569
        OpenNebula.exec_and_log("#{command(:ip)} link set #{@nic[:bridge]} up")
570
    end
571

    
572
    # Delete OvS bridge
573
    def delete_bridge
574
        OpenNebula.exec_and_log("#{command(:ovs_vsctl)} del-br #{@nic[:bridge]}")
575

    
576
        @bridges.delete(@nic[:bridge])
577
    end
578

    
579
    # Add port into OvS bridge
580
        def add_bridge_port(port, dpdk_path = nil)
581
        OpenNebula.log_error("DEBUG: Entering add_bridge_port. Bridge: #{@nic[:bridge]}, Port: #{port.inspect}")
582
        OpenNebula.log_error("DEBUG: Call Stack: #{caller.join("\n")}")
583

    
584
        return if @bridges[@nic[:bridge]].include? port
585

    
586
        ovs_cmd = "#{command(:ovs_vsctl)} add-port #{@nic[:bridge]} #{port}"
587

    
588
        OpenNebula.log_error("DEBUG: Executing OVS command: #{ovs_cmd}")
589

    
590
        if dpdk_path && dpdk?
591
            ovs_cmd << " -- set Interface #{port} type=dpdkvhostuserclient"\
592
                         " options:vhost-server-path=#{dpdk_path}"
593
        end
594

    
595
        OpenNebula.exec_and_log(ovs_cmd)
596

    
597
        @bridges[@nic[:bridge]] << port
598
    end
599

    
600
    # Delete port from OvS bridge
601
    def del_bridge_port(port)
602
        OpenNebula.exec_and_log("#{command(:ovs_vsctl)} --if-exists del-port " \
603
                                "#{@nic[:bridge]} #{port}")
604

    
605
        @bridges[@nic[:bridge]].delete(port)
606
    end
607

    
608
    # Calls ovs-vsctl set bridge to set options stored in ovs_bridge_conf
609
    def set_bridge_options
610
        @nic[:ovs_bridge_conf].each do |option, value|
611
            cmd = "#{command(:ovs_vsctl)} set bridge " <<
612
                    "#{@nic[:bridge]} #{option}=#{value}"
613

    
614
            OpenNebula.exec_and_log(cmd)
615
        end
616
    end
617

    
618
    # Get hypervisor bridges
619
    #   @return [Hash<String>] with the bridge names
620
    def list_bridges
621
        bridges = {}
622

    
623
        list_br =`#{command(:ovs_vsctl)} list-br`
624
        list_br.split.each do |bridge|
625
            bridge = bridge.strip
626

    
627
            if bridge
628
                list_ports =`#{command(:ovs_vsctl)} list-ports #{bridge}`
629
                bridges[bridge] = list_ports.split("\n")
630
            end
631
        end
632

    
633
        bridges
634
    end
635

    
636
    def validate_vlan_id
637
        OpenNebula.log_error('VLAN ID validation not supported for OpenvSwitch, skipped.')
638
    end
639

    
640
    def set_vlan_limit(limit)
641
        vl = `#{command(:ovs_vsctl)} get Open_vSwitch . other_config:vlan-limit`
642

    
643
        vl_limit = 0
644

    
645
        begin
646
            vl_limit = Integer(vl.tr("\"\n", ''))
647
        rescue ArgumentError
648
        end
649

    
650
        return if vl_limit == limit
651

    
652
        cmd = "#{command(:ovs_vsctl)} set Open_vSwitch . "\
653
            "other_config:vlan-limit=#{limit}"
654
        run cmd
655

    
656
        cmd = "#{command(:ovs_appctl)} revalidator/purge"
657
        run cmd
658
    end
659

    
660
end