Skip to content

Commit 612e390

Browse files
Add connectivity_url to Oracle's EphemeralDHCPv4 (canonical#988)
Add connectivity_url to Oracle's EphemeralDHCPv4 On bionic, when trying to bring up the EphemeralDHCPv4, it's possible that we already have a route defined, which will result in an error when trying to add the DHCP route. Use the connectivity_url to check if we can reach the metadata service, and if so, skip the EphemeralDHCPv4. The has_url_connectivity function has also been modified to take a dict of kwargs to send to readurl. LP: #1939603
1 parent cb82a45 commit 612e390

File tree

7 files changed

+78
-32
lines changed

7 files changed

+78
-32
lines changed

cloudinit/net/__init__.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import logging
1212
import os
1313
import re
14+
from typing import Any, Dict
1415

1516
from cloudinit import subp
1617
from cloudinit import util
@@ -971,18 +972,33 @@ def get_ib_hwaddrs_by_interface():
971972
return ret
972973

973974

974-
def has_url_connectivity(url):
975-
"""Return true when the instance has access to the provided URL
975+
def has_url_connectivity(url_data: Dict[str, Any]) -> bool:
976+
"""Return true when the instance has access to the provided URL.
976977
977978
Logs a warning if url is not the expected format.
979+
980+
url_data is a dictionary of kwargs to send to readurl. E.g.:
981+
982+
has_url_connectivity({
983+
"url": "http://example.invalid",
984+
"headers": {"some": "header"},
985+
"timeout": 10
986+
})
978987
"""
988+
if 'url' not in url_data:
989+
LOG.warning(
990+
"Ignoring connectivity check. No 'url' to check in %s", url_data)
991+
return False
992+
url = url_data['url']
979993
if not any([url.startswith('http://'), url.startswith('https://')]):
980994
LOG.warning(
981995
"Ignoring connectivity check. Expected URL beginning with http*://"
982996
" received '%s'", url)
983997
return False
998+
if 'timeout' not in url_data:
999+
url_data['timeout'] = 5
9841000
try:
985-
readurl(url, timeout=5)
1001+
readurl(**url_data)
9861002
except UrlError:
9871003
return False
9881004
return True
@@ -1025,14 +1041,15 @@ class EphemeralIPv4Network(object):
10251041
10261042
No operations are performed if the provided interface already has the
10271043
specified configuration.
1028-
This can be verified with the connectivity_url.
1044+
This can be verified with the connectivity_url_data.
10291045
If unconnected, bring up the interface with valid ip, prefix and broadcast.
10301046
If router is provided setup a default route for that interface. Upon
10311047
context exit, clean up the interface leaving no configuration behind.
10321048
"""
10331049

10341050
def __init__(self, interface, ip, prefix_or_mask, broadcast, router=None,
1035-
connectivity_url=None, static_routes=None):
1051+
connectivity_url_data: Dict[str, Any] = None,
1052+
static_routes=None):
10361053
"""Setup context manager and validate call signature.
10371054
10381055
@param interface: Name of the network interface to bring up.
@@ -1041,7 +1058,7 @@ def __init__(self, interface, ip, prefix_or_mask, broadcast, router=None,
10411058
prefix.
10421059
@param broadcast: Broadcast address for the IPv4 network.
10431060
@param router: Optionally the default gateway IP.
1044-
@param connectivity_url: Optionally, a URL to verify if a usable
1061+
@param connectivity_url_data: Optionally, a URL to verify if a usable
10451062
connection already exists.
10461063
@param static_routes: Optionally a list of static routes from DHCP
10471064
"""
@@ -1056,7 +1073,7 @@ def __init__(self, interface, ip, prefix_or_mask, broadcast, router=None,
10561073
'Cannot setup network: {0}'.format(e)
10571074
) from e
10581075

1059-
self.connectivity_url = connectivity_url
1076+
self.connectivity_url_data = connectivity_url_data
10601077
self.interface = interface
10611078
self.ip = ip
10621079
self.broadcast = broadcast
@@ -1066,11 +1083,11 @@ def __init__(self, interface, ip, prefix_or_mask, broadcast, router=None,
10661083

10671084
def __enter__(self):
10681085
"""Perform ephemeral network setup if interface is not connected."""
1069-
if self.connectivity_url:
1070-
if has_url_connectivity(self.connectivity_url):
1086+
if self.connectivity_url_data:
1087+
if has_url_connectivity(self.connectivity_url_data):
10711088
LOG.debug(
10721089
'Skip ephemeral network setup, instance has connectivity'
1073-
' to %s', self.connectivity_url)
1090+
' to %s', self.connectivity_url_data['url'])
10741091
return
10751092

