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
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1!10.13.3
1!10.14.0
2 changes: 2 additions & 0 deletions pycloudlib/azure/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ def _get_boot_diagnostics(self) -> Optional[str]:
)
# Azure has a 60 secs delay for the boot diagnostics to be active.
time.sleep(BOOT_DIAGNOSTICS_URI_DELAY)
if not diagnostics.serial_console_log_blob_uri:
raise PycloudlibError("No serial console log blob uri has been set.")
response = requests.get(diagnostics.serial_console_log_blob_uri, timeout=10)
except ResourceExistsError:
self._log.warning("Boot diagnostics not enabled, so none is collected.")
Expand Down
16 changes: 16 additions & 0 deletions pycloudlib/oci/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,17 @@ def launch(
self.availability_domain,
vcn_name=self.vcn_name,
)
# Configure vNIC for IPv4, IPv6 or dual stack.
subnet = self.network_client.get_subnet(subnet_id).data
vnic_kwargs = {}

# When no IPv4 CIDR is present, the value is assigned to '<null>' str instead of None.
if "null" not in subnet.cidr_block:
Comment thread
Gisaldjo marked this conversation as resolved.
vnic_kwargs["assign_public_ip"] = True

if subnet.ipv6_cidr_block is not None:
vnic_kwargs["assign_ipv6_ip"] = True

default_metadata = {
"ssh_authorized_keys": self.key_pair.public_key_content,
}
Expand All @@ -327,6 +338,11 @@ def launch(
image_id=image_id,
metadata={**default_metadata, **metadata},
compute_cluster_id=cluster_id,
create_vnic_details=oci.core.models.CreateVnicDetails(
Comment thread
uhryniuk marked this conversation as resolved.
subnet_id=subnet_id,
display_name="primary-vnic",
**vnic_kwargs,
),
**kwargs,
)

Expand Down
33 changes: 23 additions & 10 deletions pycloudlib/oci/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(
self.availability_domain = availability_domain
self._fault_domain = None
self._ip = None
self._ips: List[str] = []

if oci_config is None:
oci_config = oci.config.from_file("~/.oci/config") # noqa: E501
Expand Down Expand Up @@ -93,20 +94,32 @@ def ip(self):
]
# select vnic with is_primary = True
primary_vnic = [vnic for vnic in vnics if vnic.is_primary][0]
Comment thread
uhryniuk marked this conversation as resolved.
# if not public IP, check for ipv6
# None is specifically returned by OCI when ipv6 only vnic
if primary_vnic.public_ip is None:
if primary_vnic.ipv6_addresses:
self._ip = primary_vnic.ipv6_addresses[0]
self._log.info("Using ipv6 address: %s", self._ip)
else:
raise PycloudlibError("No public ipv4 address or ipv6 address found")
else:

# attempt to use the IPv4 address if available
if primary_vnic.public_ip:
self._ip = primary_vnic.public_ip
self._ips.append(primary_vnic.public_ip)
self._log.info("Using ipv4 address: %s", self._ip)
return self._ip

# fallback to ipv6 address if possible
if primary_vnic.ipv6_addresses:
if self._ip is None:
self._ip = primary_vnic.ipv6_addresses[0]
self._log.info("Using ipv6 address: %s", self._ip)
self._ips.extend(primary_vnic.ipv6_addresses)

if self._ip is None:
raise PycloudlibError("No public ipv4 address or ipv6 address found")

return self._ip

@property
def ips(self):
Comment thread
Gisaldjo marked this conversation as resolved.
"""Return IP address of instance."""
if self.ip is None:
return []
return self._ips

@property
def private_ip(self):
"""Return private IP address of instance."""
Expand Down
26 changes: 26 additions & 0 deletions tests/unit_tests/oci/test_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def oci_mock():
mock_network_client = mock.Mock()
mock_compute_client_class.return_value = mock_compute_client
mock_network_client_class.return_value = mock_network_client
mock_network_client.get_subnet.return_value = create_mock_subnet_response()

yield mock_compute_client, mock_network_client, oci_config

Expand Down Expand Up @@ -77,6 +78,26 @@ def oci_cloud(oci_mock, tmp_path):

yield oci_cloud

def create_mock_subnet_response(
cidr_block: str = "10.0.0.0/16",
ipv6_cidr_block: str = "2603:c010:2004:8500::/56",
display_name: str = "test-display-name",
subnet_id: str = "subnet-id",
vcn_id: str = "vcn-id"
):
"""Creates a mock OCI Response containing a Subnet model.

Since both `cidr_block` and `ipv6_cidr_block` are provided, the default
subnet is dual stack.
"""
mock_subnet_data = oci.core.models.Subnet(
cidr_block=cidr_block,
ipv6_cidr_block=ipv6_cidr_block,
display_name=display_name,
id=subnet_id,
vcn_id=vcn_id
)
return oci.response.Response(status=200, headers={}, data=mock_subnet_data, request=None)

OCI_PYCLOUDLIB_CONFIG = """\
[oci]
Expand Down Expand Up @@ -304,6 +325,11 @@ def test_launch_instance(self, mock_wait_till_ready, oci_cloud, oci_mock):
args, _ = oci_cloud.compute_client.launch_instance.call_args
launch_instance_details = args[0]
assert launch_instance_details.subnet_id == "subnet-id"

# confirm vnic options have been provided for ipv4, ipv6 and dual stack subnets
assert launch_instance_details.create_vnic_details.assign_public_ip
assert launch_instance_details.create_vnic_details.assign_ipv6_ip

assert oci_cloud.get_instance.call_count == 3


Expand Down