Skip to content

Commit 8fa0e68

Browse files
author
gitlab
committed
Merge branch 'feature-l2vlan-final@@3' into '5.1.0'
<feature>[network]: add update l2 vlan&vni feature See merge request zstackio/zstack-utility!4646
2 parents 50acd6b + b3a7d1f commit 8fa0e68

File tree

3 files changed

+214
-26
lines changed

3 files changed

+214
-26
lines changed

kvmagent/kvmagent/plugins/mevoco.py

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,34 @@ class RemoveForwardDnsRsp(kvmagent.AgentResponse):
8484
def __init__(self):
8585
super(RemoveForwardDnsRsp, self).__init__()
8686

87-
def get_phy_dev_from_bridge_name(bridge_name):
88-
# for vlan, BR_NAME is "br_eth0_100", vlan sub interface: eth0.100,
89-
# for vxlan, BR_NAME is "br_vx_7863", vxlan sub interface vxlan7863"
90-
phy_dev = bridge_name.replace('br_', '', 1)
91-
if phy_dev[:2] == "vx":
92-
phy_dev = phy_dev.replace("vx", "vxlan").replace("_", "")
87+
def get_phy_dev_from_bridge_name(bridge_name, vlan_id=None):
88+
phy_dev = ""
89+
90+
if vlan_id:
91+
if vlan_id.startswith("vlan"):
92+
vlan_number = vlan_id.replace("vlan", "")
93+
phy_nic = linux.get_bridge_phy_nic_name_from_alias(bridge_name)
94+
if not phy_nic:
95+
phy_dev = bridge_name.replace('br_', '', 1) + "." + vlan_number
96+
else:
97+
if "." in phy_nic:
98+
phy_nic = phy_nic.rsplit('.', 1)[0]
99+
phy_dev = "%s.%s" % (phy_nic, vlan_number)
100+
elif vlan_id.startswith("vxlan"):
101+
vxlan_number = vlan_id.replace("vxlan", "")
102+
phy_dev = "vxlan" + vxlan_number
93103
else:
94-
phy_nic = linux.get_bridge_phy_nic_name_from_alias(bridge_name)
95-
if not phy_nic:
96-
phy_dev = phy_dev.replace("_", ".")
104+
# for vlan, BR_NAME is "br_eth0_100", vlan sub interface: eth0.100,
105+
# for vxlan, BR_NAME is "br_vx_7863", vxlan sub interface vxlan7863"
106+
phy_dev = bridge_name.replace('br_', '', 1)
107+
if phy_dev[:2] == "vx":
108+
phy_dev = phy_dev.replace("vx", "vxlan").replace("_", "")
97109
else:
98-
phy_dev = re.sub(r"^.*_", "%s." % phy_nic, phy_dev)
110+
phy_nic = linux.get_bridge_phy_nic_name_from_alias(bridge_name)
111+
if not phy_nic:
112+
phy_dev = phy_dev.replace("_", ".")
113+
else:
114+
phy_dev = re.sub(r"^.*_", "%s." % phy_nic, phy_dev)
99115

100116
return phy_dev
101117

@@ -110,9 +126,10 @@ def getDhcpEbtableChainName(dhcpIp):
110126
return "ZSTACK-%s" % dhcpIp
111127

112128
class UserDataEnv(object):
113-
def __init__(self, bridge_name, namespace_name):
129+
def __init__(self, bridge_name, namespace_name, vlan_id):
114130
self.bridge_name = bridge_name
115131
self.namespace_name = namespace_name
132+
self.vlan_id = vlan_id
116133
self.outer_dev = None
117134
self.inner_dev = None
118135

@@ -126,7 +143,8 @@ def prepare(self):
126143
logger.debug('use id[%s] for the namespace[%s]' % (NAMESPACE_ID, NAMESPACE_NAME))
127144