10761093
self._bringup_device()

cloudinit/net/dhcp.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#
55
# This file is part of cloud-init. See LICENSE file for license information.
66

7+
from typing import Dict, Any
78
import configobj
89
import logging
910
import os
@@ -38,21 +39,26 @@ class NoDHCPLeaseError(Exception):
3839

3940

4041
class EphemeralDHCPv4(object):
41-
def __init__(self, iface=None, connectivity_url=None, dhcp_log_func=None):
42+
def __init__(
43+
self,
44+
iface=None,
45+
connectivity_url_data: Dict[str, Any] = None,
46+
dhcp_log_func=None
47+
):
4248
self.iface = iface
4349
self._ephipv4 = None
4450
self.lease = None
4551
self.dhcp_log_func = dhcp_log_func
46-
self.connectivity_url = connectivity_url
52+
self.connectivity_url_data = connectivity_url_data
4753

4854
def __enter__(self):
4955
"""Setup sandboxed dhcp context, unless connectivity_url can already be
5056
reached."""
51-
if self.connectivity_url:
52-
if has_url_connectivity(self.connectivity_url):
57+
if self.connectivity_url_data:
58+
if has_url_connectivity(self.connectivity_url_data):
5359
LOG.debug(
5460
'Skip ephemeral DHCP setup, instance has connectivity'
55-
' to %s', self.connectivity_url)
61+
' to %s', self.connectivity_url_data)
5662
return
5763
return self.obtain_lease()
5864

@@ -104,8 +110,8 @@ def obtain_lease(self):
104110
if kwargs['static_routes']:
105111
kwargs['static_routes'] = (
106112
parse_static_routes(kwargs['static_routes']))
107-
if self.connectivity_url:
108-
kwargs['connectivity_url'] = self.connectivity_url
113+
if self.connectivity_url_data:
114+
kwargs['connectivity_url_data'] = self.connectivity_url_data
109115
ephipv4 = EphemeralIPv4Network(**kwargs)
110116
ephipv4.__enter__()
111117
self._ephipv4 = ephipv4

cloudinit/net/tests/test_dhcp.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,9 @@ def test_ephemeral_dhcp_no_network_if_url_connectivity(self, m_dhcp):
617617
url = 'http://example.org/index.html'
618618

619619
httpretty.register_uri(httpretty.GET, url)
620-
with net.dhcp.EphemeralDHCPv4(connectivity_url=url) as lease:
620+
with net.dhcp.EphemeralDHCPv4(
621+
connectivity_url_data={'url': url},
622+
) as lease:
621623
self.assertIsNone(lease)
622624
# Ensure that no teardown happens:
623625
m_dhcp.assert_not_called()
@@ -635,7 +637,9 @@ def test_ephemeral_dhcp_setup_network_if_url_connectivity(
635637
m_subp.return_value = ('', '')
636638

637639
httpretty.register_uri(httpretty.GET, url, body={}, status=404)
638-
with net.dhcp.EphemeralDHCPv4(connectivity_url=url) as lease:
640+
with net.dhcp.EphemeralDHCPv4(
641+
connectivity_url_data={'url': url},
642+
) as lease:
639643
self.assertEqual(fake_lease, lease)
640644
# Ensure that dhcp discovery occurs
641645
m_dhcp.called_once_with()

cloudinit/net/tests/test_init.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -622,11 +622,14 @@ def test_ephemeral_ipv4_no_network_if_url_connectivity(
622622
params = {
623623
'interface': 'eth0', 'ip': '192.168.2.2',
624624
'prefix_or_mask': '255.255.255.0', 'broadcast': '192.168.2.255',
625-
'connectivity_url': 'http://example.org/index.html'}
625+
'connectivity_url_data': {'url': 'http://example.org/index.html'}
626+
}
626627

627628
with net.EphemeralIPv4Network(**params):
628-
self.assertEqual([mock.call('http://example.org/index.html',
629-
timeout=5)], m_readurl.call_args_list)
629+
self.assertEqual(
630+
[mock.call(url='http://example.org/index.html', timeout=5)],
631+
m_readurl.call_args_list
632+
)
630633
# Ensure that no teardown happens:
631634
m_subp.assert_has_calls([])
632635

@@ -850,25 +853,28 @@ def setUp(self):
850853
def test_url_timeout_on_connectivity_check(self, m_readurl):
851854
"""A timeout of 5 seconds is provided when reading a url."""
852855
self.assertTrue(
853-
net.has_url_connectivity(self.url), 'Expected True on url connect')
856+
net.has_url_connectivity({'url': self.url}),
857+
'Expected True on url connect')
854858

