Skip to content

Commit

Permalink
Update the code so we don't end up in an infinite loop in case API
Browse files Browse the repository at this point in the history
returns invalid response or similar.

Also add a test case for it.
  • Loading branch information
Kami committed Jul 31, 2023
1 parent c906009 commit c49ecd0
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 5 deletions.
18 changes: 15 additions & 3 deletions libcloud/compute/drivers/azure_arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import binascii

from libcloud.utils import iso8601
from libcloud.utils.py3 import basestring, urlparse, parse_qs
from libcloud.utils.py3 import parse_qs, urlparse, basestring
from libcloud.common.types import LibcloudError
from libcloud.compute.base import (
Node,
Expand Down Expand Up @@ -64,6 +64,10 @@
VM_EXTENSION_API_VERSION = "2015-06-15"
VM_SIZE_API_VERSION = "2015-06-15" # this API is deprecated

# If pagination code in the list_nodes() method has still not completed after this mount of
# seconds, we will break early from while True loop to avoid infinite loop under edge conditions.
LIST_NODES_PAGINATION_TIMEOUT = 60


class AzureImage(NodeImage):
"""Represents a Marketplace node image that an Azure VM can boot from."""
Expand Down Expand Up @@ -469,11 +473,19 @@ def list_nodes(self, ex_resource_group=None, ex_fetch_nic=True, ex_fetch_power_s
self.subscription_id
)
params = {"api-version": VM_API_VERSION}

now_ts = int(time.time())
deadline_ts = now_ts + LIST_NODES_PAGINATION_TIMEOUT

nodes = []
while True:
while time.time() < deadline_ts:
r = self.connection.request(action, params=params)
nodes.extend(self._to_node(n, fetch_nic=ex_fetch_nic, fetch_power_state=ex_fetch_power_state) for n in r.object["value"])
nodes.extend(
self._to_node(n, fetch_nic=ex_fetch_nic, fetch_power_state=ex_fetch_power_state)
for n in r.object["value"]
)
if not r.object.get("nextLink"):
# No next page
break
parsed_next_link = urlparse.urlparse(r.object["nextLink"])
params.update({k: v[0] for k, v in parse_qs(parsed_next_link.query).items()})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"expires_in":"3600","token_type":"Bearer","expires_on":"1111111111","not_before":"1111111111","resource":"https://management.core.windows.net/","access_token":"3333333333333333333333333333333333333333333333333333333"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"value": [
{
"properties": {
"vmId": "CCEEBF63-E92B-4A50-9949-6E44BFC61D3F",
"additionalCapabilities": {
"ultraSSDEnabled": "False",
"hibernationEnabled": "False"
},
"hardwareProfile": {
"vmSize": "Standard_A1"
},
"storageProfile": {
"imageReference": {
"publisher": "OpenLogic",
"offer": "CentOS",
"sku": "7.3",
"version": "latest"
},
"osDisk": {
"osType": "Linux",
"name": "test-node-disk-1",
"createOption": "FromImage",
"caching": "ReadWrite",
"managedDisk": {
"storageAccountType": "Standard_LRS",
"id": "/subscriptions/99999999-9999-9999-9999-999999999999/resourceGroups/000000/providers/Microsoft.Compute/disks/test-node-disk-1"
}
},
"dataDisks": []
},
"osProfile": {
"computerName": "test-node-1",
"adminUsername": "user",
"linuxConfiguration": {
"disablePasswordAuthentication": false
},
"secrets": []
},
"networkProfile": {
"networkInterfaces": []
},
"provisioningState": "Running"
},
"type": "Microsoft.Compute/virtualMachines",
"location": "eastus",
"tags": {},
"id": "/subscriptions/99999999-9999-9999-9999-999999999999/resourceGroups/000000/providers/Microsoft.Compute/virtualMachines/test-node-1",
"name": "test-node-1"
}
],
"nextLink": "https://management.azure.com:443/subscriptions/99999999-9999-9999-9999-999999999999/providers/Microsoft.Compute/virtualMachines?api-version=2021-11-01&$skiptoken=1!/Subscriptions/99999999-9999-9999-9999-999999999999/ResourceGroups/000000/VMs/DDFEBF64-E92B-4A50-9949-6E44BFC61D4G"
}
17 changes: 15 additions & 2 deletions libcloud/test/compute/test_azure_arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from unittest import mock

from libcloud.test import MockHttp, LibcloudTestCase, unittest
from libcloud.utils.py3 import httplib, urlparse, parse_qs, urlunquote
from libcloud.utils.py3 import httplib, parse_qs, urlparse, urlunquote
from libcloud.common.types import LibcloudError
from libcloud.compute.base import NodeSize, NodeLocation, StorageVolume, VolumeSnapshot
from libcloud.compute.types import Provider, NodeState, StorageVolumeState, VolumeSnapshotState
Expand All @@ -42,6 +42,7 @@ class AzureNodeDriverTests(LibcloudTestCase):
APPLICATION_PASS = "p4ssw0rd"

def setUp(self):
AzureMockHttp.type = None
Azure = get_driver(Provider.AZURE_ARM)
Azure.connectionCls.conn_class = AzureMockHttp
self.driver = Azure(
Expand Down Expand Up @@ -458,6 +459,18 @@ def test_list_nodes(self, fps_mock):

fps_mock.assert_called()

@mock.patch(
"libcloud.compute.drivers.azure_arm.AzureNodeDriver._fetch_power_state",
return_value=NodeState.UPDATING,
)
@mock.patch("libcloud.compute.drivers.azure_arm.LIST_NODES_PAGINATION_TIMEOUT", 1)
def test_list_nodes_pagination_timeout_reached(self, fps_mock):
# Verify we don't end up in an infinite loop in case server returns a bad response or
# similar
AzureMockHttp.type = "PAGINATION_INFINITE_LOOP"
nodes = self.driver.list_nodes()
self.assertTrue(len(nodes) >= 1)

@mock.patch(
"libcloud.compute.drivers.azure_arm.AzureNodeDriver" "._fetch_power_state",
return_value=NodeState.UPDATING,
Expand Down Expand Up @@ -824,7 +837,7 @@ def fn(method, url, body, headers):
AzureNodeDriverTests.SUBSCRIPTION_ID,
)
unquoted_url = urlunquote(url)
if "$skiptoken=" in unquoted_url:
if "$skiptoken=" in unquoted_url and self.type != "PAGINATION_INFINITE_LOOP":
parsed_url = urlparse.urlparse(unquoted_url)
params = parse_qs(parsed_url.query)
file_name += "_" + params["$skiptoken"][0].split("!")[0]
Expand Down

0 comments on commit c49ecd0

Please sign in to comment.