Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 32 additions & 95 deletions cloudinit/sources/helpers/vultr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from cloudinit import dmi
from cloudinit import log as log
from cloudinit import net, netinfo, subp, url_helper, util
from cloudinit import net, subp, url_helper, util
from cloudinit.net.dhcp import EphemeralDHCPv4, NoDHCPLeaseError

# Get LOG
Expand All @@ -30,9 +30,6 @@ def get_metadata(url, timeout, retries, sec_between, agent):
with EphemeralDHCPv4(
iface=iface[0], connectivity_url_data={"url": url}
):
# Set metadata route
set_route(iface[0])

# Fetch the metadata
v1 = read_metadata(url, timeout, retries, sec_between, agent)

Expand All @@ -43,49 +40,6 @@ def get_metadata(url, timeout, retries, sec_between, agent):
raise exception


# Set route for metadata
def set_route(iface):
# Get routes, confirm entry does not exist
routes = netinfo.route_info()

# If no tools exist and empty dict is returned
if "ipv4" not in routes:
return

# We only care about IPv4
routes = routes["ipv4"]

# Searchable list
dests = []

# Parse each route into a more searchable format
for route in routes:
dests.append(route["destination"])

gw_present = "100.64.0.0" in dests or "100.64.0.0/10" in dests
dest_present = "169.254.169.254" in dests

# If not IPv6 only (No link local)
# or the route is already present
if not gw_present or dest_present:
return

# Set metadata route
if subp.which("ip"):
subp.subp(
[
"ip",
"route",
"add",
"169.254.169.254/32",
"dev",
iface,
]
)
elif subp.which("route"):
subp.subp(["route", "add", "-net", "169.254.169.254/32", "100.64.0.1"])


# Read the system information from SMBIOS
def get_sysinfo():
return {
Expand Down Expand Up @@ -165,19 +119,18 @@ def generate_network_config(interfaces):

# Prepare interface 0, public
if len(interfaces) > 0:
public = generate_public_network_interface(interfaces[0])
public = generate_interface(interfaces[0], primary=True)
network["config"].append(public)

# Prepare additional interfaces, private
for i in range(1, len(interfaces)):
private = generate_private_network_interface(interfaces[i])
private = generate_interface(interfaces[i])
network["config"].append(private)

return network


# Input Metadata and generate public network config part
def generate_public_network_interface(interface):
def generate_interface(interface, primary=False):
interface_name = get_interface_name(interface["mac"])
if not interface_name:
raise RuntimeError(
Expand All @@ -188,13 +141,33 @@ def generate_public_network_interface(interface):
"name": interface_name,
"type": "physical",
"mac_address": interface["mac"],
"accept-ra": 1,
"subnets": [
}

if primary:
netcfg["accept-ra"] = 1
netcfg["subnets"] = [
{"type": "dhcp", "control": "auto"},
{"type": "ipv6_slaac", "control": "auto"},
],
}
]

if not primary:
netcfg["subnets"] = [
{
"type": "static",
"control": "auto",
"address": interface["ipv4"]["address"],
"netmask": interface["ipv4"]["netmask"],
}
]

generate_interface_routes(interface, netcfg)
generate_interface_additional_addresses(interface, netcfg)

# Add config to template
return netcfg


def generate_interface_routes(interface, netcfg):
# Options that may or may not be used
if "mtu" in interface:
netcfg["mtu"] = interface["mtu"]
Expand All @@ -205,6 +178,8 @@ def generate_public_network_interface(interface):
if "routes" in interface:
netcfg["subnets"][0]["routes"] = interface["routes"]


def generate_interface_additional_addresses(interface, netcfg):
# Check for additional IP's
additional_count = len(interface["ipv4"]["additional"])
if "ipv4" in interface and additional_count > 0:
Expand All @@ -228,53 +203,15 @@ def generate_public_network_interface(interface):
add = {
"type": "static6",
"control": "auto",
"address": additional["address"],
"netmask": additional["netmask"],
"address": "%s/%s"
% (additional["network"], additional["prefix"]),
}

if "routes" in additional:
add["routes"] = additional["routes"]

netcfg["subnets"].append(add)

# Add config to template
return netcfg


