Skip to content

Commit 21e0b71

Browse files
authored
Merge pull request #13 from rackerlabs/iterative-updates
adjust networking to use expected MAC addresses for traffic
2 parents fa6277e + ad216df commit 21e0b71

File tree

14 files changed

+295
-155
lines changed

14 files changed

+295
-155
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,11 @@ source .venv/bin/activate
6262
pip install esxi-img
6363
esxi-img --output esxi.img path/to/esxi.iso
6464
```
65+
66+
## ESXi Network Interfaces
67+
68+
ESXi has physical network interfaces and logical interfaces. The `vmnicX`
69+
interfaces are the physical interfaces and the `vmkX` interfaces are the
70+
logical interfaces, which are also known as VMkernel interfaces.
71+
72+
![Diagram of ESXi interfaces](/docs/assets/esxi-interfaces.png "Explanation of interfaces")

docs/assets/esxi-interfaces.png

738 KB
Loading

packages/esxi-netinit/esxi_netinit/esxconfig.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
1+
import logging
12
from functools import cached_property
23

34
from .esxhost import ESXHost
45
from .network_data import NetworkData
56
from .nic import NIC
67
from .nic_list import NICList
78

9+
logger = logging.getLogger(__name__)
10+
811

912
class ESXConfig:
1013
def __init__(self, network_data: NetworkData, dry_run=False) -> None:
1114
self.network_data = network_data
1215
self.dry_run = dry_run
1316
self.host = ESXHost(dry_run)
1417

15-
def add_default_mgmt_interface(
16-
self, portgroup_name, switch_name, interface_name="vmk0"
17-
):
18-
self.host.portgroup_add(portgroup_name=portgroup_name, switch_name=switch_name)
19-
self.host.add_ip_interface(name=interface_name, portgroup_name=portgroup_name)
20-
2118
def clean_default_network_setup(self, portgroup_name, switch_name):
2219
"""Removes default networking setup left by the installer."""
2320
self.host.delete_vmknic(portgroup_name=portgroup_name)
@@ -26,6 +23,11 @@ def clean_default_network_setup(self, portgroup_name, switch_name):
2623
)
2724
self.host.destroy_vswitch(name=switch_name)
2825

26+
def configure_portgroups(self, switch_name: str, portgroups):
27+
"""Adds each requested portgroup to the specified switch."""
28+
for portgroup_name in portgroups:
29+
self.host.portgroup_add(portgroup_name, switch_name)
30+
2931
def configure_default_route(self):
3032
"""Configures default route.
3133
@@ -34,7 +36,7 @@ def configure_default_route(self):
3436
route = self.network_data.default_route()
3537
self.host.configure_default_route(route.gateway)
3638

37-
def configure_portgroups(self, switch_name="vSwitch0"):
39+
def configure_vlans(self, switch_name="vSwitch0"):
3840
portgroups = []
3941
for link in self.network_data.links:
4042
if link.type == "vlan":
@@ -45,13 +47,23 @@ def configure_portgroups(self, switch_name="vSwitch0"):
4547
portgroups.append(pg_name)
4648
return portgroups
4749

48-
def configure_management_interface(self):
49-
mgmt_network = next(
50-
net for net in self.network_data.networks if net.default_routes()
51-
)
52-
return self.host.change_ip(
53-
"vmk0", mgmt_network.ip_address, mgmt_network.netmask
54-
)
50+
def configure_management_interface(self, mgmt_portgroup: str):
51+
for net in self.network_data.networks:
52+
logger.info(
53+
"Creating %s with MAC %s for network %s",
54+
net.id,
55+
net.link.ethernet_mac_address,
56+
net.network_id,
57+
)
58+
self.host.add_ip_interface(
59+
net.id, mgmt_portgroup, net.link.ethernet_mac_address, net.link.mtu
60+
)
61+
if net.type == "ipv4":
62+
self.host.set_static_ipv4(net.id, net.ip_address, net.netmask)
63+
elif net.type == "ipv4_dhcp":
64+
self.host.set_dhcp_ipv4(net.id)
65+
else:
66+
raise NotImplementedError(f"net type {net.type}")
5567

5668
def configure_vswitch(self, uplink: NIC, switch_name: str, mtu: int):
5769
"""Sets up vSwitch."""

packages/esxi-netinit/esxi_netinit/esxhost.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import logging
12
import subprocess
23

4+
logger = logging.getLogger(__name__)
5+
36

47
class ESXHost:
58
"""Low level commands for configuring various aspects of ESXi hypervisor."""
@@ -9,13 +12,17 @@ def __init__(self, dry_run=False) -> None:
912

1013
def __execute(self, cmd: list):
1114
if self.dry_run:
12-
print(f"Would execute: {' '.join(cmd)}")
15+
logger.info("Would execute: %s", " ".join(cmd))
1316
return cmd
1417
else:
18+
logger.debug("Executing %s", cmd)
1519
subprocess.run(cmd, check=True) # noqa: S603
1620

17-
def add_ip_interface(self, name, portgroup_name):
21+
def add_ip_interface(self, inf: str, portgroup_name: str, mac: str, mtu: int):
1822
"""Adds IP interface."""
23+
logger.info(
24+
"Adding IP interface %s (%s) for portgroup %s", inf, mac, portgroup_name
25+
)
1926
return self.__execute(
2027
[
2128
"/bin/esxcli",
@@ -24,7 +31,11 @@ def add_ip_interface(self, name, portgroup_name):
2431
"interface",
2532
"add",
2633
"--interface-name",
27-
name,
34+
inf,
35+
"--mac-address",
36+
mac,
37+
"--mtu",
38+
str(mtu),
2839
"--portgroup-name",
2940
portgroup_name,
3041
]
@@ -65,6 +76,43 @@ def change_ip(self, interface, ip, netmask):
6576
]
6677
return self.__execute(cmd)
6778

79+
def set_dhcp_ipv4(self, inf: str):
80+
"""Configures DHCP (IPv4) on an interface."""
81+
logger.info("Configuring IPv4 interface %s with DHCP", inf)
82+
cmd = [
83+
"/bin/esxcli",
84+
"network",
85+
"ip",
86+
"interface",
87+
"ipv4",
88+
"set",
89+
"--interface-name",
90+
inf,
91+
"--peer-dns=true",
92+
"--type=dhcp",
93+
]
94+
return self.__execute(cmd)
95+
96+
def set_static_ipv4(self, inf: str, ip_addr: str, netmask: str):
97+
"""Configures a static IPv4 address on an interface."""
98+
logger.info("Configuring IPv4 interface %s with static IP %s", inf, ip_addr)
99+
cmd = [
100+
"/bin/esxcli",
101+
"network",
102+
"ip",
103+
"interface",
104+
"ipv4",
105+
"set",
106+
"--interface-name",
107+
inf,
108+
"--type=static",
109+
"--ipv4",
110+
ip_addr,
111+
"--netmask",
112+
netmask,
113+
]
114+
return self.__execute(cmd)
115+
68116
def configure_dns(self, servers=None, search=None):
69117
"""Sets up arbitrary DNS servers."""
70118
if not servers:

packages/esxi-netinit/esxi_netinit/main.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import argparse
2+
import logging
3+
import logging.handlers
4+
import os
25
import sys
36

47
from esxi_netinit.esxconfig import ESXConfig
@@ -10,6 +13,31 @@
1013
NEW_VSWITCH = "vSwitch22"
1114

1215

16+
logger = logging.getLogger(__name__)
17+
18+
19+
def setup_logger(log_level=logging.INFO):
20+
"""Set up the root logger.
21+
22+
Output to stdout and to syslog at the requested log_level.
23+
"""
24+
logging.basicConfig(
25+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
26+
level=log_level,
27+
)
28+
29+
app_name = os.path.basename(sys.argv[0])
30+
31+
syslog_fmt = logging.Formatter(f"{app_name}: %(levelname)s - %(message)s")
32+
try:
33+
syslog_handler = logging.handlers.SysLogHandler()
34+
syslog_handler.setLevel(log_level)
35+
syslog_handler.setFormatter(syslog_fmt)
36+
logging.getLogger().addHandler(syslog_handler)
37+
except Exception:
38+
logger.error("Failed to setup syslog for logging")
39+
40+
1341
def main(json_file, dry_run):
1442
network_data = NetworkData.from_json_file(json_file)
1543
esx = ESXConfig(network_data, dry_run=dry_run)
@@ -18,9 +46,10 @@ def main(json_file, dry_run):
1846
uplink=esx.identify_uplink(), switch_name=NEW_VSWITCH, mtu=9000
1947
)
2048

21-
esx.configure_portgroups()
22-
esx.add_default_mgmt_interface(NEW_MGMT_PG, NEW_VSWITCH)
23-
esx.configure_management_interface()
49+
# this configures the Management Network to the default vSwitch
50+
esx.configure_portgroups(NEW_VSWITCH, [NEW_MGMT_PG])
51+
esx.configure_vlans()
52+
esx.configure_management_interface(NEW_MGMT_PG)
2453
esx.configure_default_route()
2554
esx.configure_requested_dns()
2655

@@ -35,8 +64,10 @@ def main(json_file, dry_run):
3564
)
3665
args = parser.parse_args()
3766

67+
setup_logger()
68+
3869
try:
3970
main(args.json_file, args.dry_run)
40-
except Exception as e:
41-
print(f"Error configuring network: {str(e)}")
71+
except Exception:
72+
logger.exception("Error configuring network")
4273
sys.exit(1)

packages/esxi-netinit/esxi_netinit/network_data.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,23 @@ def __init__(self, data: dict) -> None:
1313
self.data = data
1414
self.links = self._init_links(data.get("links", []))
1515
self.networks = []
16+
counter = 0
1617

1718
for net_data in data.get("networks", []):
1819
net_data = net_data.copy()
1920
routes_data = net_data.pop("routes", [])
2021
routes = [Route(**route) for route in routes_data]
21-
link_id = net_data.pop("link", [])
22+
# VMware wants vmkX to be the pattern for logical interfaces
23+
# networks map to logical interfaces so ensure we use the right
24+
# value
25+
net_data["id"] = f"vmk{counter}"
26+
# increment for the next one
27+
counter = counter + 1
28+
link_id = net_data.pop("link")
29+
if not link_id:
30+
raise ValueError(
31+
f"Network {net_data.get('network_id')} is invalid, no link supplied"
32+
) from None
2233
try:
2334
relevant_link = next(link for link in self.links if link.id == link_id)
2435
except StopIteration:

packages/esxi-netinit/esxi_netinit/nic_list.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,7 @@ def _esxi_nics(self) -> str:
3131
).stdout.decode()
3232

3333
def find_by_mac(self, mac) -> NIC:
34-
return next(nic for nic in self if nic.mac == mac)
34+
try:
35+
return next(nic for nic in self if nic.mac == mac)
36+
except StopIteration:
37+
raise ValueError(f"No NIC with MAC {mac}") from None

0 commit comments

Comments
 (0)