128145
BR_NAME = self.bridge_name
129-
BR_PHY_DEV = get_phy_dev_from_bridge_name(self.bridge_name)
146+
VLAN_ID = self.vlan_id
147+
BR_PHY_DEV = get_phy_dev_from_bridge_name(self.bridge_name, self.vlan_id)
130148
OUTER_DEV = "outer%s" % NAMESPACE_ID
131149
INNER_DEV = "inner%s" % NAMESPACE_ID
132150
MAX_MTU = linux.MAX_MTU_OF_VNIC
@@ -166,6 +184,7 @@ class DhcpEnv(object):
166184

167185
def __init__(self):
168186
self.bridge_name = None
187+
self.vlan_id = None
169188
self.dhcp_server_ip = None
170189
self.dhcp_server6_ip = None
171190
self.dhcp_netmask = None
@@ -292,6 +311,8 @@ def _prepare_dhcp6_iptables(dualStack=True):
292311
logger.debug('use id[%s] for the namespace[%s]' % (NAMESPACE_ID, NAMESPACE_NAME))
293312

294313
BR_NAME = self.bridge_name
314+
# VLAN_ID sample: vlan100, vxlan200
315+
VLAN_ID = self.vlan_id
295316
DHCP_IP = self.dhcp_server_ip
296317
DHCP6_IP = self.dhcp_server6_ip
297318
DHCP_NETMASK = self.dhcp_netmask
@@ -300,7 +321,7 @@ def _prepare_dhcp6_iptables(dualStack=True):
300321
PREFIX_LEN = linux.netmask_to_cidr(DHCP_NETMASK)
301322
PREFIX6_LEN = self.prefixLen
302323
ADDRESS_MODE = self.addressMode
303-
BR_PHY_DEV = get_phy_dev_from_bridge_name(self.bridge_name)
324+
BR_PHY_DEV = get_phy_dev_from_bridge_name(self.bridge_name, VLAN_ID)
304325
OUTER_DEV = "outer%s" % NAMESPACE_ID
305326
INNER_DEV = "inner%s" % NAMESPACE_ID
306327
if DHCP_IP is not None:
@@ -891,7 +912,7 @@ def prepare_br_connect_ns(ns, ns_inner_dev, ns_outer_dev):
891912

892913
iproute.IpNetnsShell(ns).set_link_up(userdata_br_inner_dev)
893914

894-
p = UserDataEnv(to.bridgeName, to.namespaceName)
915+
p = UserDataEnv(to.bridgeName, to.namespaceName, to.vlanId)
895916
INNER_DEV = None
896917
DHCP_IP = None
897918
NS_NAME = to.namespaceName
@@ -921,7 +942,8 @@ def prepare_br_connect_ns(ns, ns_inner_dev, ns_outer_dev):
921942

922943
# set ebtables
923944
BR_NAME = to.bridgeName
924-
ETH_NAME = get_phy_dev_from_bridge_name(BR_NAME)
945+
VLAN_ID = to.vlanId
946+
ETH_NAME = get_phy_dev_from_bridge_name(BR_NAME, VLAN_ID)
925947

926948
MAC = iproute.IpNetnsShell(NS_NAME).get_mac(INNER_DEV)
927949
CHAIN_NAME="USERDATA-%s" % BR_NAME
@@ -1323,6 +1345,7 @@ def prepare_dhcp(self, req):
13231345
cmd = jsonobject.loads(req[http.REQUEST_BODY])
13241346
p = DhcpEnv()
13251347
p.bridge_name = cmd.bridgeName
1348+
p.vlan_id = cmd.vlanId
13261349
p.dhcp_server_ip = cmd.dhcpServerIp
13271350
p.dhcp_server6_ip = cmd.dhcp6ServerIp
13281351
p.dhcp_netmask = cmd.dhcpNetmask
@@ -1357,6 +1380,7 @@ def batch_prepare_dhcp(self, req):
13571380
for info in cmd.dhcpInfos:
13581381
p = DhcpEnv()
13591382
p.bridge_name = info.bridgeName
1383+
p.vlan_id = info.vlanId
13601384
p.dhcp_server_ip = info.dhcpServerIp
13611385
p.dhcp_server6_ip = info.dhcp6ServerIp
13621386
p.dhcp_netmask = info.dhcpNetmask

