From 1501112311ff6628a295b3812fccb3a8609daad0 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 13 Feb 2025 09:56:51 +0100 Subject: [PATCH 1/7] Add list leases support --- libcloud/compute/drivers/openstack.py | 79 +++++++++++++++++ .../fixtures/openstack/_v2_0__auth.json | 14 ++++ .../fixtures/openstack_v1_1/_leases.json | 84 +++++++++++++++++++ libcloud/test/compute/test_openstack.py | 34 ++++++++ 4 files changed, 211 insertions(+) create mode 100644 libcloud/test/compute/fixtures/openstack_v1_1/_leases.json diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index cea1974567..92b359df02 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -114,6 +114,12 @@ class OpenStackVolumeV3Connection(OpenStackBaseConnection): service_region = "RegionOne" +class OpenStackReservationConnection(OpenStackBaseConnection): + service_type = "reservation" + service_name = "blazar" + service_region = "RegionOne" + + class OpenStackNodeDriver(NodeDriver, OpenStackDriverMixin): """ Base OpenStack node driver. Should not be used directly. @@ -2813,6 +2819,15 @@ def encode_data(self, data): return json.dumps(data) +class OpenStack_2_ReservationConnection(OpenStackReservationConnection): + responseCls = OpenStack_1_1_Response + accept_format = "application/json" + default_content_type = "application/json; charset=UTF-8" + + def encode_data(self, data): + return json.dumps(data) + + class OpenStack_2_PortInterfaceState(Type): """ Standard states of OpenStack_2_PortInterfaceState @@ -2873,6 +2888,10 @@ class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): volumev3_connection = None volume_connection = None + # Connection to the Blazar reservation API + reservation_connectionCls = OpenStack_2_ReservationConnection + reservation_connection = None + type = Provider.OPENSTACK features = {"create_node": ["generates_password"]} @@ -2929,6 +2948,16 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.network_connection = self.connection + # We run the init once to get the Blazar API connection + # and put that on the object under self.reservation_connection. + if original_ex_force_base_url or kwargs.get("ex_force_reservation_url"): + kwargs["ex_force_base_url"] = str( + kwargs.pop("ex_force_reservation_url", original_ex_force_base_url) + ) + self.connectionCls = self.reservation_connectionCls + super().__init__(*args, **kwargs) + self.reservation_connection = self.connection + # We run the init once again to get the compute API connection # and that's put under self.connection as normal. self._ex_force_base_url = original_ex_force_base_url @@ -4362,6 +4391,56 @@ def ex_detach_floating_ip_from_node(self, node, ip): ) return resp.status == httplib.OK + def ex_list_leases(self): + """ + List leases + + :rtype: ``list`` of :class:`OpenStack_2_Lease` + """ + return self._to_leases(self.reservation_connection.request("/leases").object) + + def _to_leases(self, obj): + lease_elements = obj["leases"] + return [self._to_lease(lease) for lease in lease_elements] + + def _to_lease(self, obj): + return OpenStack_2_Lease( + id=obj["id"], + name=obj["name"], + start=obj["start_date"], + end=obj["end_date"], + status=obj["status"], + reservations=obj["reservations"], + driver=self.reservation_connection.driver, + ) + + +class OpenStack_2_Lease: + """ + Lease info. + """ + + PENDING = "PENDING" + ACTIVE = "ACTIVE" + TERMINATED = "TERMINATED" + DELETED = "DELETED" + CREATING = "CREATING" + UPDATING = "UPDATING" + DELETING = "DELETING" + ERROR = "ERROR" + + def __init__(self, id, name, start, end, status, reservations, driver): + self.id = id + self.name = name + self.start = start + self.end = end + self.status = status + self.reservations = reservations + self.driver = driver + + def __repr__(self): + return "" % (self.id, self.name, self.status) + class OpenStack_1_1_FloatingIpPool: """ diff --git a/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json b/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json index 70c8ba8b73..3c9f5a7056 100644 --- a/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json +++ b/libcloud/test/compute/fixtures/openstack/_v2_0__auth.json @@ -164,6 +164,20 @@ "name": "cinderv3", "type": "volumev3" }, + { + "endpoints": [ + { + "region": "RegionOne", + "tenantId": "1337", + "publicURL": "https://test_endpoint.com/v1", + "versionInfo": "https://test_endpoint.com/v1/", + "versionList": "https://test_endpoint.com/", + "versionId": "1" + } + ], + "name": "blazar", + "type": "reservation" + }, { "endpoints": [ { diff --git a/libcloud/test/compute/fixtures/openstack_v1_1/_leases.json b/libcloud/test/compute/fixtures/openstack_v1_1/_leases.json new file mode 100644 index 0000000000..2acb5fd9c4 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1_1/_leases.json @@ -0,0 +1,84 @@ +{ + "leases": [ + { + "id": "6ee55c78-ac52-41a6-99af-2d2d73bcc466", + "name": "lease_foo", + "start_date": "2017-12-26T12:00:00.000000", + "end_date": "2017-12-27T12:00:00.000000", + "status":"PENDING", + "degraded": false, + "user_id": "5434f637520d4c17bbf254af034b0320", + "project_id": "aa45f56901ef45ee95e3d211097c0ea3", + "trust_id": "b442a580b9504ababf305bf2b4c49512", + "created_at": "2017-12-27 10:00:00", + "updated_at": null, + "reservations": [ + { + "id": "087bc740-6d2d-410b-9d47-c7b2b55a9d36", + "lease_id": "6ee55c78-ac52-41a6-99af-2d2d73bcc466", + "status": "pending", + "missing_resources": false, + "resources_changed": false, + "resource_id": "5e6c0e6e-f1e6-490b-baaf-50deacbbe371", + "resource_type": "physical:host", + "min": 4, + "max": 6, + "hypervisor_properties": "[\">=\", \"$vcpus\", \"4\"]", + "resource_properties": "", + "before_end": "default", + "created_at": "2017-12-27 10:00:00", + "updated_at": null + }, + { + "id": "ddc45423-f863-4e4e-8e7a-51d27cfec962", + "lease_id": "6ee55c78-ac52-41a6-99af-2d2d73bcc466", + "status": "pending", + "missing_resources": false, + "resources_changed": false, + "resource_id": "0b901727-cca2-43ed-bcc8-c21b0982dcb1", + "resource_type": "virtual:instance", + "amount": 4, + "vcpus": 2, + "memory_mb": 4096, + "disk_gb": 100, + "affinity": false, + "resource_properties": "", + "flavor_id": "ddc45423-f863-4e4e-8e7a-51d27cfec962", + "server_group_id": "33cdfc42-5a04-4fcc-b190-1abebaa056bb", + "aggregate_id": 11, + "created_at": "2017-12-27 10:00:00", + "updated_at": null + } + ], + "events": [ + { + "id": "188a8584-f832-4df9-9a4a-51e6364420ff", + "lease_id": "6ee55c78-ac52-41a6-99af-2d2d73bcc466", + "status": "UNDONE", + "event_type": "start_lease", + "time": "2017-12-26T12:00:00.000000", + "created_at": "2017-12-27 10:00:00", + "updated_at": null + }, + { + "id": "277d6436-dfcb-4eae-ae5e-ac7fa9c2fd56", + "lease_id": "6ee55c78-ac52-41a6-99af-2d2d73bcc466", + "status": "UNDONE", + "event_type": "end_lease", + "time": "2017-12-27T12:00:00.000000", + "created_at": "2017-12-27 10:00:00", + "updated_at": null + }, + { + "id": "f583af71-ca21-4b66-87de-52211d118029", + "lease_id": "6ee55c78-ac52-41a6-99af-2d2d73bcc466", + "status": "UNDONE", + "time": "2017-12-27T11:00:00.000000", + "event_type": "before_end_lease", + "created_at": "2017-12-27 10:00:00", + "updated_at": null + } + ] + } + ] +} \ No newline at end of file diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index 4fcf08c753..230f9b78d6 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -2005,6 +2005,11 @@ def setUp(self): # normally authentication happens lazily, but we force it here self.driver.volumev3_connection._populate_hosts_and_request_paths() + self.driver_klass.reservation_connectionCls.conn_class = OpenStack_2_0_MockHttp + self.driver_klass.reservation_connectionCls.auth_url = "https://auth.api.example.com" + # normally authentication happens lazily, but we force it here + self.driver.reservation_connection._populate_hosts_and_request_paths() + def test__paginated_request_single_page(self): snapshots = self.driver._paginated_request( "/snapshots/detail", "snapshots", self.driver._get_volume_connection() @@ -2547,6 +2552,25 @@ def test_ex_delete_floating_ip(self): ip = OpenStack_1_1_FloatingIpAddress("foo-bar-id", "42.42.42.42", None) self.assertTrue(self.driver.ex_delete_floating_ip(ip)) + def test_ex_attach_floating_ip_to_node(self): + image = NodeImage(id=11, name="Ubuntu 8.10 (intrepid)", driver=self.driver) + size = NodeSize(1, "256 slice", None, None, None, None, driver=self.driver) + node = self.driver.create_node(name="racktest", image=image, size=size) + node.id = 4242 + ip = "42.42.42.42" + port_id = "ce531f90-199f-48c0-816c-13e38010b442" + + self.assertTrue(self.driver.ex_attach_floating_ip_to_node(node, ip, port_id)) + + def test_ex_list_leases(self): + leases = self.driver.ex_list_leases() + self.assertEqual(len(leases), 1) + self.assertEqual(leases[0].id, "6ee55c78-ac52-41a6-99af-2d2d73bcc466") + self.assertEqual(leases[0].name, "lease_foo") + self.assertEqual(leases[0].start, "2017-12-26T12:00:00.000000") + self.assertEqual(leases[0].end, "2017-12-27T12:00:00.000000") + self.assertEqual(leases[0].status, "PENDING") + class OpenStack_1_1_FactoryMethodTests(OpenStack_1_1_Tests): should_list_locations = False @@ -3994,6 +4018,16 @@ def _v2_1337_servers_4242_os_interface(self, method, url, body, headers): httplib.responses[httplib.OK], ) + def _v1_leases(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load("_leases.json") + + return ( + httplib.OK, + body, + self.json_content_headers, + httplib.responses[httplib.OK], + ) # This exists because the nova compute url in devstack has v2 in there but the v1.1 fixtures # work fine. From 7ec3704a17e8f8fba41b8a1b2d51b7a15bc711c7 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 13 Feb 2025 10:17:30 +0100 Subject: [PATCH 2/7] Fix tests --- libcloud/test/common/test_openstack_identity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libcloud/test/common/test_openstack_identity.py b/libcloud/test/common/test_openstack_identity.py index 8f6b55066c..088ea9a02e 100644 --- a/libcloud/test/common/test_openstack_identity.py +++ b/libcloud/test/common/test_openstack_identity.py @@ -833,7 +833,7 @@ def test_parsing_auth_v2(self): catalog = OpenStackServiceCatalog(service_catalog=service_catalog, auth_version="2.0") entries = catalog.get_entries() - self.assertEqual(len(entries), 10) + self.assertEqual(len(entries), 11) entry = [e for e in entries if e.service_name == "cloudServers"][0] self.assertEqual(entry.service_type, "compute") @@ -904,6 +904,7 @@ def test_get_service_types(self): "network", "object-store", "rax:object-cdn", + "reservation", "volumev2", "volumev3", ], @@ -923,6 +924,7 @@ def test_get_service_names(self): self.assertEqual( service_names, [ + "blazar", "cinderv2", "cinderv3", "cloudFiles", From 866dccdc3cac9833bee2fe85ee47691912b81535 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Tue, 25 Feb 2025 12:47:57 +0100 Subject: [PATCH 3/7] Add list hosts --- libcloud/compute/drivers/openstack.py | 41 +++++++++++++++++++++++++ libcloud/test/compute/test_openstack.py | 18 +++++++++++ 2 files changed, 59 insertions(+) diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 92b359df02..5621e1c331 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -4414,6 +4414,47 @@ def _to_lease(self, obj): driver=self.reservation_connection.driver, ) + def ex_list_hosts(self): + """ + List leases + + :rtype: ``list`` of :class:`OpenStack_2_Host` + """ + return self._to_hosts(self.reservation_connection.request("/os-hosts").object) + + def _to_hosts(self, obj): + host_elements = obj["hosts"] + return [self._to_host(host) for host in host_elements] + + def _to_host(self, obj): + return OpenStack_2_Host( + id=obj["id"], + hypervisor_hostname=obj["hypervisor_hostname"], + vcpus=obj["vcpus"], + memory_mb=obj["memory_mb"], + local_gb=obj["local_gb"], + service_name=obj["service_name"], + ) + +class OpenStack_2_Host: + """ + Host info. + """ + + def __init__(self, id, hypervisor_hostname, vcpus, memory_mb, local_gb, service_name): + self.id = id + self.hypervisor_hostname = hypervisor_hostname + self.vcpus = vcpus + self.memory_mb = memory_mb + self.local_gb = local_gb + self.service_name = service_name + + def __repr__(self): + return "" % ( + self.id, + self.hypervisor_hostname, + ) + class OpenStack_2_Lease: """ diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index 230f9b78d6..9c22bd741c 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -2571,6 +2571,13 @@ def test_ex_list_leases(self): self.assertEqual(leases[0].end, "2017-12-27T12:00:00.000000") self.assertEqual(leases[0].status, "PENDING") + def test_ex_list_hosts(self): + hosts = self.driver.ex_list_hosts() + self.assertEqual(len(hosts), 1) + self.assertEqual(hosts[0].id, "1") + self.assertEqual(hosts[0].hypervisor_hostname, "compute-1") + self.assertEqual(hosts[0].vcpus, 4) + class OpenStack_1_1_FactoryMethodTests(OpenStack_1_1_Tests): should_list_locations = False @@ -4029,6 +4036,17 @@ def _v1_leases(self, method, url, body, headers): httplib.responses[httplib.OK], ) + def _v1_os_hosts(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load("_os_hosts.json") + + return ( + httplib.OK, + body, + self.json_content_headers, + httplib.responses[httplib.OK], + ) + # This exists because the nova compute url in devstack has v2 in there but the v1.1 fixtures # work fine. From 7d356d8c2bee2deca37e29d3335c5c5963bad450 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Tue, 25 Feb 2025 13:13:19 +0100 Subject: [PATCH 4/7] Add list hosts --- libcloud/compute/drivers/openstack.py | 1 + .../fixtures/openstack_v1_1/_os_hosts.json | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 libcloud/test/compute/fixtures/openstack_v1_1/_os_hosts.json diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 5621e1c331..070f9b3706 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -4436,6 +4436,7 @@ def _to_host(self, obj): service_name=obj["service_name"], ) + class OpenStack_2_Host: """ Host info. diff --git a/libcloud/test/compute/fixtures/openstack_v1_1/_os_hosts.json b/libcloud/test/compute/fixtures/openstack_v1_1/_os_hosts.json new file mode 100644 index 0000000000..ff773eb143 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1_1/_os_hosts.json @@ -0,0 +1,21 @@ +{ + "hosts": [ + { + "id": "1", + "hypervisor_hostname": "compute-1", + "hypervisor_type": "QEMU", + "hypervisor_version": 2010001, + "vcpus": 4, + "cpu_info": "{'arch': 'x86_64', 'model': 'cpu64-rhel6', 'vendor': 'Intel'}", + "memory_mb": 8192, + "local_gb": 100, + "service_name": "compute-1", + "reservable": true, + "status": null, + "trust_id": "5f67f11215cf4c52906453a181bfcfea", + "created_at": "2017-12-27 10:00:00", + "updated_at": null, + "extra_capability_sample": "foo" + } + ] +} \ No newline at end of file From 43d11604511c05ec77aeb0bd17e3438006944452 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Tue, 25 Feb 2025 13:33:55 +0100 Subject: [PATCH 5/7] Remove test --- libcloud/test/compute/test_openstack.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index 9c22bd741c..13f40b356b 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -2552,16 +2552,6 @@ def test_ex_delete_floating_ip(self): ip = OpenStack_1_1_FloatingIpAddress("foo-bar-id", "42.42.42.42", None) self.assertTrue(self.driver.ex_delete_floating_ip(ip)) - def test_ex_attach_floating_ip_to_node(self): - image = NodeImage(id=11, name="Ubuntu 8.10 (intrepid)", driver=self.driver) - size = NodeSize(1, "256 slice", None, None, None, None, driver=self.driver) - node = self.driver.create_node(name="racktest", image=image, size=size) - node.id = 4242 - ip = "42.42.42.42" - port_id = "ce531f90-199f-48c0-816c-13e38010b442" - - self.assertTrue(self.driver.ex_attach_floating_ip_to_node(node, ip, port_id)) - def test_ex_list_leases(self): leases = self.driver.ex_list_leases() self.assertEqual(len(leases), 1) From 07283928b52ccf07d0f9a4ba0be982ec8bf7594d Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Mon, 24 Mar 2025 14:09:50 +0100 Subject: [PATCH 6/7] Fix style --- libcloud/compute/drivers/openstack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index b05e6c4c01..590dac9245 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -4457,7 +4457,7 @@ def __init__(self, id, hypervisor_hostname, vcpus, memory_mb, local_gb, service_ self.service_name = service_name def __repr__(self): - return "" % ( + return "".format( self.id, self.hypervisor_hostname, ) @@ -4487,7 +4487,7 @@ def __init__(self, id, name, start, end, status, reservations, driver): self.driver = driver def __repr__(self): - return "" % (self.id, self.name, self.status) + return "".format(self.id, self.name, self.status) class OpenStack_1_1_FloatingIpPool: From e7df9ed57b18e8e7422d5a264f9f9fc40011a58b Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Mon, 24 Mar 2025 14:18:56 +0100 Subject: [PATCH 7/7] Fix black --- libcloud/compute/drivers/openstack.py | 4 +++- libcloud/test/compute/test_openstack.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 590dac9245..f18e5ab3ea 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -4487,7 +4487,9 @@ def __init__(self, id, name, start, end, status, reservations, driver): self.driver = driver def __repr__(self): - return "".format(self.id, self.name, self.status) + return "".format( + self.id, self.name, self.status + ) class OpenStack_1_1_FloatingIpPool: diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index ae0afacdbc..0b33b6c5df 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -4047,6 +4047,7 @@ def _v1_os_hosts(self, method, url, body, headers): httplib.responses[httplib.OK], ) + # This exists because the nova compute url in devstack has v2 in there but the v1.1 fixtures # work fine.