855859
def test_true_on_url_connectivity_success(self):
856860
httpretty.register_uri(httpretty.GET, self.url)
857861
self.assertTrue(
858-
net.has_url_connectivity(self.url), 'Expected True on url connect')
862+
net.has_url_connectivity({'url': self.url}),
863+
'Expected True on url connect')
859864

860865
@mock.patch('requests.Session.request')
861866
def test_true_on_url_connectivity_timeout(self, m_request):
862867
"""A timeout raised accessing the url will return False."""
863868
m_request.side_effect = requests.Timeout('Fake Connection Timeout')
864869
self.assertFalse(
865-
net.has_url_connectivity(self.url),
870+
net.has_url_connectivity({'url': self.url}),
866871
'Expected False on url timeout')
867872

868873
def test_true_on_url_connectivity_failure(self):
869874
httpretty.register_uri(httpretty.GET, self.url, body={}, status=404)
870875
self.assertFalse(
871-
net.has_url_connectivity(self.url), 'Expected False on url fail')
876+
net.has_url_connectivity({'url': self.url}),
877+
'Expected False on url fail')
872878

873879

874880
def _mk_v1_phys(mac, name, driver, device_id):

cloudinit/sources/DataSourceOracle.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
# https://docs.cloud.oracle.com/iaas/Content/Network/Troubleshoot/connectionhang.htm#Overview,
4141
# indicates that an MTU of 9000 is used within OCI
4242
MTU = 9000
43+
V2_HEADERS = {"Authorization": "Bearer Oracle"}
4344

4445
OpcMetadata = namedtuple("OpcMetadata", "version instance_data vnics_data")
4546

@@ -134,7 +135,13 @@ def _get_data(self):
134135
)
135136
network_context = noop()
136137
if not _is_iscsi_root():
137-
network_context = dhcp.EphemeralDHCPv4(net.find_fallback_nic())
138+
network_context = dhcp.EphemeralDHCPv4(
139+
iface=net.find_fallback_nic(),
140+
connectivity_url_data={
141+
"url": METADATA_PATTERN.format(version=2, path="instance"),
142+
"headers": V2_HEADERS,
143+
}
144+
)
138145
with network_context:
139146
fetched_metadata = read_opc_metadata(
140147
fetch_vnics_data=fetch_vnics_data
@@ -304,11 +311,9 @@ def read_opc_metadata(*, fetch_vnics_data: bool = False):
304311
retries = 2
305312

306313
def _fetch(metadata_version: int, path: str) -> dict:
307-
headers = {
308-
"Authorization": "Bearer Oracle"} if metadata_version > 1 else None
309314
return readurl(
310315
url=METADATA_PATTERN.format(version=metadata_version, path=path),
311-
headers=headers,
316+
headers=V2_HEADERS if metadata_version > 1 else None,
312317
retries=retries,
313318
)._response.json()
314319

cloudinit/sources/helpers/vultr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
def get_metadata(url, timeout, retries, sec_between):
2121
# Bring up interface
2222
try:
23-
with EphemeralDHCPv4(connectivity_url=url):
23+
with EphemeralDHCPv4(connectivity_url_data={"url": url}):
2424
# Fetch the metadata
2525
v1 = read_metadata(url, timeout, retries, sec_between)
2626
except (NoDHCPLeaseError) as exc:

cloudinit/sources/tests/test_oracle.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,15 @@ def assert_in_context_manager(**kwargs):
694694
assert oracle_ds._get_data()
695695

696696
assert [
697-
mock.call(m_find_fallback_nic.return_value)
697+
mock.call(
698+
iface=m_find_fallback_nic.return_value,
699+
connectivity_url_data={
700+
'headers': {
701+
'Authorization': 'Bearer Oracle'
702+
},
703+
'url': 'http://169.254.169.254/opc/v2/instance/'
704+
}
705+
)
698706
] == m_EphemeralDHCPv4.call_args_list
699707

700708

0 commit comments

Comments
 (0)