1010
1111from pycloudlib .errors import PycloudlibError
1212from pycloudlib .instance import BaseInstance
13- from pycloudlib .oci .utils import get_subnet_id , get_subnet_id_by_name , wait_till_ready
13+ from pycloudlib .oci .utils import (
14+ generate_create_vnic_details ,
15+ get_subnet_id ,
16+ get_subnet_id_by_name ,
17+ wait_till_ready ,
18+ )
19+ from pycloudlib .types import NetworkingConfig
1420
1521
1622class OciInstance (BaseInstance ):
@@ -27,6 +33,7 @@ def __init__(
2733 oci_config = None ,
2834 * ,
2935 username : Optional [str ] = None ,
36+ vcn_name : Optional [str ] = None ,
3037 ):
3138 """Set up the instance.
3239
@@ -46,6 +53,7 @@ def __init__(
4653 self .availability_domain = availability_domain
4754 self ._fault_domain = None
4855 self ._ip = None
56+ self ._vcn_name : Optional [str ] = vcn_name
4957
5058 if oci_config is None :
5159 oci_config = oci .config .from_file ("~/.oci/config" ) # noqa: E501
@@ -145,7 +153,8 @@ def secondary_vnic_private_ip(self) -> Optional[str]:
145153 for vnic_attachment in vnic_attachments
146154 ]
147155 secondary_vnic_attachment = [vnic for vnic in vnics if not vnic .is_primary ][0 ]
148- return secondary_vnic_attachment .private_ip
156+ self ._log .debug ("secondary vnic attachment data:\n %s" , secondary_vnic_attachment )
157+ return secondary_vnic_attachment .private_ip or secondary_vnic_attachment .ipv6_addresses [0 ]
149158
150159 @property
151160 def instance_data (self ):
@@ -258,7 +267,7 @@ def get_secondary_vnic_ip(self) -> str:
258267 def add_network_interface (
259268 self ,
260269 nic_index : int = 0 ,
261- use_private_subnet : bool = False ,
270+ networking_config : Optional [ NetworkingConfig ] = None ,
262271 subnet_name : Optional [str ] = None ,
263272 ** kwargs : Any ,
264273 ) -> str :
@@ -270,13 +279,19 @@ def add_network_interface(
270279
271280 Args:
272281 nic_index: The index of the NIC to add
273- subnet_name: Name of the subnet to add the NIC to. If not provided,
274- will use `use_private_subnet` to select first available subnet.
275- use_private_subnet: If True, will select the first available private
276- subnet. If False, will select the first available public subnet.
277- This is only used if `subnet_name` is not provided.
282+ networking_config: Networking configuration to use when selecting subnet. This specifies
283+ the networking type (ipv4, ipv6, or dualstack) and whether to use a public or
284+ private subnet. If not provided, will default to selecting the first public subnet
285+ found.
286+ subnet_name: Name of the subnet to add the NIC to. If provided, this subnet will
287+ blindly be selected and networking_config will be ignored.
288+
289+ Returns:
290+ str: The private IP address of the added network interface.
278291 """
279292 if subnet_name :
293+ if networking_config :
294+ self ._log .debug ("Ignoring networking_config when subnet_name is provided." )
280295 subnet_id = get_subnet_id_by_name (
281296 self .network_client ,
282297 self .compartment_id ,
@@ -287,10 +302,11 @@ def add_network_interface(
287302 self .network_client ,
288303 self .compartment_id ,
289304 self .availability_domain ,
290- private = use_private_subnet ,
305+ networking_config = networking_config ,
306+ vcn_name = self ._vcn_name ,
291307 )
292- create_vnic_details = oci . core . models . CreateVnicDetails ( # noqa: E501
293- subnet_id = subnet_id ,
308+ create_vnic_details = generate_create_vnic_details (
309+ subnet_id = subnet_id , networking_config = networking_config
294310 )
295311 attach_vnic_details = oci .core .models .AttachVnicDetails ( # noqa: E501
296312 create_vnic_details = create_vnic_details ,
@@ -304,13 +320,29 @@ def add_network_interface(
304320 desired_state = vnic_attachment_data .LIFECYCLE_STATE_ATTACHED ,
305321 )
306322 vnic_data = self .network_client .get_vnic (vnic_attachment_data .vnic_id ).data
323+ self ._log .debug (
324+ "Newly attached vnic data:\n %s" ,
325+ vnic_data ,
326+ )
327+ try :
328+ new_ip = vnic_data .private_ip or vnic_data .ipv6_addresses [0 ]
329+ except IndexError :
330+ err_msg = (
331+ "Unexpected error occurred when trying to retrieve local IP address of the "
332+ "newly attached NIC. No private IP or IPv6 address found."
333+ )
334+ self ._log .error (
335+ err_msg + "Full vnic data for debugging purposes:\n %s" ,
336+ vnic_data ,
337+ )
338+ raise PycloudlibError (err_msg )
307339 self ._log .info (
308- "Added network interface with private IP %s to instance %s on nic #%s" ,
309- vnic_data . private_ip ,
340+ "Added network interface with IP %s to instance %s on nic #%s" ,
341+ new_ip ,
310342 self .instance_id ,
311343 nic_index ,
312344 )
313- return vnic_data . private_ip
345+ return new_ip
314346
315347 def remove_network_interface (self , ip_address : str ):
316348 """Remove network interface based on IP address.
@@ -355,14 +387,20 @@ def configure_secondary_vnic(self) -> str:
355387 or if the IP address was not successfully assigned to the interface.
356388 PycloudlibError: If failed to fetch secondary VNIC data from the Oracle Cloud metadata service.
357389 """
358- if not self .secondary_vnic_private_ip :
390+ secondary_ip = self .secondary_vnic_private_ip
391+ if not secondary_ip :
359392 raise ValueError ("Cannot configure secondary VNIC without a secondary VNIC attached" )
393+ if ":" in secondary_ip :
394+ imds_url = "http://[fd00:c1::a9fe:a9fe]/opc/v1/vnics"
395+ else :
396+ imds_url = "http://169.254.169.254/opc/v1/vnics"
397+
360398 secondary_vnic_imds_data : Optional [Dict [str , str ]] = None
361399 # it can take a bit for the secondary VNIC to show up in the IMDS
362400 # so we need to retry fetching the data for roughly a minute
363401 for _ in range (60 ):
364402 # Fetch JSON data from the Oracle Cloud metadata service
365- imds_req = self .execute ("curl -s http://169.254.169.254/opc/v1/vnics " ).stdout
403+ imds_req = self .execute (f "curl -s { imds_url } " ).stdout
366404 vnics_data = json .loads (imds_req )
367405 if len (vnics_data ) > 1 :
368406 self ._log .debug ("Successfully fetched secondary VNIC data from IMDS" )
0 commit comments