kvmagent/kvmagent/plugins/network_plugin.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
KVM_CHANGE_LLDP_MODE_PATH = '/network/lldp/changemode'
3232
KVM_GET_LLDP_INFO_PATH = '/network/lldp/get'
3333
KVM_APPLY_LLDP_CONFIG_PATH = '/network/lldp/apply'
34+
KVM_UPDATE_L2VLAN_NETWORK_PATH = "/network/l2vlan/updatebridge"
35+
KVM_UPDATE_L2VXLAN_NETWORK_PATH = "/network/l2vxlan/updatebridge"
3436
KVM_REALIZE_L2NOVLAN_NETWORK_PATH = "/network/l2novlan/createbridge"
3537
KVM_REALIZE_L2VLAN_NETWORK_PATH = "/network/l2vlan/createbridge"
3638
KVM_CHECK_L2NOVLAN_NETWORK_PATH = "/network/l2novlan/checkbridge"
@@ -943,6 +945,94 @@ def apply_lldp_config(self, req):
943945

944946
return jsonobject.dumps(rsp)
945947

948+
@lock.lock('bridge')
949+
@kvmagent.replyerror
950+
def update_vlan_bridge(self, req):
951+
rsp = CreateBridgeResponse()
952+
cmd = jsonobject.loads(req[http.REQUEST_BODY])
953+
try:
954+
self.update_bridge_vlan(cmd, rsp)
955+
except Exception as e:
956+
logger.warning(traceback.format_exc())
957+
rsp.error = 'unable to update vlan bridge[%s], because %s' % (
958+
cmd.bridgeName, str(e))
959+
rsp.success = False
960+
return jsonobject.dumps(rsp)
961+
962+
@lock.lock('bridge')
963+
@kvmagent.replyerror
964+
def update_vxlan_bridge(self, req):
965+
rsp = CreateBridgeResponse()
966+
cmd = jsonobject.loads(req[http.REQUEST_BODY])
967+
try:
968+
self.update_bridge_vxlan(cmd, rsp)
969+
except Exception as e:
970+
logger.warning(traceback.format_exc())
971+
rsp.error = 'unable to update vxlan bridge[%s], because %s' % (
972+
cmd.bridgeName, str(e))
973+
rsp.success = False
974+
return jsonobject.dumps(rsp)
975+
976+
def update_bridge_vxlan(self, cmd, rsp):
977+
if not cmd.oldVlan or not cmd.newVlan:
978+
error_msg = 'both oldVlan and newVlan must be provided.'
979+
logger.warning(error_msg)
980+
rsp.error = error_msg
981+
rsp.success = False
982+
return jsonobject.dumps(rsp)
983+
new_vxlan_interface = None
984+
old_vxlan_interface = None
985+
if cmd.newVlan:
986+
new_vxlan_interface = 'vxlan%s' % cmd.newVlan
987+
if cmd.oldVlan:
988+
old_vxlan_interface = 'vxlan%s' % cmd.oldVlan
989+
try:
990+
if cmd.peers:
991+
linux.delete_vxlan_fdbs([old_vxlan_interface], cmd.peers)
992+
linux.change_vxlan_interface(cmd.oldVlan, cmd.newVlan)
993+
linux.update_bridge_interface_configuration(old_vxlan_interface, new_vxlan_interface,
994+
cmd.bridgeName, cmd.l2NetworkUuid)
995+
if cmd.peers:
996+
linux.populate_vxlan_fdbs([new_vxlan_interface], cmd.peers)
997+
logger.debug('successfully update bridge[%s] vxlan interface from device[%s] to device[%s]'
998+
% (cmd.bridgeName, cmd.oldVlanInterface, cmd.newVlanInterface))
999+
except Exception as e:
1000+
logger.warning(traceback.format_exc())
1001+
rsp.error = ('unable to update bridge[%s] vxlan interface from device[%s] to device[%s], because %s'
1002+
% (cmd.bridgeName, old_vxlan_interface, new_vxlan_interface, str(e)))
1003+
rsp.success = False
1004+
return jsonobject.dumps(rsp)
1005+
1006+
def update_bridge_vlan(self, cmd, rsp):
1007+
self._ifup_device_if_down(cmd.physicalInterfaceName)
1008+
if cmd.newVlan:
1009+
new_vlan_interface = '%s.%s' % (cmd.physicalInterfaceName, cmd.newVlan)
1010+
else:
1011+
new_vlan_interface = '%s' % cmd.physicalInterfaceName
1012+
if cmd.oldVlan:
1013+
old_vlan_interface = '%s.%s' % (cmd.physicalInterfaceName, cmd.oldVlan)
1014+
else:
1015+
old_vlan_interface = '%s' % cmd.physicalInterfaceName
1016+
try:
1017+
if cmd.newVlan:
1018+
linux.create_vlan_eth(cmd.physicalInterfaceName, cmd.newVlan)
1019+
linux.update_bridge_interface_configuration(old_vlan_interface, new_vlan_interface,
1020+
cmd.bridgeName, cmd.l2NetworkUuid)
1021+
# switch to NoVlan Network need keep physical dev ip and route in bridge
1022+
if not cmd.newVlan and cmd.oldVlan:
1023+
linux.move_dev_route(cmd.physicalInterfaceName, cmd.bridgeName)
1024+
# switch to Vlan Network will return bridge ip and route to physical dev
1025+
if cmd.newVlan and not cmd.oldVlan:
1026+
linux.move_dev_route(cmd.bridgeName, cmd.physicalInterfaceName)
1027+
logger.debug('successfully update bridge[%s] vlan interface from device[%s] to device[%s]'
1028+
% (cmd.bridgeName, cmd.oldVlanInterface, cmd.newVlanInterface))
1029+
except Exception as e:
1030+
logger.warning(traceback.format_exc())
1031+
rsp.error = ('unable to update bridge[%s] vlan interface from device[%s] to device[%s], because %s'
1032+
% (cmd.bridgeName, old_vlan_interface, new_vlan_interface, str(e)))
1033+
rsp.success = False
1034+
return jsonobject.dumps(rsp)
1035+
9461036
@lock.lock('bridge')
9471037
@kvmagent.replyerror
9481038
def create_bridge(self, req):
@@ -1475,6 +1565,8 @@ def start(self):
14751565
http_server.register_async_uri(KVM_CHANGE_LLDP_MODE_PATH, self.change_lldp_mode)
14761566
http_server.register_async_uri(KVM_GET_LLDP_INFO_PATH, self.get_lldp_info)
14771567
http_server.register_async_uri(KVM_APPLY_LLDP_CONFIG_PATH, self.apply_lldp_config)
1568+
http_server.register_async_uri(KVM_UPDATE_L2VLAN_NETWORK_PATH, self.update_vlan_bridge)
1569+
http_server.register_async_uri(KVM_UPDATE_L2VXLAN_NETWORK_PATH, self.update_vxlan_bridge)
14781570
http_server.register_async_uri(KVM_REALIZE_L2NOVLAN_NETWORK_PATH, self.create_bridge)
14791571
http_server.register_async_uri(KVM_REALIZE_L2VLAN_NETWORK_PATH, self.create_vlan_bridge)
14801572
http_server.register_async_uri(KVM_REALIZE_MACVLAN_L2VLAN_NETWORK_PATH, self.create_mac_vlan_eth)

