diff --git a/README.md b/README.md index 67d0c43..f7a1d0a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,70 @@ # esisdk Unified SDK for ESI + +## Use the SDK in python scripts +### Install ESI SDK: +``` +python setup.py install +``` + +### Create a connection to ESI SDK + +There are several methods to establish a connection using the ESI SDK. Since the ***esi.connection.ESIConnection*** class inherits from ***openstack.connection.Connection***, [methods](https://docs.openstack.org/openstacksdk/latest/user/connection.html) applicable for creating connections in the OpenStack SDK can also be used with the ESI SDK. Below are some common ways to create an ESIConnection: + +**Create a connection using only keyword arguments** +``` +from esi import connection + +conn = connection.ESIConnection( + region_name='example-region', + auth={ + 'auth_url': 'https://auth.example.com', + 'username': 'user', + 'password': 'password', + 'project_name': 'project_name', + 'user_domain_name': 'user_domain_name', + 'project_domain_name': 'project_domain_name' + }, + interface='public' +) +``` +**Create a connection from existing CloudRegion** +``` +from esi import connection +import openstack.config + +config = openstack.config.get_cloud_region( + cloud='example', + region_name='earth' +) +conn = connection.ESIConnectionn(config=config) +``` + +### Make API calls +Detailed APIs can be found in the `esi/lease/v1/_proxy.py` file. Below are simple examples demonstrating lease resource CRUD operations. +``` +import esi +import os + +TEST_CLOUD = os.getenv('OS_TEST_CLOUD', 'devstack-admin') +conn = esi.connect(cloud=TEST_CLOUD) + +# Create a lease +def lease_create(conn, resource_uuid, project_id, **kwargs): + lease = conn.lease.create_lease(resource_uuid=resource_uuid, + project_id=project_id, + **kwargs) + +# List leases +def lease_list(conn, **kwargs): + leases = conn.lease.leases(**kwargs) + +# Update a lease +def lease_update(conn, lease, **kwargs): + lease_dict = conn.lease.update_lease(lease, **kwargs) + +# Delete a lease +def lease_delete(conn, lease_id): + leases = conn.lease.delete_lease(lease_id) + +``` \ No newline at end of file diff --git a/esi/__init__.py b/esi/__init__.py index e69de29..de9a792 100644 --- a/esi/__init__.py +++ b/esi/__init__.py @@ -0,0 +1,71 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import argparse +import typing as ty + +import esi.connection + +from openstack._log import enable_logging +import openstack.config + +__all__ = [ + 'connect', + 'enable_logging', +] + + +def connect( + cloud: ty.Optional[str] = None, + app_name: ty.Optional[str] = None, + app_version: ty.Optional[str] = None, + options: ty.Optional[argparse.Namespace] = None, + load_yaml_config: bool = True, + load_envvars: bool = True, + **kwargs, +) -> esi.connection.ESIConnection: + """Create a :class:`~esi.connection.ESIConnection` + + :param string cloud: + The name of the configuration to load from clouds.yaml. Defaults + to 'envvars' which will load configuration settings from environment + variables that start with ``OS_``. + :param argparse.Namespace options: + An argparse Namespace object. Allows direct passing in of + argparse options to be added to the cloud config. Values + of None and '' will be removed. + :param bool load_yaml_config: + Whether or not to load config settings from clouds.yaml files. + Defaults to True. + :param bool load_envvars: + Whether or not to load config settings from environment variables. + Defaults to True. + :param kwargs: + Additional configuration options. + + :returns: esi.connnection.ESIConnection + :raises: keystoneauth1.exceptions.MissingRequiredOptions + on missing required auth parameters + """ + cloud_region = openstack.config.get_cloud_region( + cloud=cloud, + app_name=app_name, + app_version=app_version, + load_yaml_config=load_yaml_config, + load_envvars=load_envvars, + options=options, + **kwargs, + ) + return esi.connection.ESIConnection( + config=cloud_region, + vendor_hook=kwargs.get('vendor_hook'), + ) diff --git a/esi/cloud/_lease.py b/esi/cloud/_lease.py index 26a9887..3e4c1fb 100644 --- a/esi/cloud/_lease.py +++ b/esi/cloud/_lease.py @@ -24,9 +24,9 @@ def list_offers(self, **kwargs): """Return a list of all offers.""" return list(self.lease.offers(**kwargs)) - def create_offer(self, resource_id, node_type, **kwargs): + def create_offer(self, resource_uuid, node_type, **kwargs): """Create an offer""" - return self.lease.create_offer(resource_id=resource_id, + return self.lease.create_offer(resource_uuid=resource_uuid, node_type=node_type, **kwargs) @@ -42,9 +42,9 @@ def list_leases(self, **kwargs): """Return a list of all leases""" return list(self.lease.leases(**kwargs)) - def create_lease(self, resource_id, project_id, **kwargs): + def create_lease(self, resource_uuid, project_id, **kwargs): """Create a lease""" - return self.lease.create_lease(resource_id=resource_id, + return self.lease.create_lease(resource_uuid=resource_uuid, project_id=project_id, **kwargs) diff --git a/esi/lease/v1/_proxy.py b/esi/lease/v1/_proxy.py index 9cde520..972e5b9 100644 --- a/esi/lease/v1/_proxy.py +++ b/esi/lease/v1/_proxy.py @@ -174,6 +174,19 @@ def create_lease(self, **attrs): """ return self._create(_lease.Lease, **attrs) + def update_lease(self, lease, **attrs): + """Update a lease. + + :param lease: The value can be the ID of a lease or a + :class:`~esi_leap.v1.lease.Lease` instance. + :param dict attrs: The attributes to update on the lease. + + :returns: The updated lease + :rtype: :class:`~esi_leap.v1.lease.Lease`. + """ + res = self._get_resource(_lease.Lease, lease) + return res.update(self, **attrs) + def get_lease(self, lease, fields=None): """Get a specific lease. @@ -203,12 +216,12 @@ def delete_lease(self, lease, ignore_missing=True): """ return self._delete(_lease.Lease, lease, ignore_missing=ignore_missing) - def nodes(self): + def nodes(self, **query): """Retrieve a generator of nodes. :returns: A generator of lease instances. """ - return _node.Node.list(self) + return _node.Node.list(self, **query) def events(self, **query): """Retrieve a generator of events. diff --git a/esi/lease/v1/event.py b/esi/lease/v1/event.py index 004a962..9b9f4e7 100644 --- a/esi/lease/v1/event.py +++ b/esi/lease/v1/event.py @@ -28,11 +28,12 @@ class Event(resource.Resource): # client-side query parameter _query_mapping = resource.QueryParameters( - 'ID', + 'last_event_id', 'event_type', - 'event_time', + 'last_event_time', 'resource_type', - 'resource_uuid' + 'resource_uuid', + 'lessee_or_owner_id' ) #: The transaction date and time. @@ -40,10 +41,16 @@ class Event(resource.Resource): #: The value of the resource. Also available in headers. id = resource.Body("id", alternate_id=True) event_type = resource.Body("event_type") + last_event_id = resource.Body("last_event_id") + last_event_time = resource.Body("last_event_time") + event_id = resource.Body("event_id") event_time = resource.Body("event_time") object_type = resource.Body("object_type") object_uuid = resource.Body("object_uuid") node_type = resource.Body("resource_type") resource_uuid = resource.Body("resource_uuid") - lease_id = resource.Body("lease_id") + lessee_or_owner_id = resource.Body("lessee_or_owner_id") + lessee_id = resource.Body("lessee_id") owner_id = resource.Body("owner_id") + + _attr_aliases = {'resource_type': 'node_type'} diff --git a/esi/lease/v1/lease.py b/esi/lease/v1/lease.py index 02c2dae..f417fc2 100644 --- a/esi/lease/v1/lease.py +++ b/esi/lease/v1/lease.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from openstack import exceptions from openstack import resource @@ -23,6 +24,7 @@ class Lease(resource.Resource): allow_commit = True allow_delete = True allow_list = True + allow_patch = True commit_method = 'PATCH' commit_jsonpatch = True @@ -32,14 +34,22 @@ class Lease(resource.Resource): 'resource_type', 'status', 'uuid', + 'project_id', + 'start_time', + 'end_time', + 'owner_id', + 'resource_class', + 'purpose', + 'properties', ) #: The transaction date and time. timestamp = resource.Header("x-timestamp") #: The value of the resource. Also available in headers. - id = resource.Body("uuid", alternate_id=True) + uuid = resource.Body("uuid", alternate_id=True) node_type = resource.Body("resource_type") - resource_id = resource.Body("resource_uuid") + resource_name = resource.Body("resource") + resource_uuid = resource.Body("resource_uuid") resource_class = resource.Body("resource_class") offer_uuid = resource.Body("offer_uuid") owner = resource.Body("owner") @@ -50,9 +60,37 @@ class Lease(resource.Resource): fulfill_time = resource.Body("fulfill_time") expire_time = resource.Body("expire_time") status = resource.Body("status") - name = resource.Body("name") project = resource.Body("project") project_id = resource.Body("project_id") - lease_resource = resource.Body("resource") properties = resource.Body("properties") + resource_properties = resource.Body("resource_properties") purpose = resource.Body("purpose") + + _attr_aliases = {'resource_type': 'node_type', + 'resource': 'resource_name'} + + def update(self, session, **kwargs): + """Update a lease. + + :param session: The session to use for making this request. + :type session: :class:`~keystoneauth1.adapter.Adapter` + + :returns: The result of update. + :rtype: Response json data. + """ + session = self._get_session(session) + + request = self._prepare_request(requires_id=True) + response = session.patch( + request.url, + json=kwargs, + headers=request.headers, + microversion=None, + retriable_status_codes=None, + ) + + msg = ( + "Failed to update lease {lease} ".format(lease=self.id) + ) + exceptions.raise_from_response(response, error_message=msg) + return response.json() diff --git a/esi/lease/v1/node.py b/esi/lease/v1/node.py index a991d56..07ad167 100644 --- a/esi/lease/v1/node.py +++ b/esi/lease/v1/node.py @@ -36,13 +36,14 @@ class Node(resource.Resource): #: The transaction date and time. timestamp = resource.Header("x-timestamp") #: The value of the resource. Also available in headers. - id = resource.Body("uuid", alternate_id=True) - name = resource.Body("name") + uuid = resource.Body("uuid", alternate_id=True) owner = resource.Body("owner") lessee = resource.Body("lessee") provision_state = resource.Body("provision_state") maintenance = resource.Body("maintenance") - offer_id = resource.Body("offer_uuid") - lease_id = resource.Body("lease_uuid") + offer_uuid = resource.Body("offer_uuid") + lease_uuid = resource.Body("lease_uuid") future_offers = resource.Body("future_offers") future_leases = resource.Body("future_leases") + properties = resource.Body("properties") + resource_class = resource.Body("resource_class") diff --git a/esi/lease/v1/offer.py b/esi/lease/v1/offer.py index ede9e3e..985cbae 100644 --- a/esi/lease/v1/offer.py +++ b/esi/lease/v1/offer.py @@ -32,17 +32,26 @@ class Offer(resource.Resource): _query_mapping = resource.QueryParameters( 'resource_uuid', 'resource_type', + 'resource_class', 'status', 'uuid', 'lessee', + 'start_time', + 'end_time', + 'lessee_id', + 'name', + 'properties', + 'project_id', + 'available_start_time', + 'available_end_time', ) #: The transaction date and time. timestamp = resource.Header("x-timestamp") #: The value of the resource. Also available in headers. - id = resource.Body("uuid", alternate_id=True) + uuid = resource.Body("uuid", alternate_id=True) node_type = resource.Body("resource_type") - resource_id = resource.Body("resource_uuid") + resource_uuid = resource.Body("resource_uuid") resource_class = resource.Body("resource_class") lessee = resource.Body("lessee") lessee_id = resource.Body("lessee_id") @@ -50,12 +59,17 @@ class Offer(resource.Resource): start_time = resource.Body("start_time") end_time = resource.Body("end_time") status = resource.Body("status") + available_start_time = resource.Body("available_start_time") + available_end_time = resource.Body("available_end_time") availabilities = resource.Body("availabilities") - name = resource.Body("name") project = resource.Body("project") project_id = resource.Body("project_id") - offer_resource = resource.Body("resource") + resource_name = resource.Body("resource") properties = resource.Body("properties") + resource_properties = resource.Body("resource_properties") + + _attr_aliases = {'resource_type': 'node_type', + 'resource': 'resource_name'} def claim_offer(self, session, **kwargs): """Claim an offer. diff --git a/esi/tests/fakes.py b/esi/tests/fakes.py index dd2b853..b3af79f 100644 --- a/esi/tests/fakes.py +++ b/esi/tests/fakes.py @@ -24,30 +24,30 @@ class FakeOffer: def __init__(self, id, node_id, node_type): self.id = id - self.resource_id = node_id + self.resource_uuid = node_id self.node_type = node_type class FakeLease: def __init__(self, id, node_id, node_type, offer_uuid): self.id = id - self.resource_id = node_id + self.resource_uuid = node_id self.node_type = node_type self.offer_uuid = offer_uuid class FakeNode: - def __init__(self, id, offer_id, lease_id): + def __init__(self, id, offer_uuid, lease_uuid): self.id = id - self.offer_id = offer_id - self.lease_id = lease_id + self.offer_uuid = offer_uuid + self.lease_uuid = lease_uuid class FakeEvent: - def __init__(self, id, event_type, event_time): + def __init__(self, id, event_type, last_event_time): self.id = id self.event_type = event_type - self.event_time = event_time + self.last_event_time = last_event_time def make_fake_offer(id, node_id, node_type): @@ -63,16 +63,16 @@ def make_fake_lease(id, node_id, node_type, offer_uuid): offer_uuid=offer_uuid)) -def make_fake_node(id, offer_id, lease_id): +def make_fake_node(id, offer_uuid, lease_uuid): return meta.obj_to_munch(FakeNode(id=id, - offer_id=offer_id, - lease_id=lease_id)) + offer_uuid=offer_uuid, + lease_uuid=lease_uuid)) -def make_fake_event(id, event_type, event_time): +def make_fake_event(id, event_type, last_event_time): return meta.obj_to_munch(FakeEvent(id=id, event_type=event_type, - event_time=event_time)) + last_event_time=last_event_time)) def get_lease_endpoint(): diff --git a/esi/tests/functional/lease/README.md b/esi/tests/functional/lease/README.md new file mode 100644 index 0000000..9ebfee1 --- /dev/null +++ b/esi/tests/functional/lease/README.md @@ -0,0 +1,25 @@ +### Prerequisites + +These tests are intended to be run against a functioning OpenStack cloud with esi-leap services enabled and running (https://github.com/CCI-MOC/esi-leap). Please set the following environment variables in order to run full tests: +* TestESILEAPLease and TestESILEAPOffer: set `NODE_1_UUID`, `NODE_1_TYPE`, `NODE_2_UUID`, `NODE_2_TYPE` in tox.ini. These nodes should not be associated with any existing leases/offers during testing. +* TestESILEAPEvent: set `LAST_EVENT_ID`, `NODE_1_UUID` and `NODE_1_TYPE` in tox.ini. +* TestESILEAPNode: set `NODE_3_NAME` in tox.ini. This node should be associated with leases/offers. + +The clouds.yaml file should be like this: https://github.com/openstack/openstacksdk/blob/master/doc/source/contributor/clouds.yaml + +### Running the tests + +By default, the functional tests will not run when invoking `tox` with no additional options. To run them, you must specify the 'functional' testenv like this: + +``` +$ tox -e functional +``` + +To run specific tests, +``` +$ tox -e functional -- "test_node_list" +``` +or +``` +$ tox -e functional -- "TestESILEAPOffer" +``` \ No newline at end of file diff --git a/esi/tests/functional/lease/base.py b/esi/tests/functional/lease/base.py index f8caf11..3abeacb 100644 --- a/esi/tests/functional/lease/base.py +++ b/esi/tests/functional/lease/base.py @@ -11,12 +11,18 @@ # under the License. from esi import connection - from openstack.tests.functional import base +#: Defines the OpenStack Client Config (OCC) cloud key in your OCC config +#: file, typically in $HOME/.config/openstack/clouds.yaml. That configuration +#: will determine where the functional tests will be run and what resource +#: defaults will be used to run the functional tests. + + class BaseESILEAPTest(base.BaseFunctionalTest): min_microversion = None + flavor = None def require_service(service_type, min_microversion=None, **kwargs): pass @@ -26,7 +32,7 @@ def setUp(self): self.conn = connection.ESIConnection(config=base.TEST_CLOUD_REGION) def create_offer(self, node_id=None, node_type=None, **kwargs): - offer = self.conn.lease.create_offer(resource_id=node_id, + offer = self.conn.lease.create_offer(resource_uuid=node_id, node_type=node_type, **kwargs) self.addCleanup( @@ -37,7 +43,7 @@ def create_offer(self, node_id=None, node_type=None, **kwargs): return offer def create_lease(self, node_id=None, project_id=None, **kwargs): - lease = self.conn.lease.create_lease(resource_id=node_id, + lease = self.conn.lease.create_lease(resource_uuid=node_id, project_id=project_id, **kwargs) self.addCleanup( @@ -46,3 +52,17 @@ def create_lease(self, node_id=None, project_id=None, **kwargs): ) ) return lease + + def claim_offer(self, offer, **kwargs): + lease = self.conn.lease.claim_offer(offer, **kwargs) + self.addCleanup( + lambda: self.conn.lease.delete_lease( + lease["uuid"], ignore_missing=True + ) + ) + return lease + + def _pick_flavor(self): + # override - + # openstack.tests.functional.base.BaseFunctionalTest._pick_flavor() + return diff --git a/esi/tests/functional/lease/test_esi_leap_event.py b/esi/tests/functional/lease/test_esi_leap_event.py index fae0d47..ac29256 100644 --- a/esi/tests/functional/lease/test_esi_leap_event.py +++ b/esi/tests/functional/lease/test_esi_leap_event.py @@ -11,24 +11,29 @@ # under the License. from esi.tests.functional.lease import base +import os class TestESILEAPEvent(base.BaseESILEAPTest): def setUp(self): super(TestESILEAPEvent, self).setUp() self.project_id = self.conn.session.get_project_id() + self.node_1_uuid = os.getenv('NODE_1_UUID') + self.node_1_type = os.getenv('NODE_1_TYPE') + self.last_event_id = os.getenv('LAST_EVENT_ID') def test_event_list(self): """ Tests functionality "esi event list" using node_uuid or node name. - checks node_uuid or node_name is present in node list or not. + checks node_uuid or node_name is present in event list or not. Test steps: 1) Create a lease for a node - 2) Checks that the output of "event list" contains + 2) Run event list with the last event id + 3) Checks that the output of "event list" contains the node uuid it's tested with. """ - self.create_lease('1719', + self.create_lease(self.node_1_uuid, self.project_id, - node_type='dummy_node') - events = self.conn.lease.events() + node_type=self.node_1_type) + events = self.conn.lease.events(last_event_id=self.last_event_id) self.assertNotEqual(events, []) - self.assertIn('1719', [x['resource_uuid'] for x in events]) + self.assertIn(self.node_1_uuid, [x['resource_uuid'] for x in events]) diff --git a/esi/tests/functional/lease/test_esi_leap_lease.py b/esi/tests/functional/lease/test_esi_leap_lease.py index 9879407..428cd0b 100644 --- a/esi/tests/functional/lease/test_esi_leap_lease.py +++ b/esi/tests/functional/lease/test_esi_leap_lease.py @@ -10,39 +10,44 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from esi.tests.functional.lease import base from openstack import exceptions +import os class TestESILEAPLease(base.BaseESILEAPTest): def setUp(self): super(TestESILEAPLease, self).setUp() self.project_id = self.conn.session.get_project_id() + self.node_1_uuid = os.getenv('NODE_1_UUID') + self.node_1_type = os.getenv('NODE_1_TYPE') + self.node_2_uuid = os.getenv('NODE_2_UUID') + self.node_2_type = os.getenv('NODE_2_TYPE') def test_lease_create_show_delete(self): - time_now = datetime.now() + time_now = datetime.now(timezone.utc) start_time = time_now + timedelta(minutes=5) end_time = start_time + timedelta(minutes=30) - extra_fields = {"node_type": "dummy_node", + extra_fields = {"node_type": self.node_1_type, "start_time": start_time, "end_time": end_time} - lease = self.create_lease('1719', + lease = self.create_lease(self.node_1_uuid, self.project_id, **extra_fields) - self.assertEqual(lease.resource_id, '1719') + self.assertEqual(lease.resource_uuid, self.node_1_uuid) self.assertEqual(lease.project_id, self.project_id) - self.assertEqual(lease.node_type, 'dummy_node') + self.assertEqual(lease.node_type, self.node_1_type) loaded = self.conn.lease.get_lease(lease.id) self.assertEqual(loaded.id, lease.id) - self.assertEqual(loaded.resource_id, '1719') - self.assertEqual(loaded.node_type, 'dummy_node') + self.assertEqual(loaded.resource_uuid, self.node_1_uuid) + self.assertEqual(loaded.node_type, self.node_1_type) self.conn.lease.delete_lease(lease.id, ignore_missing=False) - leases = self.conn.lease.leases(resource_id='1719') + leases = self.conn.lease.leases(resource_uuid=self.node_1_uuid) self.assertNotIn(lease.id, [l.id for l in leases]) def test_lease_show_not_found(self): @@ -53,28 +58,57 @@ def test_lease_show_not_found(self): ) def test_lease_list(self): - time_now = datetime.now() + time_now = datetime.now(timezone.utc) start_time_1 = time_now + timedelta(minutes=5) end_time_1 = start_time_1 + timedelta(minutes=30) start_time_2 = end_time_1 + timedelta(minutes=5) end_time_2 = start_time_2 + timedelta(minutes=30) - lease1 = self.create_lease('1719', + lease1 = self.create_lease(self.node_1_uuid, self.project_id, - **{"node_type": "dummy_node", + **{"node_type": self.node_1_type, "start_time": start_time_1, "end_time": end_time_1}) - lease2 = self.create_lease('1719', + lease2 = self.create_lease(self.node_1_uuid, self.project_id, - **{"node_type": "dummy_node", + **{"node_type": self.node_1_type, "start_time": start_time_2, "end_time": end_time_2}) - lease3 = self.create_lease('1720', + lease3 = self.create_lease(self.node_2_uuid, self.project_id, - node_type='dummy_node') - leases_1719 = self.conn.lease.leases(resource_id='1719') - lease_id_list = [l.id for l in leases_1719] + node_type=self.node_2_type) + leases_node1 = self.conn.lease.leases(resource_uuid=self.node_1_uuid) + lease_id_list = [l.id for l in leases_node1] for lease_id in lease1.id, lease2.id: self.assertIn(lease_id, lease_id_list) - leases_1720 = self.conn.lease.leases(resource_id='1720') - self.assertEqual([l.id for l in leases_1720], [lease3.id]) + leases_node2 = self.conn.lease.leases(resource_uuid=self.node_2_uuid) + self.assertEqual([l.id for l in leases_node2], [lease3.id]) + + def test_lease_update_valid(self): + time_now = datetime.now(timezone.utc) + start_time = time_now + timedelta(minutes=5) + end_time = start_time + timedelta(minutes=30) + end_time_new = (end_time + timedelta(minutes=30)).strftime('%Y-%m-%dT%H:%M:%S') + extra_fields = {"node_type": self.node_1_type, + "start_time": start_time, + "end_time": end_time} + lease = self.create_lease(self.node_1_uuid, + self.project_id, + **extra_fields) + updated_lease = self.conn.lease.update_lease(lease, end_time=end_time_new) + self.assertEqual(updated_lease.get("end_time"), end_time_new) + + def test_lease_update_invalid(self): + time_now = datetime.now(timezone.utc).replace(microsecond=0) + start_time = time_now + timedelta(minutes=5) + end_time = start_time + timedelta(minutes=30) + start_time_new = start_time + timedelta(minutes=10) + extra_fields = {"node_type": self.node_1_type, + "start_time": start_time, + "end_time": end_time} + lease = self.create_lease(self.node_1_uuid, + self.project_id, + **extra_fields) + self.assertRaises(exceptions.HttpException, + self.conn.lease.update_lease, + lease, start_time=start_time_new) diff --git a/esi/tests/functional/lease/test_esi_leap_node.py b/esi/tests/functional/lease/test_esi_leap_node.py index 2bb6f8f..65f7c69 100644 --- a/esi/tests/functional/lease/test_esi_leap_node.py +++ b/esi/tests/functional/lease/test_esi_leap_node.py @@ -23,18 +23,14 @@ def test_node_list(self): """ Tests functionality "esi node list" using node_uuid or node name. checks node_uuid or node_name is present in node list or not. Test steps: - 1) Set either of the environment variables using - export OS_FUNCTIONAL_NODE_UUID=node_uuid or - export OS_FUNCTIONAL_NODE_NAME=node_name + 1) Set the environment variables using + export NODE_3_NAME=node_name 2) Checks that the output of "node list" contains the node uuid or node name it's tested with. """ - node_uuid = os.getenv('OS_FUNCTIONAL_NODE_UUID') - node_name = os.getenv('OS_FUNCTIONAL_NODE_NAME') + node_name = os.getenv('NODE_3_NAME') nodes = self.conn.lease.nodes() self.assertNotEqual(nodes, []) - if node_uuid is not None: - self.assertIn(node_uuid, [x['UUID'] for x in nodes]) if node_name is not None: - self.assertIn(node_name, [x['Name'] for x in nodes]) + self.assertIn(node_name, [x.name for x in nodes]) diff --git a/esi/tests/functional/lease/test_esi_leap_offer.py b/esi/tests/functional/lease/test_esi_leap_offer.py index 667a6b7..8aec0e5 100644 --- a/esi/tests/functional/lease/test_esi_leap_offer.py +++ b/esi/tests/functional/lease/test_esi_leap_offer.py @@ -10,45 +10,51 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from openstack import exceptions from esi.tests.functional.lease import base +import os + class TestESILEAPOffer(base.BaseESILEAPTest): def setUp(self): super(TestESILEAPOffer, self).setUp() self.project_id = self.conn.session.get_project_id() + self.node_1_uuid = os.getenv('NODE_1_UUID') + self.node_1_type = os.getenv('NODE_1_TYPE') + self.node_2_uuid = os.getenv('NODE_2_UUID') + self.node_2_type = os.getenv('NODE_2_TYPE') def test_offer_create_show_delete(self): - offer = self.create_offer('1719', 'dummy_node') + offer = self.create_offer(self.node_1_uuid, self.node_1_type) - self.assertEqual(offer.resource_id, '1719') - self.assertEqual(offer.node_type, 'dummy_node') + self.assertEqual(offer.resource_uuid, self.node_1_uuid) + self.assertEqual(offer.node_type, self.node_1_type) loaded = self.conn.lease.get_offer(offer.id) self.assertEqual(loaded.id, offer.id) - self.assertEqual(loaded.resource_id, '1719') - self.assertEqual(loaded.node_type, 'dummy_node') + self.assertEqual(loaded.resource_uuid, self.node_1_uuid) + self.assertEqual(loaded.node_type, self.node_1_type) self.conn.lease.delete_offer(offer.id, ignore_missing=False) - offers = self.conn.lease.offers(resource_id='1719') + offers = self.conn.lease.offers(resource_uuid=self.node_1_uuid) self.assertNotIn(offer.id, [o.id for o in offers]) def test_offer_create_detail(self): - time_now = datetime.now() + time_now = datetime.now(timezone.utc) start_time = time_now + timedelta(minutes=5) end_time = start_time + timedelta(minutes=30) extra_fields = {"lessee_id": self.project_id, "start_time": start_time, "end_time": end_time} - offer = self.create_offer('1719', 'dummy_node', **extra_fields) + offer = self.create_offer(self.node_1_uuid, self.node_1_type, **extra_fields) loaded = self.conn.lease.get_offer(offer.id) self.assertEqual(loaded.id, offer.id) - self.assertEqual(loaded.resource_id, '1719') - self.assertEqual(loaded.node_type, 'dummy_node') + self.assertEqual(loaded.resource_uuid, self.node_1_uuid) + self.assertEqual(loaded.node_type, self.node_1_type) self.assertEqual(loaded.lessee_id, self.project_id) def test_offer_show_not_found(self): @@ -59,37 +65,37 @@ def test_offer_show_not_found(self): ) def test_offer_list(self): - time_now = datetime.now() + time_now = datetime.now(timezone.utc) start_time_1 = time_now + timedelta(minutes=5) end_time_1 = start_time_1 + timedelta(minutes=30) start_time_2 = end_time_1 + timedelta(minutes=5) end_time_2 = start_time_2 + timedelta(minutes=30) - offer1 = self.create_offer('1719', 'dummy_node', + offer1 = self.create_offer(self.node_1_uuid, self.node_1_type, **{"start_time": start_time_1, "end_time": end_time_1}) - offer2 = self.create_offer('1719', 'dummy_node', + offer2 = self.create_offer(self.node_1_uuid, self.node_1_type, **{"start_time": start_time_2, "end_time": end_time_2}) - offer3 = self.create_offer('1720', 'dummy_node') + offer3 = self.create_offer(self.node_2_uuid, self.node_2_type) - offers_1719 = self.conn.lease.offers(resource_id='1719') - offer_id_list = [o.id for o in offers_1719] + offers_node1 = self.conn.lease.offers(resource_uuid=self.node_1_uuid) + offer_id_list = [o.id for o in offers_node1] self.assertEqual(len(offer_id_list), 2) for offer_id in offer1.id, offer2.id: self.assertIn(offer_id, offer_id_list) - offers_1720 = self.conn.lease.offers(resource_id='1720') - self.assertEqual([o.id for o in offers_1720], [offer3.id]) + offers_node2 = self.conn.lease.offers(resource_uuid=self.node_2_uuid) + self.assertEqual([o.id for o in offers_node2], [offer3.id]) def test_offer_claim(self): - offer = self.create_offer('1719', 'dummy_node') + offer = self.create_offer(self.node_1_uuid, self.node_1_type) fields = {"name": "new_lease"} - lease = self.conn.lease.claim_offer(offer, **fields) + lease = self.claim_offer(offer, **fields) self.assertNotEqual(lease, {}) def test_offer_claim_multiple(self): - offer = self.create_offer('1719', 'dummy_node') - time_now = datetime.now() + offer = self.create_offer(self.node_1_uuid, self.node_1_type) + time_now = datetime.now(timezone.utc) lease1_start_time = time_now + timedelta(minutes=5) lease1_end_time = lease1_start_time + timedelta(minutes=30) lease2_start_time = lease1_end_time + timedelta(minutes=5) @@ -100,13 +106,13 @@ def test_offer_claim_multiple(self): new_lease2 = {"name": "new_lease2", "start_time": lease2_start_time, "end_time": lease2_end_time} - lease1 = self.conn.lease.claim_offer(offer, **new_lease1) + lease1 = self.claim_offer(offer, **new_lease1) self.assertNotEqual(lease1, {}) - lease2 = self.conn.lease.claim_offer(offer, **new_lease2) + lease2 = self.claim_offer(offer, **new_lease2) self.assertNotEqual(lease2, {}) - lease_list = self.conn.lease.leases(resource_id='1719') + lease_list = self.conn.lease.leases(resource_uuid=self.node_1_uuid) uuid_list = [l.id for l in lease_list] self.assertNotEqual(lease_list, []) for lease_id in lease1["uuid"], lease2["uuid"]: diff --git a/esi/tests/unit/base.py b/esi/tests/unit/base.py deleted file mode 100644 index e69de29..0000000 diff --git a/esi/tests/unit/cloud/test_lease.py b/esi/tests/unit/cloud/test_lease.py index e093daa..c546cab 100644 --- a/esi/tests/unit/cloud/test_lease.py +++ b/esi/tests/unit/cloud/test_lease.py @@ -89,9 +89,9 @@ def test_create_offer(self, mock_ged): ), ] ) - offer = self.cloud.create_offer(resource_id="fake_node_id", + offer = self.cloud.create_offer(resource_uuid="fake_node_id", node_type="fake_type") - self.assertEqual(offer.resource_id, self.fake_offer.resource_id) + self.assertEqual(offer.resource_uuid, self.fake_offer.resource_uuid) self.assertEqual(offer.node_type, self.fake_offer.node_type) self.assert_calls() @@ -120,7 +120,7 @@ def test_claim_offer(self, mock_ged): ] ) rep_json = self.cloud.claim_offer(self.fake_offer) - self.assertEqual(rep_json["resource_id"], self.fake_lease.resource_id) + self.assertEqual(rep_json["resource_uuid"], self.fake_lease.resource_uuid) self.assertEqual(rep_json["node_type"], self.fake_lease.node_type) self.assertEqual(rep_json["offer_uuid"], self.fake_offer.id) self.assert_calls() @@ -157,10 +157,10 @@ def test_create_lease(self, mock_ged): ), ] ) - lease = self.cloud.create_lease(resource_id="fake_node_id", + lease = self.cloud.create_lease(resource_uuid="fake_node_id", node_type="fake_type", project_id="fake_project") - self.assertEqual(lease.resource_id, self.fake_lease.resource_id) + self.assertEqual(lease.resource_uuid, self.fake_lease.resource_uuid) self.assertEqual(lease.node_type, self.fake_lease.node_type) self.assert_calls() diff --git a/esi/tests/unit/lease/v1/test_event.py b/esi/tests/unit/lease/v1/test_event.py index de2addc..670e028 100644 --- a/esi/tests/unit/lease/v1/test_event.py +++ b/esi/tests/unit/lease/v1/test_event.py @@ -19,13 +19,17 @@ FAKE = {'id': 'abc_001', 'event_type': 'notification', + 'last_event_time': event_time, + 'last_event_id': '001', 'event_time': event_time, - 'lease_id': 'lease_id', - 'owner_id': 'owner_id', + 'event_id': '001', + 'lessee_or_owner_id': 'lease_or_id', 'object_type': 'offer', 'object_uuid': 'offer_001', 'resource_type': 'baremetal', 'resource_uuid': 'bm_node_001', + 'lessee_id': 'lessee_id', + 'owner_id': 'owner_id', } @@ -46,10 +50,21 @@ def test_instantiate(self): e = event.Event(**FAKE) self.assertEqual(FAKE['id'], e.id) self.assertEqual(FAKE['event_type'], e.event_type) - self.assertEqual(FAKE['event_time'], e.event_time) - self.assertEqual(FAKE['lease_id'], e.lease_id) - self.assertEqual(FAKE['owner_id'], e.owner_id) + self.assertEqual(FAKE['last_event_time'], + e.last_event_time) + self.assertEqual(FAKE['last_event_id'], + e.last_event_id) + self.assertEqual(FAKE['lessee_or_owner_id'], + e.lessee_or_owner_id) + self.assertEqual(FAKE['event_id'], + e.last_event_id) + self.assertEqual(FAKE['event_time'], + e.event_time) + self.assertEqual(FAKE['lessee_or_owner_id'], + e.lessee_or_owner_id) self.assertEqual(FAKE['object_type'], e.object_type) self.assertEqual(FAKE['object_uuid'], e.object_uuid) self.assertEqual(FAKE['resource_type'], e.node_type) self.assertEqual(FAKE['resource_uuid'], e.resource_uuid) + self.assertEqual(FAKE['lessee_id'], e.lessee_id) + self.assertEqual(FAKE['owner_id'], e.owner_id) diff --git a/esi/tests/unit/lease/v1/test_lease.py b/esi/tests/unit/lease/v1/test_lease.py index 4d6c9b7..5f7c94f 100644 --- a/esi/tests/unit/lease/v1/test_lease.py +++ b/esi/tests/unit/lease/v1/test_lease.py @@ -13,7 +13,11 @@ import datetime from esi.lease.v1 import lease +from keystoneauth1 import adapter + +from openstack import exceptions from openstack.tests.unit import base +from unittest import mock start = datetime.datetime(2016, 7, 16, 19, 20, 30) FAKE = {'uuid': 'lease_uuid', @@ -32,9 +36,10 @@ 'name': 'offer_name', 'project': 'project_name', 'project_id': 'project_id', - 'resource': 'resource', 'properties': None, 'purpose': 'test', + 'resource_properties': None, + 'resource_name': 'node-1819' } @@ -48,13 +53,14 @@ def test_basic(self): self.assertTrue(l.allow_fetch) self.assertTrue(l.allow_commit) self.assertTrue(l.allow_delete) + self.assertTrue(l.allow_patch) self.assertTrue(l.allow_list) self.assertEqual('PATCH', l.commit_method) def test_instantiate(self): l = lease.Lease(**FAKE) - self.assertEqual(FAKE['uuid'], l.id) - self.assertEqual(FAKE['resource_uuid'], l.resource_id) + self.assertEqual(FAKE['uuid'], l.uuid) + self.assertEqual(FAKE['resource_uuid'], l.resource_uuid) self.assertEqual(FAKE['resource_type'], l.node_type) self.assertEqual(FAKE['resource_class'], l.resource_class) self.assertEqual(FAKE['parent_lease_uuid'], l.parent_lease_uuid) @@ -66,6 +72,27 @@ def test_instantiate(self): self.assertEqual(FAKE['name'], l.name) self.assertEqual(FAKE['project'], l.project) self.assertEqual(FAKE['project_id'], l.project_id) - self.assertEqual(FAKE['resource'], l.lease_resource) self.assertEqual(FAKE['properties'], l.properties) self.assertEqual(FAKE['purpose'], l.purpose) + self.assertEqual(FAKE['resource_properties'], l.resource_properties) + self.assertEqual(FAKE['resource_name'], l.resource_name) + + +@mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) +class TestLeaseUpdate(object): + def setUp(self): + super(TestLeaseUpdate, self).setUp() + self.lease = lease.Lease(**FAKE) + self.session = lease.Mock( + spec=adapter.Adapter, default_microversion=None + ) + self.session.log = mock.Mock() + + def test_update_lease(self): + self.lease.update(self.session) + self.session.get.assert_called_once_with( + 'lease/%s' % self.lease.id, + headers=mock.ANY, + microversion=None, + retriable_status_codes=None, + ) diff --git a/esi/tests/unit/lease/v1/test_node.py b/esi/tests/unit/lease/v1/test_node.py index c17e9a0..1fdfc6b 100644 --- a/esi/tests/unit/lease/v1/test_node.py +++ b/esi/tests/unit/lease/v1/test_node.py @@ -23,6 +23,8 @@ 'lease_uuid': 'lease_001', 'future_offers': None, 'future_leases': None, + 'resource_class': 'test', + 'properties': None } @@ -46,7 +48,9 @@ def test_instantiate(self): self.assertEqual(FAKE['lessee'], n.lessee) self.assertEqual(FAKE['provision_state'], n.provision_state) self.assertEqual(FAKE['maintenance'], n.maintenance) - self.assertEqual(FAKE['offer_uuid'], n.offer_id) - self.assertEqual(FAKE['lease_uuid'], n.lease_id) + self.assertEqual(FAKE['offer_uuid'], n.offer_uuid) + self.assertEqual(FAKE['lease_uuid'], n.lease_uuid) self.assertEqual(FAKE['future_offers'], n.future_offers) self.assertEqual(FAKE['future_leases'], n.future_leases) + self.assertEqual(FAKE['resource_class'], n.resource_class) + self.assertEqual(FAKE['properties'], n.properties) diff --git a/esi/tests/unit/lease/v1/test_offer.py b/esi/tests/unit/lease/v1/test_offer.py index c8018a4..51dcf4d 100644 --- a/esi/tests/unit/lease/v1/test_offer.py +++ b/esi/tests/unit/lease/v1/test_offer.py @@ -31,12 +31,15 @@ 'start_time': start, 'end_time': start + datetime.timedelta(days=100), 'status': 'available', - 'availabilities': 'Availabilities', + 'available_start_time': start, + 'available_end_time': start + datetime.timedelta(days=101), + 'availabilities': None, 'name': 'offer_name', 'project': 'project_name', 'project_id': 'project_id', - 'resource': 'resource', + 'resource_name': 'resource', 'properties': None, + 'resource_properties': None } @@ -55,8 +58,8 @@ def test_basic(self): def test_instantiate(self): o = offer.Offer(**FAKE) - self.assertEqual(FAKE['uuid'], o.id) - self.assertEqual(FAKE['resource_uuid'], o.resource_id) + self.assertEqual(FAKE['uuid'], o.uuid) + self.assertEqual(FAKE['resource_uuid'], o.resource_uuid) self.assertEqual(FAKE['resource_type'], o.node_type) self.assertEqual(FAKE['resource_class'], o.resource_class) self.assertEqual(FAKE['lessee'], o.lessee) @@ -65,12 +68,18 @@ def test_instantiate(self): self.assertEqual(FAKE['start_time'], o.start_time) self.assertEqual(FAKE['end_time'], o.end_time) self.assertEqual(FAKE['status'], o.status) + self.assertEqual(FAKE['available_start_time'], + o.available_start_time) + self.assertEqual(FAKE['available_end_time'], + o.available_end_time) self.assertEqual(FAKE['availabilities'], o.availabilities) self.assertEqual(FAKE['name'], o.name) self.assertEqual(FAKE['project'], o.project) self.assertEqual(FAKE['project_id'], o.project_id) - self.assertEqual(FAKE['resource'], o.offer_resource) + self.assertEqual(FAKE['resource_name'], o.resource_name) self.assertEqual(FAKE['properties'], o.properties) + self.assertEqual(FAKE['resource_properties'], + o.resource_properties) @mock.patch.object(exceptions, 'raise_from_response', mock.Mock()) diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/setup.cfg b/setup.cfg index e2c9eea..e3a2e27 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,4 @@ python_requires = >=3.6 [files] packages = - esisdk - -# [entry_points] - + esi diff --git a/test-requirements.txt b/test-requirements.txt index db7622a..ea97f63 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,4 +16,4 @@ statsd>=3.3.0 stestr>=1.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT -pylibmc \ No newline at end of file +importlib-metadata<5.0.0; python_version<'3.8' # Apache-2.0 diff --git a/tox.ini b/tox.ini index dcd6538..35ed95c 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,15 @@ setenv = OS_TEST_TIMEOUT=600 OPENSTACKSDK_FUNC_TEST_TIMEOUT_LOAD_BALANCER=600 OPENSTACKSDK_EXAMPLE_CONFIG_KEY=functional - OPENSTACKSDK_FUNC_TEST_TIMEOUT_PROJECT_CLEANUP=1200 + OPENSTACKSDK_FUNC_TEST_TIMEOUT_PROJECT_CLEANUP=120 + # TODO: edit the values here + NODE_1_UUID=d62347eb-2f7a-4887-a13f-c4d4e87bdd06 + NODE_1_TYPE=ironic_node + NODE_2_UUID=697f1cd0-60ec-426e-bdd8-e75e915b2de0 + NODE_2_TYPE=ironic_node + LAST_EVENT_ID=3280 + NODE_3_NAME=oct4-12 + commands = stestr --test-path ./esi/tests/functional/ run --serial {posargs} stestr slowest