# Input Metadata and generate private network config part
def generate_private_network_interface(interface):
interface_name = get_interface_name(interface["mac"])
if not interface_name:
raise RuntimeError(
"Interface: %s could not be found on the system" % interface["mac"]
)

netcfg = {
"name": interface_name,
"type": "physical",
"mac_address": interface["mac"],
"subnets": [
{
"type": "static",
"control": "auto",
"address": interface["ipv4"]["address"],
"netmask": interface["ipv4"]["netmask"],
}
],
}

# Options that may or may not be used
if "mtu" in interface:
netcfg["mtu"] = interface["mtu"]

if "accept-ra" in interface:
netcfg["accept-ra"] = interface["accept-ra"]

if "routes" in interface:
netcfg["subnets"][0]["routes"] = interface["routes"]

return netcfg


# Make required adjustments to the network configs provided
def add_interface_names(interfaces):
Expand Down
53 changes: 52 additions & 1 deletion tests/unittests/sources/test_vultr.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import json

from cloudinit import helpers, settings
from cloudinit.net.dhcp import NoDHCPLeaseError
from cloudinit.sources import DataSourceVultr
from cloudinit.sources.helpers import vultr
from tests.unittests.helpers import CiTestCase, mock
Expand Down Expand Up @@ -95,7 +96,9 @@
"netmask": "255.255.254.0",
},
"ipv6": {
"additional": [],
"additional": [
{"network": "2002:19f0:5:28a7::", "prefix": "64"}
],
"address": "2001:19f0:5:28a7:5400:03ff:fe1b:4eca",
"network": "2001:19f0:5:28a7::",
"prefix": "64",
Expand Down Expand Up @@ -138,6 +141,14 @@

SSH_KEYS_1 = ["ssh-rsa AAAAB3NzaC1y...IQQhv5PAOKaIl+mM3c= test3@key"]

INTERFACES = [
["lo", "56:00:03:15:c4:00", "drv", "devid0"],
["dummy0", "56:00:03:15:c4:01", "drv", "devid1"],
["eth1", "56:00:03:15:c4:02", "drv", "devid2"],
["eth0", "56:00:03:15:c4:04", "drv", "devid4"],
["eth2", "56:00:03:15:c4:03", "drv", "devid3"],
]

# Expected generated objects

# Expected config
Expand Down Expand Up @@ -182,6 +193,11 @@
"subnets": [
{"type": "dhcp", "control": "auto"},
{"type": "ipv6_slaac", "control": "auto"},
{
"type": "static6",
"control": "auto",
"address": "2002:19f0:5:28a7::/64",
},
],
},
{
Expand All @@ -208,6 +224,9 @@
}


EPHERMERAL_USED = ""


class TestDataSourceVultr(CiTestCase):
def setUp(self):
super(TestDataSourceVultr, self).setUp()
Expand Down Expand Up @@ -284,5 +303,37 @@ def test_private_network_config(self, mock_netmap):
EXPECTED_VULTR_NETWORK_2, vultr.generate_network_config(interf)
)

def ephemeral_init(self, iface="", connectivity_url_data=None):
global EPHERMERAL_USED
EPHERMERAL_USED = iface
if iface == "eth0":
return
raise NoDHCPLeaseError("Generic for testing")

# Test interface seeking to ensure we are able to find the correct one
@mock.patch("cloudinit.net.dhcp.EphemeralDHCPv4.__init__", ephemeral_init)
@mock.patch("cloudinit.sources.helpers.vultr.is_vultr")
@mock.patch("cloudinit.sources.helpers.vultr.read_metadata")
@mock.patch("cloudinit.net.get_interfaces")
def test_interface_seek(
self, mock_get_interfaces, mock_read_metadata, mock_isvultr
):
mock_read_metadata.side_effect = NoDHCPLeaseError(
"Generic for testing"
)
mock_isvultr.return_value = True
mock_get_interfaces.return_value = INTERFACES

source = DataSourceVultr.DataSourceVultr(
settings.CFG_BUILTIN, None, helpers.Paths({"run_dir": self.tmp})
)

try:
source._get_data()
except Exception:
pass

self.assertEqual(EPHERMERAL_USED, INTERFACES[3][0])


# vi: ts=4 expandtab