zstacklib/zstacklib/utils/linux.py

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,15 @@ def set_bridge_alias_using_phy_nic_name(bridge_name, nic_name):
405405
def get_bridge_phy_nic_name_from_alias(bridge_name):
406406
return shell.call("ip link show %s | awk '/alias/{ print $NF; exit }'" % bridge_name).strip()
407407

408+
def get_bridge_alias_related_slave(bridge_name):
409+
phy_nic_alias = get_bridge_phy_nic_name_from_alias(bridge_name)
410+
slaves = shell.call("bridge link show | grep 'master %s'"
411+
" | awk '{print $2}' | sed 's/://' | sed 's/@.*$//'" % bridge_name).strip().split('\n')
412+
for slave in slaves:
413+
if slave.startswith(phy_nic_alias):
414+
return slave
415+
return None
416+
408417
def get_total_disk_size(dir_path):
409418
stat = os.statvfs(dir_path)
410419
return stat.f_blocks * stat.f_frsize
@@ -1432,6 +1441,20 @@ def delete_bridge(bridge_name):
14321441
shell.run("ip link set %s down" % bridge_name)
14331442
shell.run("brctl delbr %s" % bridge_name)
14341443

1444+
def check_bridge_with_interface(vlan_interface, expected_bridge_name):
1445+
bridge_name = find_bridge_having_physical_interface(vlan_interface)
1446+
if bridge_name and bridge_name != expected_bridge_name:
1447+
raise Exception('failed to check vlan interface[%s], it has been occupied by bridge[%s]'
1448+
% (vlan_interface, bridge_name))
1449+
1450+
def update_bridge_interface_configuration(old_interface, new_interface, bridge_name, l2_network_uuid):
1451+
check_bridge_with_interface(old_interface, bridge_name)
1452+
ip_link_set_net_device_nomaster(old_interface)
1453+
ip_link_set_net_device_master(new_interface, bridge_name)
1454+
set_bridge_alias_using_phy_nic_name(bridge_name, new_interface)
1455+
set_device_uuid_alias(new_interface, l2_network_uuid)
1456+
1457+
14351458
def find_bridge_having_physical_interface(ifname):
14361459
if is_bridge_slave(ifname):
14371460
br_name = shell.call("cat /sys/class/net/%s/master/uevent | grep 'INTERFACE' | awk -F '=' '{printf $2}'" % ifname)
@@ -1476,6 +1499,14 @@ def ip_link_set_net_device_master(net_device, master):
14761499
if not actual_result or actual_result != master:
14771500
raise Exception("set net device[%s] master to [%s] failed, try again now" % (net_device, master))
14781501

