OpenvSwitch.rb
| 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
|