1502+
@retry(times=2, sleep_time=1)
1503+
def ip_link_set_net_device_nomaster(net_device):
1504+
shell.call("ip link set %s nomaster" % net_device)
1505+
# Double check, because sometimes the master might not be removed successfully
1506+
actual_result = shell.call("cat /sys/class/net/%s/master/uevent | grep 'INTERFACE'" % net_device, exception=False).strip('\n')
1507+
if actual_result:
1508+
raise Exception("set net device[%s] nomaster failed, try again now" % net_device)
1509+
14791510
def delete_novlan_bridge(bridge_name, interface, move_route=True):
14801511
if not is_network_device_existing(bridge_name):
14811512
logger.debug("can not find bridge %s" % bridge_name)
@@ -1546,30 +1577,40 @@ def modify_device_state_in_networkmanager(device_name, state):
15461577
# MAC address.
15471578
shell.call("ip link set %s address `cat /sys/class/net/%s/address`" % (bridge_name, interface))
15481579

1549-
if not move_route:
1550-
return
1580+
if move_route:
1581+
move_dev_route(interface, bridge_name)
1582+
1583+
1584+
def move_dev_route(src_dev, dest_dev):
1585+
"""
1586+
Move IP address and routes from one network device (src_dev) to another (dest_dev).
15511587
1552-
out = shell.call('ip addr show dev %s | grep "inet "' % interface, exception=False)
1588+
Args:
1589+
- src_dev: The source device from which the IP and routes will be moved.
1590+
- dest_dev: The destination device to which the IP and routes will be moved.
1591+
"""
1592+
# Check if the source device has an IP address set
1593+
out = shell.call('ip addr show dev %s | grep "inet "' % src_dev, exception=False)
15531594
if not out:
1554-
logger.debug("Interface %s doesn't set ip address yet. No need to move route. " % interface)
1595+
logger.debug("Source device %s doesn't have an IP address set. No need to move routes." % src_dev)
15551596
return
15561597

1557-
#record old routes
1598+
# Record old routes associated with the source device
15581599
routes = []
1559-
r_out = shell.call("ip route show dev %s | grep via | sed 's/onlink//g'" % interface)
1600+
r_out = shell.call("ip route show dev %s | grep via | sed 's/onlink//g'" % src_dev)
15601601
for line in r_out.split('\n'):
15611602
if line != "":
15621603
routes.append(line)
15631604
shell.call('ip route del %s' % line)
15641605

1565-
#mv ip on interface to bridge
1606+
# Move IP address from the source device to the destination device
15661607
ip = out.strip().split()[1]
1567-
shell.call('ip addr del %s dev %s' % (ip, interface))
1568-
r_out = shell.call('ip addr show dev %s | grep "inet %s"' % (bridge_name, ip), exception=False)
1608+
shell.call('ip addr del %s dev %s' % (ip, src_dev))
1609+
r_out = shell.call('ip addr show dev %s | grep "inet %s"' % (dest_dev, ip), exception=False)
15691610
if not r_out:
1570-
shell.call('ip addr add %s dev %s' % (ip, bridge_name))
1611+
shell.call('ip addr add %s dev %s' % (ip, dest_dev))
15711612

1572-
#restore routes on bridge
1613+
# Restore routes on the destination device
15731614
for r in routes:
15741615
shell.call('ip route add %s' % r)
15751616

@@ -2405,6 +2446,37 @@ def get_nics_by_cidr(cidr):
24052446

24062447
return nics
24072448

2449+
def get_vxlan_details(vxlan_interface):
2450+
cmd = shell.ShellCmd("ip -d link show dev {name}".format(name=vxlan_interface))
2451+
cmd(is_exception=False)
2452+
if cmd.return_code == 0:
2453+
for line in cmd.stdout.split("\n"):
2454+
if "vxlan id" in line:
2455+
vtep_ip = line.split("local ")[1].split(" ")[0]
2456+
dst_port = line.split("dstport ")[1].split(" ")[0]
2457+
return vtep_ip, dst_port
2458+
return None, None
2459+
2460+
2461+
def change_vxlan_interface(old_vni, new_vni):
2462+
old_vxlan = "vxlan" + str(old_vni)
2463+
vtep_ip, dst_port = get_vxlan_details(old_vxlan)
2464+
if not vtep_ip or not dst_port:
2465+
raise Exception("Failed to get details for VXLAN interface: {}".format(old_vxlan))
2466+
new_vxlan = "vxlan" + str(new_vni)
2467+
create_vxlan_interface(new_vni, vtep_ip, dst_port)
2468+
cmd = shell.ShellCmd("ip link set %s address `cat /sys/class/net/%s/address`" % (new_vxlan, old_vxlan))
2469+
cmd(is_exception=False)
2470+
cmd = shell.ShellCmd("ip link set {name} down".format(name=old_vxlan))
2471+
cmd(is_exception=False)
2472+
cmd = shell.ShellCmd("ip link set {name} up".format(name=new_vxlan))
2473+
cmd(is_exception=False)
2474+
if cmd.return_code != 0:
2475+
raise Exception("Failed to set new VXLAN interface up: {}".format(new_vxlan))
2476+
2477+
logger.debug("Successfully changed VXLAN interface from {old} to {new}.".format(old=old_vxlan, new=new_vxlan))
2478+
2479+
24082480
def create_vxlan_interface(vni, vtepIp,dstport):
24092481
vni = str(vni)
24102482
cmd = shell.ShellCmd("ip -d -o link show dev {name} | grep -w {ip} ".format(**{"name": "vxlan" + vni, "ip": vtepIp}))

0 commit comments

Comments
 (0)