From a1ddb0c0c621b6ccb1a64820100233ab8a1f158a Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 8 Mar 2022 22:28:59 +0100 Subject: [PATCH 01/65] Fix context unit test This was caused by the bump of oslo.context to 4.1.0 [1], which includes removal of the deprecated argument tenant from RequestContext [2]. Replace tenant key in the context object by project_id to fix the lower constraints job, which is still using oslo.context 2.22.0. [1] https://review.opendev.org/c/openstack/requirements/+/829599 [2] https://review.opendev.org/c/openstack/oslo.context/+/815938 Change-Id: I281ffa4f6fc29c691da48a792bc203818e334d9d (cherry picked from commit 8af178c38fa2510fc4da726e1b7ac578b4a3070f) --- blazar/tests/test_context.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/blazar/tests/test_context.py b/blazar/tests/test_context.py index ddeda2de5..c7af1cd19 100644 --- a/blazar/tests/test_context.py +++ b/blazar/tests/test_context.py @@ -38,6 +38,14 @@ def test_to_dict(self): ctx = context.BlazarContext( user_id=111, project_id=222, request_id='req-679033b7-1755-4929-bf85-eb3bfaef7e0b') + + # NOTE(priteau): for compatibility with oslo.context<4.0.0 which + # returns a tenant key instead of project_id + ctx_dict = ctx.to_dict() + if 'tenant' in ctx_dict: + ctx_dict['project_id'] = ctx_dict['tenant'] + del ctx_dict['tenant'] + expected = { 'auth_token': None, 'domain': None, @@ -46,6 +54,7 @@ def test_to_dict(self): 'is_admin_project': True, 'project': 222, 'project_domain': None, + 'project_id': 222, 'read_only': False, 'request_id': 'req-679033b7-1755-4929-bf85-eb3bfaef7e0b', 'resource_uuid': None, @@ -53,11 +62,10 @@ def test_to_dict(self): 'service_catalog': [], 'show_deleted': False, 'system_scope': None, - 'tenant': 222, 'user': 111, 'user_domain': None, 'user_identity': '111 222 - - -'} - self.assertEqual(expected, ctx.to_dict()) + self.assertEqual(expected, ctx_dict) def test_service_catalog_default(self): ctxt = context.BlazarContext(user_id=uuidsentinel.user_id, From 936afa448d55439c6594a15e5eab04b39c6d00db Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 8 Mar 2022 12:00:18 +0000 Subject: [PATCH 02/65] Update .gitreview for stable/yoga Change-Id: I103cd8f850488e6a634dd427c41ee1fe7c4f1748 --- .gitreview | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitreview b/.gitreview index 1500a8d6d..9a1259420 100644 --- a/.gitreview +++ b/.gitreview @@ -2,3 +2,4 @@ host=review.opendev.org port=29418 project=openstack/blazar.git +defaultbranch=stable/yoga From 591b4acc0eb81ee498b5da318d0469882e997fb1 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 8 Mar 2022 12:00:19 +0000 Subject: [PATCH 03/65] Update TOX_CONSTRAINTS_FILE for stable/yoga Update the URL to the upper-constraints file to point to the redirect rule on releases.openstack.org so that anyone working on this branch will switch to the correct upper-constraints list automatically when the requirements repository branches. Until the requirements repository has as stable/yoga branch, tests will continue to use the upper-constraints list on master. Change-Id: I78b12f832750c144752f8138e4283b1735367280 --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index eedcad03c..84d2b6c88 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ basepython = python3 usedevelop = True allowlist_externals = rm deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/yoga} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt setenv = VIRTUAL_ENV={envdir} @@ -39,7 +39,7 @@ commands = flake8 {posargs} commands = {posargs} [testenv:docs] -deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} +deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/yoga} -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/html doc/build From e61d4cf3533dd753882b18c65893e93cf5696e0a Mon Sep 17 00:00:00 2001 From: Vu Cong Tuan Date: Wed, 11 Jul 2018 09:15:00 +0700 Subject: [PATCH 04/65] Switch to stestr According to Openstack summit session [1], stestr is maintained project to which all Openstack projects should migrate. Let's switch to stestr as other projects have already moved to it. [1] https://etherpad.openstack.org/p/YVR-python-pti Change-Id: I9d22be39bec67fc17841367a0ef0ac439143af6c (cherry picked from commit aa2a14a037da20571bd03b1dd1fa34566e0b1dca) --- .gitignore | 4 +--- .stestr.conf | 3 +++ .testr.conf | 7 ------- blazar/tests/__init__.py | 2 +- lower-constraints.txt | 2 +- test-requirements.txt | 2 +- tox.ini | 14 +++++++------- 7 files changed, 14 insertions(+), 20 deletions(-) create mode 100644 .stestr.conf delete mode 100644 .testr.conf diff --git a/.gitignore b/.gitignore index 3a64ce30d..72f965149 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ pip-log.txt # Unit test / coverage reports .coverage .tox -nosetests.xml +.stestr/ cover # Translations @@ -35,8 +35,6 @@ cover AUTHORS ChangeLog -.testrepository - # generated policy file etc/blazar/policy.yaml.sample diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 000000000..604900fbf --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=${TEST_PATH:-./blazar/tests} +top_dir=./ diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 4da4d7dba..000000000 --- a/.testr.conf +++ /dev/null @@ -1,7 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ - OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ - OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - ${PYTHON:-python} -m subunit.run discover $DISCOVER_DIRECTORY $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/blazar/tests/__init__.py b/blazar/tests/__init__.py index ad2e15e15..f9cb329f1 100644 --- a/blazar/tests/__init__.py +++ b/blazar/tests/__init__.py @@ -17,10 +17,10 @@ import tempfile import testscenarios -from oslo_config import cfg from oslo_log import log as logging from oslotest import base +from blazar import config as cfg from blazar import context from blazar.db.sqlalchemy import api as db_api from blazar.db.sqlalchemy import facade_wrapper diff --git a/lower-constraints.txt b/lower-constraints.txt index 27634cd8c..cb91e9d8b 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -100,7 +100,7 @@ statsd==3.2.2 stevedore==1.20.0 Tempita==0.5.2 tenacity==4.9.0 -testrepository==0.0.18 +stestr==2.0.0 testresources==2.0.1 testscenarios==0.4 testtools==2.2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 08c90830d..f0696ad5d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,7 @@ hacking>=3.0.1,<3.1.0 # Apache-2.0 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD -testrepository>=0.0.18 # Apache-2.0/BSD +stestr>=2.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT coverage!=4.4,>=4.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 84d2b6c88..6bcc8b15a 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ setenv = VIRTUAL_ENV={envdir} DISCOVER_DIRECTORY=blazar/tests PYTHONHASHSEED=0 commands = - lockutils-wrapper python setup.py testr --slowest --testr-args="{posargs}" + stestr run --slowest {posargs} sitepackages = False @@ -25,12 +25,12 @@ setenv = {[testenv]setenv} PYTHON=coverage run --source blazar --parallel-mode commands = - coverage erase - lockutils-wrapper python setup.py testr --testr-args="{posargs}" - coverage combine - coverage html -d cover - coverage xml -o cover/coverage.xml - coverage report + coverage erase + stestr run {posargs} + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml + coverage report [testenv:pep8] commands = flake8 {posargs} From 51aed25bb6ae3a0c230bbe879d5ef7eb38e5af19 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Thu, 10 Mar 2022 16:47:21 +0100 Subject: [PATCH 05/65] Fix references to start and end dates The lease attributes are called start_date and end_date, not start_time and end_time. Change-Id: I77fb08a3811ed1445e31b3552ca23d5bfc43a07d (cherry picked from commit bd6469d53c692effdb04f67ce625b29f2d862d35) --- blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py | 2 +- doc/source/admin/usage-enforcement.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py b/blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py index 596e8350d..3ce9faf2e 100644 --- a/blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py +++ b/blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py @@ -332,7 +332,7 @@ def test_lease_list(self): self.assertEqual(['1', '2'], db_api.lease_list()) def test_lease_update(self): - """Update both start_time and name and check lease has been updated.""" + """Update both start_date and name and check lease has been updated.""" result = _create_physical_lease() result = db_api.lease_update(result['id'], values={'name': 'lease_renamed'}) diff --git a/doc/source/admin/usage-enforcement.rst b/doc/source/admin/usage-enforcement.rst index 2ae96f086..f1018586f 100644 --- a/doc/source/admin/usage-enforcement.rst +++ b/doc/source/admin/usage-enforcement.rst @@ -39,7 +39,7 @@ as follows: MaxLeaseDurationFilter ---------------------- -This filter simply examines the lease ``start_time`` and ``end_time`` +This filter simply examines the lease ``start_date`` and ``end_date`` attributes and rejects the lease if its duration exceeds a threshold. It supports two configuration options: From 8e0e6c3c71e76c00cb798f0934f13f4985c850a6 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Thu, 25 Aug 2022 16:52:45 +0200 Subject: [PATCH 06/65] Synchronise tox.ini with changes from other branches In other maintained branches, the blazar installation was removed in [1] and then restored in [2] to fix an issue with generated docs. This was not done on stable/yoga, so this change synchronises tox.ini with these changes made on other branches. [1] Ic55dba0038a2209e9fb291a604a5c1da57607e1e [2] I28d06ae31c4cf44d010e0892a094138112b08641 Change-Id: Ibc7c7751dd8769d3eb5c73c3b9a82b383c6d5630 --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 6bcc8b15a..4baf4d9b3 100644 --- a/tox.ini +++ b/tox.ini @@ -33,6 +33,7 @@ commands = coverage report [testenv:pep8] +skip_install = True commands = flake8 {posargs} [testenv:venv] @@ -40,6 +41,7 @@ commands = {posargs} [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/yoga} + -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/html doc/build From 590730a1d79992133874250f09f884a1aca39b5a Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Fri, 20 May 2022 10:35:03 +0200 Subject: [PATCH 07/65] [CI] Move queue setting to project level Per [1]. [1] http://lists.zuul-ci.org/pipermail/zuul-discuss/2022-May/001801.html Change-Id: I34934eefee6c0cd3b999fa028e6697d1c5e8476a (cherry picked from commit b6ec5600fd7f5c6d04d8b60654406def9b3a95b3) --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index cef278d65..69f4889a2 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,4 +1,5 @@ - project: + queue: blazar templates: - check-requirements - openstack-cover-jobs @@ -15,7 +16,6 @@ - openstack-tox-pylint: voting: false gate: - queue: blazar jobs: - blazar-tempest-plugin-base - blazar-tempest-plugin-ipv6-only From 7f024406fac0523c9b91114e68b165d0b82ca9dd Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 30 Jun 2023 09:56:38 +0100 Subject: [PATCH 08/65] WIP: Record placement resources and traits for hosts When we add a host, go to placement and collect the resource class and trait information. We can then use this to better filter hosts via flavors that have advanced requests in them, like PCPUs and Baremetal flavors. Note we can also find out about allocation raitos from placement. Change-Id: I5821fcf868013de0b27accb893b6bc2c611f86d1 --- blazar/db/api.py | 12 +++++++++ blazar/db/sqlalchemy/api.py | 32 ++++++++++++++++++++++++ blazar/db/sqlalchemy/models.py | 36 +++++++++++++++++++++++++++ blazar/manager/exceptions.py | 4 +++ blazar/plugins/oshosts/host_plugin.py | 27 +++++++++++++++++--- 5 files changed, 107 insertions(+), 4 deletions(-) diff --git a/blazar/db/api.py b/blazar/db/api.py index 2f9d3d052..b1748b03d 100644 --- a/blazar/db/api.py +++ b/blazar/db/api.py @@ -378,6 +378,18 @@ def host_update(host_id, values): IMPL.host_update(host_id, values) +# ComputeHostCustomResource + +def host_custom_resource_create(values): + """Create a Host CustomResource from the values.""" + return IMPL.host_custom_resource_create(values) + + +def host_custom_resource_get_all_per_host(host_id): + """Return all custom resources belonging to a specific Compute host.""" + return IMPL.host_custom_resource_get_all_per_host(host_id) + + # ComputeHostExtraCapabilities def host_extra_capability_create(values): diff --git a/blazar/db/sqlalchemy/api.py b/blazar/db/sqlalchemy/api.py index e394179a7..1a67505e8 100644 --- a/blazar/db/sqlalchemy/api.py +++ b/blazar/db/sqlalchemy/api.py @@ -759,6 +759,38 @@ def host_destroy(host_id): session.delete(host) +# ComputeHostCustomResource + +def host_custom_resource_create(values): + values = values.copy() + + custom_resource = models.ComputeHostCustomResource() + custom_resource.update(values) + + session = get_session() + with session.begin(): + try: + custom_resource.save(session=session) + except common_db_exc.DBDuplicateEntry as e: + # raise exception about duplicated columns (e.columns) + raise db_exc.BlazarDBDuplicateEntry( + model=custom_resource.__class__.__name__, + columns=e.columns) + + return None + + +def _host_custom_resource_get_all_per_host(session, host_id): + query = model_query(models.ComputeHostCustomResource, session) + LOG.info(query) + return query.filter_by(computehost_id=host_id) + + +def host_custom_resource_get_all_per_host(host_id): + return _host_custom_resource_get_all_per_host(get_session(), + host_id).all() + + # ComputeHostExtraCapability def _host_resource_property_query(session): diff --git a/blazar/db/sqlalchemy/models.py b/blazar/db/sqlalchemy/models.py index 410dd962d..6ad0a2901 100644 --- a/blazar/db/sqlalchemy/models.py +++ b/blazar/db/sqlalchemy/models.py @@ -206,6 +206,9 @@ class InstanceReservations(mb.BlazarBase): amount = sa.Column(sa.Integer, nullable=False) affinity = sa.Column(sa.Boolean, nullable=False) resource_properties = sa.Column(MediumText(), nullable=True) + resource_inventory = sa.Column(MediumText(), nullable=True) + resource_traits = sa.Column(MediumText(), nullable=True) + source_flavor_id = sa.Column(sa.String(36), nullable=True) flavor_id = sa.Column(sa.String(36), nullable=True) aggregate_id = sa.Column(sa.Integer, nullable=True) server_group_id = sa.Column(sa.String(36), nullable=True) @@ -253,11 +256,44 @@ class ComputeHost(mb.BlazarBase): cascade="all,delete", backref='computehost', lazy='joined') + computehost_resource_inventory = relationship( + 'ComputeHostResourceInventory', cascade="all,delete", + backref='computehost', lazy='joined') + computehost_traits = relationship( + 'ComputeHostTrait', cascade="all,delete", + backref='computehost', lazy='joined') def to_dict(self): return super(ComputeHost, self).to_dict() +class ComputeHostResourceInventory(mb.BlazarBase): + __tablename__ = 'computehost_resource_inventory' + + id = _id_column() + computehost_id = sa.Column(sa.String(36), sa.ForeignKey('computehosts.id')) + resource_class = sa.Column(sa.String(255), nullable=False) + allocation_ratio = sa.Column(sa.Integer, nullable=False) + total = sa.Column(sa.Integer, nullable=False) + reserved = sa.Column(sa.Integer, nullable=False) + max_unit = sa.Column(sa.Integer, nullable=False) + min_unit = sa.Column(sa.Integer, nullable=False) + + def to_dict(self): + return super(ComputeHostResourceInventory, self).to_dict() + + +class ComputeHostTrait(mb.BlazarBase): + __tablename__ = 'computehost_trait' + + id = _id_column() + computehost_id = sa.Column(sa.String(36), sa.ForeignKey('computehosts.id')) + trait = sa.Column(sa.String(255), nullable=False) + + def to_dict(self): + return super(ComputeHostTrait, self).to_dict() + + class ComputeHostExtraCapability(mb.BlazarBase): """Description diff --git a/blazar/manager/exceptions.py b/blazar/manager/exceptions.py index 9883af637..221789f0a 100644 --- a/blazar/manager/exceptions.py +++ b/blazar/manager/exceptions.py @@ -60,6 +60,10 @@ class HostNotFound(exceptions.NotFound): msg_fmt = _("Host '%(host)s' not found!") +class ResourceProviderNotFound(exceptions.NotFound): + msg_fmt = _("Resource provider for host '%(host)s' not found!") + + class InvalidHost(exceptions.NotAuthorized): msg_fmt = _("Invalid values for host %(host)s") diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 7a7ff0e79..f477adfab 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -348,6 +348,7 @@ def create_computehost(self, host_values): raise manager_ex.HostHavingServers(host=host_ref, servers=servers) host_details = inventory.get_host_details(host_ref) + hostname = host_details['hypervisor_hostname'] # NOTE(sbauza): Only last duplicate name for same extra capability # will be stored to_store = set(host_values.keys()) - set(host_details.keys()) @@ -359,8 +360,7 @@ def create_computehost(self, host_values): if any([len(key) > 64 for key in extra_capabilities_keys]): raise manager_ex.ExtraCapabilityTooLong() - self.placement_client.create_reservation_provider( - host_details['hypervisor_hostname']) + self.placement_client.create_reservation_provider(hostname) pool = nova.ReservationPool() pool.add_computehost(self.freepool_name, @@ -378,8 +378,7 @@ def create_computehost(self, host_values): # transactions pool.remove_computehost(self.freepool_name, host_details['service_name']) - self.placement_client.delete_reservation_provider( - host_details['hypervisor_hostname']) + self.placement_client.delete_reservation_provider(hostname) raise e for key in extra_capabilities: values = {'computehost_id': host['id'], @@ -394,6 +393,26 @@ def create_computehost(self, host_values): raise manager_ex.CantAddExtraCapability( keys=cantaddextracapability, host=host['id']) + + # Check for any custom resource classes + rp = self.placement_client.get_resource_provider(hostname) + if rp is None: + raise manager_ex.ResourceProviderNotFound(host=hostname) + inventories = self.placement_client.get_inventory(rp['uuid']) + for rc, inventory in inventories['inventories'].items(): + cr = { + 'computehost_id': host['id'], + 'resource_class': rc, + # TODO what about reserved? + 'units': inventory['max_unit'], + 'pci_alias': None, + } + if rc.startswith('CUSTOM_PCI_'): + alias = rc.split('CUSTOM_PCI_') + alias = alias[1].lower() + cr['pci_alias'] = alias + db_api.host_custom_resource_create(cr) + return self.get_computehost(host['id']) def is_updatable_extra_capability(self, capability, property_name): From 1c5b01be1ea47e9ce2fadd97795b9d9be560e27e Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 29 Jun 2023 16:46:58 +0100 Subject: [PATCH 09/65] WIP: Option to use flavor_id to request reservation Instead of creating custom RAM and CPU ratios, we offer the option to reserve a specific number of a pre-defined flavor. When you specify the flavor, we still allow amount and affinity, but we ignore resource_properties, vcpus, memory_mb and disk_gb, as these are all taken from the flavor instead. TODO: should we have the option to block non-flavor based instance reservation via a configuration option? Change-Id: I9803a7ee21011584263c83fb982d403fa605ae34 --- blazar/plugins/instances/instance_plugin.py | 77 +++++++++++++++++++-- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 97e883641..f51f1c377 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -186,6 +186,8 @@ def query_allocations(self, hosts, lease_id=None, reservation_id=None): def query_available_hosts(self, cpus=None, memory=None, disk=None, resource_properties=None, + resource_inventory=None, + resource_traits=None, start_date=None, end_date=None, excludes_res=None): """Returns a list of available hosts for a reservation. @@ -213,6 +215,26 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, filters += plugins_utils.convert_requirements(resource_properties) hosts = db_api.reservable_host_get_all_by_queries(filters) + # TODO(johngarbutt) can we change to do this via the DB?! + # Remove hosts without the required custom resources + if resource_inventory: + cr_hosts = [] + for req in resource_inventory.split(','): + rc, amount = req.split(':') + for host in hosts: + # TODO(johngarbutt): the join means we should have + # already got this from the previous query? + host_crs = db_api.host_custom_resource_get_all_per_host( + host['id']) + for cr in host_crs: + if cr['resource_class'] == rc: + if cr['units'] >= int(amount): + cr_hosts.append(host) + hosts = cr_hosts + if resource_traits: + # TODO(johngarbutt) filter out traits + pass + free_hosts, reserved_hosts = self.filter_hosts_by_reservation( hosts, start_date - datetime.timedelta(minutes=CONF.cleaning_time), @@ -242,11 +264,28 @@ def pickup_hosts(self, reservation_id, values): req_amount = values['amount'] affinity = bool_from_string(values['affinity'], default=None) + # Look up flavor to get the reservation details + flavor_id = values.get('flavor_id') + if flavor_id: + user_client = nova.NovaClientWrapper() + flavor = user_client.nova.nova.flavor.get(flavor_id) + flavor_details = flavor.to_dict() + values['cpus'] = int(flavor_details['vcpus']) + values['memory_mb'] = int(flavor_details['ram']) + values['disk_gb'] = ( + int(flavor_details['disk']) + + int(flavor_details['OS-FLV-EXT-DATA:ephemeral'])) + # TODO(johngarbutt) make use of extra_specs + # extra_specs = flavor.get_keys() + values['resource_properties'] = {} + query_params = { 'cpus': values['vcpus'], 'memory': values['memory_mb'], 'disk': values['disk_gb'], 'resource_properties': values['resource_properties'], + 'resource_inventory': values['resource_inventory'], + 'resource_traits': values['resource_traits'], 'start_date': values['start_date'], 'end_date': values['end_date'] } @@ -317,7 +356,8 @@ def pickup_hosts(self, reservation_id, values): return {'added': added_host_ids, 'removed': removed_host_ids} - def _create_flavor(self, reservation_id, vcpus, memory, disk, group_id): + def _create_flavor(self, reservation_id, vcpus, memory, disk, group_id, + resources=None): flavor_details = { 'flavorid': reservation_id, 'name': RESERVATION_PREFIX + ":" + reservation_id, @@ -336,11 +376,15 @@ def _create_flavor(self, reservation_id, vcpus, memory, disk, group_id): "affinity_id": group_id, reservation_rc: "1" } + # Set any required custom resource classes + for cr in resources or []: + extra_specs["resources:%s" % cr['name']] = cr['value'] + # TODO(johngarbutt) and required/forbidden traits? reserved_flavor.set_keys(extra_specs) return reserved_flavor - def _create_resources(self, inst_reservation): + def _create_resources(self, inst_reservation, resource_inventory): reservation_id = inst_reservation['reservation_id'] ctx = context.current() @@ -351,11 +395,18 @@ def _create_resources(self, inst_reservation): 'affinity' if inst_reservation['affinity'] else 'anti-affinity' ) + resources = [] + for req in resource_inventory.split(','): + resource_class, amount = req.split(':') + resources.append({'name': resource_class, 'value': amount}) + # TODO(johngarbutt): traits and pci alias!? + reserved_flavor = self._create_flavor(reservation_id, inst_reservation['vcpus'], inst_reservation['memory_mb'], inst_reservation['disk_gb'], - reserved_group.id) + reserved_group.id, + resources=resources) pool = nova.ReservationPool() pool_metadata = { @@ -421,6 +472,13 @@ def update_resources(self, reservation_id): else: try: self.nova.nova.flavors.delete(reservation['id']) + # TODO(johngarbutt): get inventory? + resource_inventory = "" + resources = [] + for req in resource_inventory.split(','): + resource_class, amount = req.split(':') + resources.append({'name': resource_class, 'value': amount}) + # TODO(johngarbutt): traits and pci alias!? self._create_flavor(reservation['id'], reservation['vcpus'], reservation['memory_mb'], @@ -432,9 +490,15 @@ def update_resources(self, reservation_id): raise mgr_exceptions.NovaClientError() def _check_missing_reservation_params(self, values): - marshall_attributes = set(['vcpus', 'memory_mb', 'disk_gb', - 'amount', 'affinity', - 'resource_properties']) + marshall_attributes = set(['amount', 'affinity']) + # TODO(johngarbutt): do we want a config to require + # flavor_id and reject other requests, or an enforcer? + # if flavor_id is present, we ignore the components + # if flavor_id is not present, we require the components + if "flavor_id" not in values.keys(): + marshall_attributes = marshall_attributes.union( + ['vcpus', 'memory_mb', 'disk_gb', 'resource_properties']) + missing_attr = marshall_attributes - set(values.keys()) if missing_attr: raise mgr_exceptions.MissingParameter(param=','.join(missing_attr)) @@ -459,6 +523,7 @@ def reserve_resource(self, reservation_id, values): hosts = self.pickup_hosts(reservation_id, values) + # TODO(johngarbutt): need the flavor resource_inventory stuff here instance_reservation_val = { 'reservation_id': reservation_id, 'vcpus': values['vcpus'], From 1e28e304398ab51b6325d77270640ec01da9c54e Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 15:07:48 +0100 Subject: [PATCH 10/65] HACK: ignore when servers existing on host Change-Id: I5805c4cab75b5d1b7f6077950d07197e7df29fe3 --- blazar/plugins/oshosts/host_plugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index f477adfab..7f6cab132 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -343,11 +343,13 @@ def create_computehost(self, host_values): with trusts.create_ctx_from_trust(trust_id): inventory = nova.NovaInventory() - servers = inventory.get_servers_per_host(host_ref) - if servers: - raise manager_ex.HostHavingServers(host=host_ref, - servers=servers) + # TODO(johngarbutt): hack to work around full hypervisors! + #servers = inventory.get_servers_per_host(host_ref) + #if servers: + # raise manager_ex.HostHavingServers(host=host_ref, + # servers=servers) host_details = inventory.get_host_details(host_ref) + LOG.debug(f"host details: {host_details}") hostname = host_details['hypervisor_hostname'] # NOTE(sbauza): Only last duplicate name for same extra capability # will be stored From d98667ff2ea068f23efc7a50f72d5f86d753556f Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 15:22:34 +0100 Subject: [PATCH 11/65] Hack: remove host with instances Change-Id: Iae5bbc54a5def46356b3295200b5d195e63c27b0 --- blazar/plugins/oshosts/host_plugin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 7f6cab132..9a2e2a551 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -499,11 +499,12 @@ def delete_computehost(self, host_id): ) inventory = nova.NovaInventory() - servers = inventory.get_servers_per_host( - host['hypervisor_hostname']) - if servers: - raise manager_ex.HostHavingServers( - host=host['hypervisor_hostname'], servers=servers) + # TODO(johng): hack to allow remove host! + #servers = inventory.get_servers_per_host( + # host['hypervisor_hostname']) + #if servers: + # raise manager_ex.HostHavingServers( + # host=host['hypervisor_hostname'], servers=servers) try: pool = nova.ReservationPool() From 2524dca12bb43ce31b3dac87046308a8d5a39f14 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 15:28:35 +0100 Subject: [PATCH 12/65] Fix up reference to ComputeHostResourceInventory Change-Id: I4c09c627b06eecb312dec444d316d8e60f80920d --- blazar/db/sqlalchemy/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blazar/db/sqlalchemy/api.py b/blazar/db/sqlalchemy/api.py index 1a67505e8..5b672be97 100644 --- a/blazar/db/sqlalchemy/api.py +++ b/blazar/db/sqlalchemy/api.py @@ -764,7 +764,7 @@ def host_destroy(host_id): def host_custom_resource_create(values): values = values.copy() - custom_resource = models.ComputeHostCustomResource() + custom_resource = models.ComputeHostResourceInventory() custom_resource.update(values) session = get_session() @@ -781,7 +781,7 @@ def host_custom_resource_create(values): def _host_custom_resource_get_all_per_host(session, host_id): - query = model_query(models.ComputeHostCustomResource, session) + query = model_query(models.ComputeHostResourceInventory, session) LOG.info(query) return query.filter_by(computehost_id=host_id) From b20e8660b443ed92decb1a0cf47ab9acdbfb7a9a Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 15:49:05 +0100 Subject: [PATCH 13/65] Attempt adding alembic migration But we seem to be missing the new tables! Change-Id: I620dcf6a1d9b6a3dbdd25da0b4440fbe3d049ed2 --- .../e20cbce0504c_add_resource_inventory.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 blazar/db/migration/alembic_migrations/versions/e20cbce0504c_add_resource_inventory.py diff --git a/blazar/db/migration/alembic_migrations/versions/e20cbce0504c_add_resource_inventory.py b/blazar/db/migration/alembic_migrations/versions/e20cbce0504c_add_resource_inventory.py new file mode 100644 index 000000000..eb1d8e341 --- /dev/null +++ b/blazar/db/migration/alembic_migrations/versions/e20cbce0504c_add_resource_inventory.py @@ -0,0 +1,52 @@ +# Copyright 2023 OpenStack Foundation. +# +# 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. + +"""add resource inventory + +Revision ID: e20cbce0504c +Revises: 02e2f2186d98 +Create Date: 2023-07-06 14:45:39.036229 + +""" + +# revision identifiers, used by Alembic. +revision = 'e20cbce0504c' +down_revision = '02e2f2186d98' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('instance_reservations', sa.Column('resource_inventory', sa.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql'), nullable=True)) + op.add_column('instance_reservations', sa.Column('resource_traits', sa.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql'), nullable=True)) + op.add_column('instance_reservations', sa.Column('source_flavor_id', sa.String(length=36), nullable=True)) + op.alter_column('instance_reservations', 'affinity', + existing_type=mysql.TINYINT(display_width=1), + nullable=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('instance_reservations', 'affinity', + existing_type=mysql.TINYINT(display_width=1), + nullable=True) + op.drop_column('instance_reservations', 'source_flavor_id') + op.drop_column('instance_reservations', 'resource_traits') + op.drop_column('instance_reservations', 'resource_inventory') + # ### end Alembic commands ### \ No newline at end of file From a138dc1a1b86d0efe5392bf5816d4db4232cf9bf Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 16:05:19 +0100 Subject: [PATCH 14/65] Fill in missing resource details Change-Id: I47a99f2253adbc6f7dba6460843d860410995c81 --- blazar/plugins/oshosts/host_plugin.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 9a2e2a551..fc6cda6ae 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -402,12 +402,18 @@ def create_computehost(self, host_values): raise manager_ex.ResourceProviderNotFound(host=hostname) inventories = self.placement_client.get_inventory(rp['uuid']) for rc, inventory in inventories['inventories'].items(): + reserved = int(inventory['reserved']) + # Hack for when ironic nodes are not currently available + if reserved == 1: + reserved = 0 cr = { 'computehost_id': host['id'], 'resource_class': rc, - # TODO what about reserved? - 'units': inventory['max_unit'], - 'pci_alias': None, + 'allocation_ratio': inventory['allocation_ratio'], + 'total': inventory['total'], + 'reserved': reserved, + 'max_unit': inventory['max_unit'], + 'min_unit': inventory['min_unit'], } if rc.startswith('CUSTOM_PCI_'): alias = rc.split('CUSTOM_PCI_') From eb224325e0bfd56413bda97e65344a6e22f7c091 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 16:25:17 +0100 Subject: [PATCH 15/65] Fetch traits for compute node Change-Id: I4a7713e4eea6e0a17f464311519f75f94e603e97 --- blazar/plugins/oshosts/host_plugin.py | 5 +++++ blazar/utils/openstack/placement.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index fc6cda6ae..06b5d3644 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -400,6 +400,7 @@ def create_computehost(self, host_values): rp = self.placement_client.get_resource_provider(hostname) if rp is None: raise manager_ex.ResourceProviderNotFound(host=hostname) + inventories = self.placement_client.get_inventory(rp['uuid']) for rc, inventory in inventories['inventories'].items(): reserved = int(inventory['reserved']) @@ -421,6 +422,10 @@ def create_computehost(self, host_values): cr['pci_alias'] = alias db_api.host_custom_resource_create(cr) + traits = self.placement_client.get_traits(rp['uuid']) + for trait in traits['traits']: + LOG.info(trait) + return self.get_computehost(host['id']) def is_updatable_extra_capability(self, capability, property_name): diff --git a/blazar/utils/openstack/placement.py b/blazar/utils/openstack/placement.py index 1a787e4ae..64c9c0d7e 100644 --- a/blazar/utils/openstack/placement.py +++ b/blazar/utils/openstack/placement.py @@ -314,6 +314,17 @@ def get_inventory(self, rp_uuid): return resp.json() raise exceptions.ResourceProviderNotFound(resource_provider=rp_uuid) + def get_traits(self, rp_uuid): + """Calls the placement API to get resource inventory information. + + :param rp_uuid: UUID of the resource provider to get + """ + url = '/resource_providers/%s/traits' % rp_uuid + resp = self.get(url) + if resp: + return resp.json() + raise exceptions.ResourceProviderNotFound(resource_provider=rp_uuid) + @retrying.retry(stop_max_attempt_number=5, retry_on_exception=lambda e: isinstance( e, exceptions.InventoryConflict)) From 8601aec761b8e43321920324030a770987302ffb Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 16:33:23 +0100 Subject: [PATCH 16/65] Add traits into db when adding a host Change-Id: I99e1353a30f35393a3ff1fc3136c7415dfa491c3 --- blazar/db/api.py | 12 ++++++++- blazar/db/sqlalchemy/api.py | 35 +++++++++++++++++++++++++-- blazar/plugins/oshosts/host_plugin.py | 5 +++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/blazar/db/api.py b/blazar/db/api.py index b1748b03d..c41b262cf 100644 --- a/blazar/db/api.py +++ b/blazar/db/api.py @@ -378,7 +378,7 @@ def host_update(host_id, values): IMPL.host_update(host_id, values) -# ComputeHostCustomResource +# ComputeHostResourceInventory def host_custom_resource_create(values): """Create a Host CustomResource from the values.""" @@ -390,6 +390,16 @@ def host_custom_resource_get_all_per_host(host_id): return IMPL.host_custom_resource_get_all_per_host(host_id) +# ComputeHostTrait + +def host_trait_create(values): + return IMPL.host_trait_create(values) + + +def host_trait_get_all_per_host(host_id): + return IMPL.host_trait_get_all_per_host(host_id) + + # ComputeHostExtraCapabilities def host_extra_capability_create(values): diff --git a/blazar/db/sqlalchemy/api.py b/blazar/db/sqlalchemy/api.py index 5b672be97..e2dd03e03 100644 --- a/blazar/db/sqlalchemy/api.py +++ b/blazar/db/sqlalchemy/api.py @@ -759,7 +759,7 @@ def host_destroy(host_id): session.delete(host) -# ComputeHostCustomResource +# ComputeHostResourceInventory def host_custom_resource_create(values): values = values.copy() @@ -782,7 +782,7 @@ def host_custom_resource_create(values): def _host_custom_resource_get_all_per_host(session, host_id): query = model_query(models.ComputeHostResourceInventory, session) - LOG.info(query) + LOG.debug(query) return query.filter_by(computehost_id=host_id) @@ -791,6 +791,37 @@ def host_custom_resource_get_all_per_host(host_id): host_id).all() +# ComputeHostTrait + +def host_trait_create(values): + values = values.copy() + + custom_resource = models.ComputeHostTrait() + custom_resource.update(values) + + session = get_session() + with session.begin(): + try: + custom_resource.save(session=session) + except common_db_exc.DBDuplicateEntry as e: + # raise exception about duplicated columns (e.columns) + raise db_exc.BlazarDBDuplicateEntry( + model=custom_resource.__class__.__name__, + columns=e.columns) + + return None + + +def _host_trait_get_all_per_host(session, host_id): + query = model_query(models.ComputeHostTrait, session) + LOG.debug(query) + return query.filter_by(computehost_id=host_id) + + +def host_trait_get_all_per_host(host_id): + return _host_trait_get_all_per_host(get_session(), + host_id).all() + # ComputeHostExtraCapability def _host_resource_property_query(session): diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 06b5d3644..acb036dd7 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -424,7 +424,10 @@ def create_computehost(self, host_values): traits = self.placement_client.get_traits(rp['uuid']) for trait in traits['traits']: - LOG.info(trait) + db_api.host_trait_create({ + 'computehost_id': host['id'], + 'trait': trait, + }) return self.get_computehost(host['id']) From 3e1230a283f1178c13698cb19295dc12b4aded5a Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 16:49:24 +0100 Subject: [PATCH 17/65] Add missing tables Change-Id: I03b239fe986fb7272b8c6482f5300eb2471d597a --- .../2ae105568b6d_add_inventory_traits.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 blazar/db/migration/alembic_migrations/versions/2ae105568b6d_add_inventory_traits.py diff --git a/blazar/db/migration/alembic_migrations/versions/2ae105568b6d_add_inventory_traits.py b/blazar/db/migration/alembic_migrations/versions/2ae105568b6d_add_inventory_traits.py new file mode 100644 index 000000000..5bdb29b56 --- /dev/null +++ b/blazar/db/migration/alembic_migrations/versions/2ae105568b6d_add_inventory_traits.py @@ -0,0 +1,64 @@ +# Copyright 2023 OpenStack Foundation. +# +# 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. + +"""test + +Revision ID: 2ae105568b6d +Revises: e20cbce0504c +Create Date: 2023-07-06 15:46:28.036821 + +""" + +# revision identifiers, used by Alembic. +revision = '2ae105568b6d' +down_revision = 'e20cbce0504c' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('computehost_resource_inventory', + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('computehost_id', sa.String(length=36), nullable=True), + sa.Column('resource_class', sa.String(length=255), nullable=False), + sa.Column('allocation_ratio', sa.Integer(), nullable=False), + sa.Column('total', sa.Integer(), nullable=False), + sa.Column('reserved', sa.Integer(), nullable=False), + sa.Column('max_unit', sa.Integer(), nullable=False), + sa.Column('min_unit', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['computehost_id'], ['computehosts.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('computehost_trait', + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('computehost_id', sa.String(length=36), nullable=True), + sa.Column('trait', sa.String(length=255), nullable=False), + sa.ForeignKeyConstraint(['computehost_id'], ['computehosts.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('computehost_trait') + op.drop_table('computehost_resource_inventory') + # ### end Alembic commands ### \ No newline at end of file From cae9021d73d913a51e1308667e3fcb386b60d638 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 16:52:25 +0100 Subject: [PATCH 18/65] Remote dead pci_alias code Change-Id: I226d61741041b777e65e73d02e5b726f7b2b66b0 --- blazar/plugins/oshosts/host_plugin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index acb036dd7..4261bdf89 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -416,10 +416,6 @@ def create_computehost(self, host_values): 'max_unit': inventory['max_unit'], 'min_unit': inventory['min_unit'], } - if rc.startswith('CUSTOM_PCI_'): - alias = rc.split('CUSTOM_PCI_') - alias = alias[1].lower() - cr['pci_alias'] = alias db_api.host_custom_resource_create(cr) traits = self.placement_client.get_traits(rp['uuid']) From 37febfa68a69548831b0fc153e3f9b8f0b919c04 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 16:59:08 +0100 Subject: [PATCH 19/65] Comment out missing keys for now Change-Id: I548229b1b91cfaf8e3f5a3d79548eb09997bab95 --- blazar/plugins/instances/instance_plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index f51f1c377..a9bce5ac8 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -277,15 +277,15 @@ def pickup_hosts(self, reservation_id, values): int(flavor_details['OS-FLV-EXT-DATA:ephemeral'])) # TODO(johngarbutt) make use of extra_specs # extra_specs = flavor.get_keys() - values['resource_properties'] = {} query_params = { 'cpus': values['vcpus'], 'memory': values['memory_mb'], 'disk': values['disk_gb'], 'resource_properties': values['resource_properties'], - 'resource_inventory': values['resource_inventory'], - 'resource_traits': values['resource_traits'], + # TODO... + #'resource_inventory': values['resource_inventory'], + #'resource_traits': values['resource_traits'], 'start_date': values['start_date'], 'end_date': values['end_date'] } From 57f695535e84bd86ef823a2ae8dd5fe440430843 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 17:07:36 +0100 Subject: [PATCH 20/65] hack to get flavor_id Change-Id: I5ab96b7cdb2953c28e3b8058dbea0dd1a598183d --- blazar/plugins/instances/instance_plugin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index a9bce5ac8..fa51de0ee 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -266,6 +266,12 @@ def pickup_hosts(self, reservation_id, values): # Look up flavor to get the reservation details flavor_id = values.get('flavor_id') + + # hack to get flavor from resource_properties!! + if not flavor_id and values['resource_properties'] and "flavor" == values['resource_properties'][0]: + flavor_id = values['resource_properties'][1] + values['resource_properties'] = [] + if flavor_id: user_client = nova.NovaClientWrapper() flavor = user_client.nova.nova.flavor.get(flavor_id) From 1c88acf4e973f9cfe3b0897afdc3a2b23e91eb14 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 17:22:59 +0100 Subject: [PATCH 21/65] Fix up get flavor_id hack Change-Id: I6629d3f88ac769769793d6480e480bde1732fed7 --- blazar/plugins/instances/instance_plugin.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index fa51de0ee..9bc4e6b7f 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -268,9 +268,11 @@ def pickup_hosts(self, reservation_id, values): flavor_id = values.get('flavor_id') # hack to get flavor from resource_properties!! - if not flavor_id and values['resource_properties'] and "flavor" == values['resource_properties'][0]: - flavor_id = values['resource_properties'][1] - values['resource_properties'] = [] + if not flavor_id and values['resource_properties'] and "flavor" in values['resource_properties']: + from oslo_serialization import jsonutils + requirements = jsonutils.loads(values['resource_properties']) + flavor_id = requirements[1] + values['resource_properties'] = "" if flavor_id: user_client = nova.NovaClientWrapper() From b4b5cb413f82481cbcd9b067370045f145bb0ae7 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 17:27:45 +0100 Subject: [PATCH 22/65] Fix client name to flavors Change-Id: I5a4561be315e23ed3710ae21584276874604a908 --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 9bc4e6b7f..6287efe4f 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -276,7 +276,7 @@ def pickup_hosts(self, reservation_id, values): if flavor_id: user_client = nova.NovaClientWrapper() - flavor = user_client.nova.nova.flavor.get(flavor_id) + flavor = user_client.nova.nova.flavors.get(flavor_id) flavor_details = flavor.to_dict() values['cpus'] = int(flavor_details['vcpus']) values['memory_mb'] = int(flavor_details['ram']) From 5efbdda9ce18ed9463d5bab3bd41df6704a2a01f Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 17:31:57 +0100 Subject: [PATCH 23/65] HAck to get extra specs Change-Id: If95168bb5b13af017f9f1e64a84cf84afcefac77 --- blazar/plugins/instances/instance_plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 6287efe4f..c5e1ea008 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -284,7 +284,10 @@ def pickup_hosts(self, reservation_id, values): int(flavor_details['disk']) + int(flavor_details['OS-FLV-EXT-DATA:ephemeral'])) # TODO(johngarbutt) make use of extra_specs - # extra_specs = flavor.get_keys() + LOG.error(flavor_details) + extra_specs = flavor.get_keys() + LOG.error(extra_specs) + query_params = { 'cpus': values['vcpus'], From ba57e8d5ecf72d2b8b5a7c9233903ae9cc5b6ad1 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 17:41:23 +0100 Subject: [PATCH 24/65] Start to use extra specs Change-Id: I541b5f37488d9e7112f8723fa61089af649c4875 --- blazar/plugins/instances/instance_plugin.py | 26 +++++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index c5e1ea008..5efcad1de 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -278,16 +278,32 @@ def pickup_hosts(self, reservation_id, values): user_client = nova.NovaClientWrapper() flavor = user_client.nova.nova.flavors.get(flavor_id) flavor_details = flavor.to_dict() - values['cpus'] = int(flavor_details['vcpus']) + values['vcpus'] = int(flavor_details['vcpus']) values['memory_mb'] = int(flavor_details['ram']) values['disk_gb'] = ( int(flavor_details['disk']) + int(flavor_details['OS-FLV-EXT-DATA:ephemeral'])) - # TODO(johngarbutt) make use of extra_specs - LOG.error(flavor_details) - extra_specs = flavor.get_keys() - LOG.error(extra_specs) + # TODO(johngarbutt): use newer api to get this above + extra_specs = flavor.get_keys() + LOG.debug(extra_specs) + + # check for pcpus + resource_inventory = {} + hw_cpu_policy = extra_specs.get("hw:cpu_policy") + if hw_cpu_policy == "dedicated": + values['vcpus'] = 0 + # TODO request PCPUs! + resource_inventory["PCPU"] = flavor_details['vcpus'] + LOG.debug(resource_inventory) + + required_traits = [] + for key, value in extra_specs.items(): + if key.startswith("trait:"): + trait = key.split(":")[1] + if value == "required": + required_traits += trait + LOG.debug(required_traits) query_params = { 'cpus': values['vcpus'], From bbce89fa77ee7fe954327c4cea73e60da9eba401 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 17:46:13 +0100 Subject: [PATCH 25/65] Add missing _create_resources param Change-Id: If5e845822b68dc100548410e5b7a94b722df22e0 --- blazar/plugins/instances/instance_plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 5efcad1de..7e089ea23 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -568,7 +568,8 @@ def reserve_resource(self, reservation_id, values): 'reservation_id': reservation_id}) try: - flavor, group, pool = self._create_resources(instance_reservation) + # TODO... get resource inventory! + flavor, group, pool = self._create_resources(instance_reservation, resource_inventory="") except nova_exceptions.ClientException: LOG.exception("Failed to create Nova resources " "for reservation %s", reservation_id) From a250bdd98e5d79e789c0419b508aa0221a65cf7b Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 17:48:50 +0100 Subject: [PATCH 26/65] HACK: stop creating server group! Change-Id: I7001569fa2cd6345522bebb29d845933c948ca65 --- blazar/plugins/instances/instance_plugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 7e089ea23..3e46c10fe 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -417,10 +417,12 @@ def _create_resources(self, inst_reservation, resource_inventory): ctx = context.current() user_client = nova.NovaClientWrapper() - reserved_group = user_client.nova.server_groups.create( - RESERVATION_PREFIX + ':' + reservation_id, - 'affinity' if inst_reservation['affinity'] else 'anti-affinity' - ) + #reserved_group = user_client.nova.server_groups.create( + # RESERVATION_PREFIX + ':' + reservation_id, + # 'affinity' if inst_reservation['affinity'] else 'anti-affinity' + # ) + # TODO this should be optional!! + reserved_group = "asdf134" resources = [] for req in resource_inventory.split(','): From 5fa95e38dccc6d4147dd39347c50a6444e5df650 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Thu, 6 Jul 2023 17:51:08 +0100 Subject: [PATCH 27/65] Hack up resource_inventory to parse empty string Change-Id: I2e65b6c9ded7a29ed871e368990d785912c476d7 --- blazar/plugins/instances/instance_plugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 3e46c10fe..96e01ed92 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -425,9 +425,10 @@ def _create_resources(self, inst_reservation, resource_inventory): reserved_group = "asdf134" resources = [] - for req in resource_inventory.split(','): - resource_class, amount = req.split(':') - resources.append({'name': resource_class, 'value': amount}) + if resource_inventory: + for req in resource_inventory.split(','): + resource_class, amount = req.split(':') + resources.append({'name': resource_class, 'value': amount}) # TODO(johngarbutt): traits and pci alias!? reserved_flavor = self._create_flavor(reservation_id, From ddeb67ae1a25d20aad36115d4a9055f28811ccc5 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 7 Jul 2023 09:26:12 +0100 Subject: [PATCH 28/65] Fix server_group workaround Change-Id: I6944eba6171ea67e082733afb47197b0508398e4 --- blazar/plugins/instances/instance_plugin.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 96e01ed92..b73021b11 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -415,14 +415,13 @@ def _create_resources(self, inst_reservation, resource_inventory): reservation_id = inst_reservation['reservation_id'] ctx = context.current() - user_client = nova.NovaClientWrapper() - + #user_client = nova.NovaClientWrapper() #reserved_group = user_client.nova.server_groups.create( # RESERVATION_PREFIX + ':' + reservation_id, # 'affinity' if inst_reservation['affinity'] else 'anti-affinity' # ) # TODO this should be optional!! - reserved_group = "asdf134" + reserved_group_id = None resources = [] if resource_inventory: @@ -435,20 +434,20 @@ def _create_resources(self, inst_reservation, resource_inventory): inst_reservation['vcpus'], inst_reservation['memory_mb'], inst_reservation['disk_gb'], - reserved_group.id, + reserved_group_id, resources=resources) pool = nova.ReservationPool() pool_metadata = { RESERVATION_PREFIX: reservation_id, 'filter_tenant_id': ctx.project_id, - 'affinity_id': reserved_group.id + 'affinity_id': reserved_group_id } agg = pool.create(name=reservation_id, metadata=pool_metadata) self.placement_client.create_reservation_class(reservation_id) - return reserved_flavor, reserved_group, agg + return reserved_flavor, reserved_group_id, agg def cleanup_resources(self, instance_reservation): def check_and_delete_resource(client, id): @@ -572,7 +571,7 @@ def reserve_resource(self, reservation_id, values): try: # TODO... get resource inventory! - flavor, group, pool = self._create_resources(instance_reservation, resource_inventory="") + flavor, group_id, pool = self._create_resources(instance_reservation, resource_inventory="") except nova_exceptions.ClientException: LOG.exception("Failed to create Nova resources " "for reservation %s", reservation_id) @@ -581,7 +580,7 @@ def reserve_resource(self, reservation_id, values): db_api.instance_reservation_update(instance_reservation['id'], {'flavor_id': flavor.id, - 'server_group_id': group.id, + 'server_group_id': group_id, 'aggregate_id': pool.id}) return instance_reservation['id'] From 581693b3dcc4ffcd180daaf75f9d05982df182c3 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 7 Jul 2023 10:13:56 +0100 Subject: [PATCH 29/65] Hack to let flavor get created Change-Id: I7338d11ed416d038b8ee44314ef78239cc764aad --- blazar/plugins/instances/instance_plugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index b73021b11..472d61dad 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -428,8 +428,12 @@ def _create_resources(self, inst_reservation, resource_inventory): for req in resource_inventory.split(','): resource_class, amount = req.split(':') resources.append({'name': resource_class, 'value': amount}) + # TODO(johngarbutt): traits and pci alias!? + # TODO get PCPUs and more! + if not inst_reservation['vcpus']: + inst_reservation['vcpus'] = 1 reserved_flavor = self._create_flavor(reservation_id, inst_reservation['vcpus'], inst_reservation['memory_mb'], From 9fa7a156a50603d9621b837cad1a613ff89cccdb Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 7 Jul 2023 10:16:51 +0100 Subject: [PATCH 30/65] Make affinity optional Change-Id: I998cbe9b2ab297407efa87151f1cb3694184c393 --- blazar/plugins/instances/instance_plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 472d61dad..0bfd0c653 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -400,13 +400,15 @@ def _create_flavor(self, reservation_id, vcpus, memory, disk, group_id, reservation_rc = "resources:CUSTOM_RESERVATION_" + rsv_id_rc_format extra_specs = { FLAVOR_EXTRA_SPEC: reservation_id, - "affinity_id": group_id, reservation_rc: "1" } + if group_id: + extra_specs["affinity_id"] = group_id # Set any required custom resource classes for cr in resources or []: extra_specs["resources:%s" % cr['name']] = cr['value'] # TODO(johngarbutt) and required/forbidden traits? + LOG.debug(extra_specs) reserved_flavor.set_keys(extra_specs) return reserved_flavor From 84794d61d9237f3234cdec03b9bbd6e92d56355f Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 14:14:39 +0100 Subject: [PATCH 31/65] Attempt to populate values from the flavor Change-Id: I7eeccb8dcfe0ce6b21d28734dea51c3b26665894 --- .../e20cbce0504c_add_resource_inventory.py | 6 +- blazar/db/sqlalchemy/models.py | 4 +- blazar/plugins/instances/instance_plugin.py | 76 +++++++++++++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/blazar/db/migration/alembic_migrations/versions/e20cbce0504c_add_resource_inventory.py b/blazar/db/migration/alembic_migrations/versions/e20cbce0504c_add_resource_inventory.py index eb1d8e341..5ba0f246d 100644 --- a/blazar/db/migration/alembic_migrations/versions/e20cbce0504c_add_resource_inventory.py +++ b/blazar/db/migration/alembic_migrations/versions/e20cbce0504c_add_resource_inventory.py @@ -34,10 +34,10 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.add_column('instance_reservations', sa.Column('resource_inventory', sa.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql'), nullable=True)) op.add_column('instance_reservations', sa.Column('resource_traits', sa.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql'), nullable=True)) - op.add_column('instance_reservations', sa.Column('source_flavor_id', sa.String(length=36), nullable=True)) + op.add_column('instance_reservations', sa.Column('source_flavor', sa.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql'), nullable=True)) op.alter_column('instance_reservations', 'affinity', existing_type=mysql.TINYINT(display_width=1), - nullable=False) + nullable=True) # ### end Alembic commands ### @@ -45,7 +45,7 @@ def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.alter_column('instance_reservations', 'affinity', existing_type=mysql.TINYINT(display_width=1), - nullable=True) + nullable=False) op.drop_column('instance_reservations', 'source_flavor_id') op.drop_column('instance_reservations', 'resource_traits') op.drop_column('instance_reservations', 'resource_inventory') diff --git a/blazar/db/sqlalchemy/models.py b/blazar/db/sqlalchemy/models.py index 6ad0a2901..5d695a2d3 100644 --- a/blazar/db/sqlalchemy/models.py +++ b/blazar/db/sqlalchemy/models.py @@ -204,11 +204,11 @@ class InstanceReservations(mb.BlazarBase): memory_mb = sa.Column(sa.Integer, nullable=False) disk_gb = sa.Column(sa.Integer, nullable=False) amount = sa.Column(sa.Integer, nullable=False) - affinity = sa.Column(sa.Boolean, nullable=False) + affinity = sa.Column(sa.Boolean, nullable=True) resource_properties = sa.Column(MediumText(), nullable=True) resource_inventory = sa.Column(MediumText(), nullable=True) resource_traits = sa.Column(MediumText(), nullable=True) - source_flavor_id = sa.Column(sa.String(36), nullable=True) + source_flavor = sa.Column(MediumText(), nullable=True) flavor_id = sa.Column(sa.String(36), nullable=True) aggregate_id = sa.Column(sa.Integer, nullable=True) server_group_id = sa.Column(sa.String(36), nullable=True) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 0bfd0c653..94792979d 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -14,6 +14,7 @@ import collections import datetime +import json import retrying from novaclient import exceptions as nova_exceptions @@ -552,10 +553,85 @@ def _validate_reservation_params(self, values): raise mgr_exceptions.MalformedParameter( param='affinity (must be a bool value or None)') + def _populate_values_with_flavor_info(values): + # do not mutate the user input in place + values = values.copy() + + # Look up flavor to get the reservation details + flavor_id = values.get('flavor_id') + + # TODO(johngarbutt) hack to get flavor in via horizon!! + if not flavor_id and values['resource_properties'] and "flavor" in values['resource_properties']: + from oslo_serialization import jsonutils + requirements = jsonutils.loads(values['resource_properties']) + flavor_id = requirements[1] + values['resource_properties'] = "" + + resource_inventory = {} + resource_traits = {} + source_flavor = {} + + if not flavor_id: + # create resource requests from legacy values, if present + resource_inventory["VCPU"] = values.get('vcpus', 0) + resource_inventory["MEMORY_MB"] = values.get('memory_mb', 0) + resource_inventory["DISK_GB"] = values.get('disk_gb', 0) + + else: + user_client = nova.NovaClientWrapper() + flavor = user_client.nova.nova.flavors.get(flavor_id) + source_flavor = flavor.to_dict() + # TODO(johngarbutt): use newer api to get this above + source_flavor["extra_specs"] = flavor.get_keys() + + # Populate the legacy instance reservation fields + # And override what the user specified, if anything + values['vcpus'] = int(source_flavor['vcpus']) + values['memory_mb'] = int(source_flavor['ram']) + values['disk_gb'] = ( + int(source_flavor['disk']) + + int(source_flavor['OS-FLV-EXT-DATA:ephemeral'])) + + # add default resource requests + resource_inventory["VCPU"] = values['vcpus'] + resource_inventory["MEMORY_MB"] = values['memory_mb'] + resource_inventory["DISK_GB"] = values['disk_gb'] + + # Check for PCPUs + hw_cpu_policy = source_flavor['extra_specs'].get("hw:cpu_policy") + if hw_cpu_policy == "dedicated": + resource_inventory["PCPU"] = source_flavor['vcpus'] + resource_inventory["VCPU"] = 0 + values["resource_inventory"] = json.dumps(resource_inventory) + + # Check for traits and extra resources + for key, value in source_flavor['extra_specs'].items(): + if key.startswith("trait:"): + trait = key.split(":")[1] + if value == "required": + resource_traits[trait] = "required" + elif value == "forbidden": + resource_traits[trait] = "forbidden" + + if key.startswith("resource:"): + rc = key.split(":")[1] + values[rc] = int(key) + + values["resource_inventory"] = json.dumps(resource_inventory) + values["resource_traits"] = json.dumps(resource_traits) + values["source_flavor"] = json.dumps(source_flavor) + + LOG.debug(values) + return values + def reserve_resource(self, reservation_id, values): self._check_missing_reservation_params(values) self._validate_reservation_params(values) + # when user specifies a flavor, + # populate values from the flavor + values = self._populate_values_with_flavor_info(values) + hosts = self.pickup_hosts(reservation_id, values) # TODO(johngarbutt): need the flavor resource_inventory stuff here From 57143e04c1a7651bd9d87cd08d4f6121cd86499e Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 15:01:17 +0100 Subject: [PATCH 32/65] Fix _populate_values_with_flavor_info signature Change-Id: I2740ad0127228cc8623f16a1a20c2f0a5db02bd3 --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 94792979d..a9dbbd8b1 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -553,7 +553,7 @@ def _validate_reservation_params(self, values): raise mgr_exceptions.MalformedParameter( param='affinity (must be a bool value or None)') - def _populate_values_with_flavor_info(values): + def _populate_values_with_flavor_info(self, values): # do not mutate the user input in place values = values.copy() From 5fdb916492567a1a3ae8fce81ac3041f6062b081 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 17:31:50 +0100 Subject: [PATCH 33/65] Try to filter hosts using new resources Change-Id: I367a3e34c4df0f4376b154cd7e4f788a0660f446 --- blazar/plugins/instances/instance_plugin.py | 90 ++++++++------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index a9dbbd8b1..f80b49059 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -216,24 +216,37 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, filters += plugins_utils.convert_requirements(resource_properties) hosts = db_api.reservable_host_get_all_by_queries(filters) - # TODO(johngarbutt) can we change to do this via the DB?! + + LOG.debug(f"Found some hosts: {hosts}") + # Remove hosts without the required custom resources + resource_inventory = resource_inventory.copy() + # TODO(johngarbutt) can we remove vcpus,disk,etc as a special case? + del resource_inventory["VCPU"] + del resource_inventory["MEMORY_MB"] + del resource_inventory["DISK_GB"] if resource_inventory: cr_hosts = [] - for req in resource_inventory.split(','): - rc, amount = req.split(':') - for host in hosts: - # TODO(johngarbutt): the join means we should have - # already got this from the previous query? - host_crs = db_api.host_custom_resource_get_all_per_host( + for host in hosts: + host_crs = db_api.host_custom_resource_get_all_per_host( host['id']) - for cr in host_crs: - if cr['resource_class'] == rc: - if cr['units'] >= int(amount): - cr_hosts.append(host) + host_inventory = {cr['resource_class']: cr for cr in host_crs} + host_is_ok = False + for rc, request in resource_inventory.items(): + host_inventory = host_inventory[rc] + host_max = host_inventory['max_unit'] + if request <= host_max: + host_is_ok = True + else: + host_is_ok = False + LOG.debug(f"Filter out becase of {rc} for {host}") + break + if host_is_ok: + cr_hosts.append(host) hosts = cr_hosts + if resource_traits: - # TODO(johngarbutt) filter out traits + # TODO(johngarbutt): filter resource traits! pass free_hosts, reserved_hosts = self.filter_hosts_by_reservation( @@ -265,55 +278,16 @@ def pickup_hosts(self, reservation_id, values): req_amount = values['amount'] affinity = bool_from_string(values['affinity'], default=None) - # Look up flavor to get the reservation details - flavor_id = values.get('flavor_id') - - # hack to get flavor from resource_properties!! - if not flavor_id and values['resource_properties'] and "flavor" in values['resource_properties']: - from oslo_serialization import jsonutils - requirements = jsonutils.loads(values['resource_properties']) - flavor_id = requirements[1] - values['resource_properties'] = "" - - if flavor_id: - user_client = nova.NovaClientWrapper() - flavor = user_client.nova.nova.flavors.get(flavor_id) - flavor_details = flavor.to_dict() - values['vcpus'] = int(flavor_details['vcpus']) - values['memory_mb'] = int(flavor_details['ram']) - values['disk_gb'] = ( - int(flavor_details['disk']) + - int(flavor_details['OS-FLV-EXT-DATA:ephemeral'])) - - # TODO(johngarbutt): use newer api to get this above - extra_specs = flavor.get_keys() - LOG.debug(extra_specs) - - # check for pcpus - resource_inventory = {} - hw_cpu_policy = extra_specs.get("hw:cpu_policy") - if hw_cpu_policy == "dedicated": - values['vcpus'] = 0 - # TODO request PCPUs! - resource_inventory["PCPU"] = flavor_details['vcpus'] - LOG.debug(resource_inventory) - - required_traits = [] - for key, value in extra_specs.items(): - if key.startswith("trait:"): - trait = key.split(":")[1] - if value == "required": - required_traits += trait - LOG.debug(required_traits) + # TODO need to check for custom resource requests! + resource_inventory = json.loads(values['resource_inventory']) + # TODO need to check traits as well! query_params = { - 'cpus': values['vcpus'], - 'memory': values['memory_mb'], - 'disk': values['disk_gb'], + 'cpus': resource_inventory['VCPU'], + 'memory': resource_inventory['MEMORY_MB'], + 'disk': resource_inventory['DISK_GB'], 'resource_properties': values['resource_properties'], - # TODO... - #'resource_inventory': values['resource_inventory'], - #'resource_traits': values['resource_traits'], + 'resource_inventory': resource_inventory, 'start_date': values['start_date'], 'end_date': values['end_date'] } From 0d0d6ea4121a60685c929d9f202c1060b0c765fd Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 17:39:54 +0100 Subject: [PATCH 34/65] Attempt in place edit of values Change-Id: Id6f8ddf003574ef3badbde95d47f64f13f0b1fa8 --- blazar/plugins/instances/instance_plugin.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index f80b49059..10956bf8b 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -528,9 +528,6 @@ def _validate_reservation_params(self, values): param='affinity (must be a bool value or None)') def _populate_values_with_flavor_info(self, values): - # do not mutate the user input in place - values = values.copy() - # Look up flavor to get the reservation details flavor_id = values.get('flavor_id') @@ -576,7 +573,6 @@ def _populate_values_with_flavor_info(self, values): if hw_cpu_policy == "dedicated": resource_inventory["PCPU"] = source_flavor['vcpus'] resource_inventory["VCPU"] = 0 - values["resource_inventory"] = json.dumps(resource_inventory) # Check for traits and extra resources for key, value in source_flavor['extra_specs'].items(): @@ -596,7 +592,6 @@ def _populate_values_with_flavor_info(self, values): values["source_flavor"] = json.dumps(source_flavor) LOG.debug(values) - return values def reserve_resource(self, reservation_id, values): self._check_missing_reservation_params(values) @@ -604,7 +599,7 @@ def reserve_resource(self, reservation_id, values): # when user specifies a flavor, # populate values from the flavor - values = self._populate_values_with_flavor_info(values) + self._populate_values_with_flavor_info(values) hosts = self.pickup_hosts(reservation_id, values) From 17f656a0d73cbde0688497b9b79b59c49587084f Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 17:41:32 +0100 Subject: [PATCH 35/65] Hack in exception to test theory Change-Id: I4ee78b3230ea55945fad263cbfe793aa4553ada0 --- blazar/plugins/instances/instance_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 10956bf8b..9e565408c 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -500,6 +500,7 @@ def update_resources(self, reservation_id): raise mgr_exceptions.NovaClientError() def _check_missing_reservation_params(self, values): + raise Exception("test") marshall_attributes = set(['amount', 'affinity']) # TODO(johngarbutt): do we want a config to require # flavor_id and reject other requests, or an enforcer? From 1a7222eb6b67af73982504423009982f42ce6a69 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 18:00:35 +0100 Subject: [PATCH 36/65] Try to add flavor_info during allocation_candidates Change-Id: I0a5e1dc53b828f87579a808843310f5a3ec66fb4 --- blazar/plugins/instances/instance_plugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 9e565408c..3f59df68f 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -143,6 +143,7 @@ def get_hosts_list(self, host_info, cpus, memory, disk): return hosts_list def allocation_candidates(self, reservation): + self._populate_values_with_flavor_info(reservation) return self.pickup_hosts(None, reservation)['added'] def list_allocations(self, query): @@ -529,6 +530,9 @@ def _validate_reservation_params(self, values): param='affinity (must be a bool value or None)') def _populate_values_with_flavor_info(self, values): + if "resource_inventory" in value.keys(): + return + # Look up flavor to get the reservation details flavor_id = values.get('flavor_id') From 826534f1b0cd0954a0bab551da4c64452c4d1452 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 18:02:07 +0100 Subject: [PATCH 37/65] Fix typo in _populate_values_with_flavor_info Change-Id: I7f59ef043bd0623e883bbcfc67e42e11e05f70a7 --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 3f59df68f..602796871 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -530,7 +530,7 @@ def _validate_reservation_params(self, values): param='affinity (must be a bool value or None)') def _populate_values_with_flavor_info(self, values): - if "resource_inventory" in value.keys(): + if "resource_inventory" in values.keys(): return # Look up flavor to get the reservation details From 3f8127af749df0391b0f046ec3aa21a4b73cb3d7 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 18:03:31 +0100 Subject: [PATCH 38/65] Remove exception hack Change-Id: Iff5fcf502cd3555e32cb17bf469cc92c00e3cb75 --- blazar/plugins/instances/instance_plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 602796871..fbfbebba2 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -501,7 +501,6 @@ def update_resources(self, reservation_id): raise mgr_exceptions.NovaClientError() def _check_missing_reservation_params(self, values): - raise Exception("test") marshall_attributes = set(['amount', 'affinity']) # TODO(johngarbutt): do we want a config to require # flavor_id and reject other requests, or an enforcer? From 7961d5bfbe79273211a452aba5a7b6f11374e39e Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 18:10:15 +0100 Subject: [PATCH 39/65] Add extra info into instance reservation db Change-Id: I3c1e67a473be4f008185ba5521facc949a834c0f --- blazar/plugins/instances/instance_plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index fbfbebba2..0fc3c42bc 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -615,7 +615,10 @@ def reserve_resource(self, reservation_id, values): 'disk_gb': values['disk_gb'], 'amount': values['amount'], 'affinity': bool_from_string(values['affinity'], default=None), - 'resource_properties': values['resource_properties'] + 'resource_properties': values['resource_properties'], + 'resource_inventory': values['resource_inventory'], + 'resource_traits': values['resource_traits'], + 'source_flavor': values['source_flavor'], } instance_reservation = db_api.instance_reservation_create( instance_reservation_val) From 4efc30a0a310b1f3363d0279716c3589bf67b8a3 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 10 Jul 2023 18:23:59 +0100 Subject: [PATCH 40/65] Copy across extra specs to flavor Change-Id: I16e28120db151e73d4896f7a038ad52ff45ab673 --- blazar/plugins/instances/instance_plugin.py | 36 ++++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 0fc3c42bc..6b479959b 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -360,7 +360,7 @@ def pickup_hosts(self, reservation_id, values): return {'added': added_host_ids, 'removed': removed_host_ids} def _create_flavor(self, reservation_id, vcpus, memory, disk, group_id, - resources=None): + source_flavor=None): flavor_details = { 'flavorid': reservation_id, 'name': RESERVATION_PREFIX + ":" + reservation_id, @@ -380,16 +380,25 @@ def _create_flavor(self, reservation_id, vcpus, memory, disk, group_id, } if group_id: extra_specs["affinity_id"] = group_id - # Set any required custom resource classes - for cr in resources or []: - extra_specs["resources:%s" % cr['name']] = cr['value'] - # TODO(johngarbutt) and required/forbidden traits? + + # Copy across any extra specs from the source flavor + # while being sure not to overide the ones used above + if source_flavor: + source_flavor = json.loads(source_flavor) + if source_flavor: + extra_specs["blazar_copy_from_id"] = source_flavor["id"] + extra_specs["blazar_copy_from_name"] = source_flavor["name"] + source_extra_specs = source_flavor["extra_specs"] + for key, value in source_extra_specs.items(): + if key not in extra_specs.keys(): + extra_specs[key] = value + LOG.debug(extra_specs) reserved_flavor.set_keys(extra_specs) return reserved_flavor - def _create_resources(self, inst_reservation, resource_inventory): + def _create_resources(self, inst_reservation): reservation_id = inst_reservation['reservation_id'] ctx = context.current() @@ -401,13 +410,8 @@ def _create_resources(self, inst_reservation, resource_inventory): # TODO this should be optional!! reserved_group_id = None - resources = [] - if resource_inventory: - for req in resource_inventory.split(','): - resource_class, amount = req.split(':') - resources.append({'name': resource_class, 'value': amount}) - # TODO(johngarbutt): traits and pci alias!? + resources = [] # TODO get PCPUs and more! if not inst_reservation['vcpus']: @@ -417,7 +421,7 @@ def _create_resources(self, inst_reservation, resource_inventory): inst_reservation['memory_mb'], inst_reservation['disk_gb'], reserved_group_id, - resources=resources) + inst_reservation['source_flavor']) pool = nova.ReservationPool() pool_metadata = { @@ -494,7 +498,8 @@ def update_resources(self, reservation_id): reservation['vcpus'], reservation['memory_mb'], reservation['disk_gb'], - reservation['server_group_id']) + reservation['server_group_id'], + reservation['source_flavor']) except nova_exceptions.ClientException: LOG.exception("Failed to update Nova resources " "for reservation %s", reservation['id']) @@ -628,8 +633,7 @@ def reserve_resource(self, reservation_id, values): 'reservation_id': reservation_id}) try: - # TODO... get resource inventory! - flavor, group_id, pool = self._create_resources(instance_reservation, resource_inventory="") + flavor, group_id, pool = self._create_resources(instance_reservation) except nova_exceptions.ClientException: LOG.exception("Failed to create Nova resources " "for reservation %s", reservation_id) From 12a97e24378405222ef9b6b89b71d2bc092ebff8 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 16:07:48 +0100 Subject: [PATCH 41/65] Add debug logs for candiate hosts Change-Id: Ie65aa7d5a09b0d07858e57c616215bef890d81b6 --- blazar/plugins/instances/instance_plugin.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 6b479959b..e41d38648 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -109,6 +109,7 @@ def resource_usage_by_event(event, resource_type): current_vcpus = current_memory = current_disk = 0 for event in events_list: + LOG.debug(f"For {host} we are checking event {event}") if event['event']['event_type'] == 'start_lease': current_vcpus += resource_usage_by_event(event, 'vcpus') current_memory += resource_usage_by_event(event, 'memory_mb') @@ -132,6 +133,9 @@ def get_hosts_list(self, host_info, cpus, memory, disk): reservations = host_info['reservations'] max_cpus, max_memory, max_disk = self.max_usages(host, reservations) + LOG.debug(f"checking {host} with " + f"cpu:{max_cpus} mem:{max_memory} disk:{max_disk}" + f"\nfor slots for: {cpus} {memory} {disk}") used_cpus, used_memory, used_disk = (cpus, memory, disk) while (max_cpus + used_cpus <= host['vcpus'] and max_memory + used_memory <= host['memory_mb'] and @@ -140,6 +144,8 @@ def get_hosts_list(self, host_info, cpus, memory, disk): used_cpus += cpus used_memory += memory used_disk += disk + + LOG.debug(f"For host {host} we have {len(hosts_list)} slots.") return hosts_list def allocation_candidates(self, reservation): @@ -218,7 +224,7 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, hosts = db_api.reservable_host_get_all_by_queries(filters) - LOG.debug(f"Found some hosts: {hosts}") + LOG.debug(f"Found some hosts from db: {hosts}") # Remove hosts without the required custom resources resource_inventory = resource_inventory.copy() @@ -246,16 +252,21 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, cr_hosts.append(host) hosts = cr_hosts + LOG.debug(f"Filtered hosts by resource classes: {hosts}") + if resource_traits: # TODO(johngarbutt): filter resource traits! pass + # Look for all reservations that match our time window + # and group that by host free_hosts, reserved_hosts = self.filter_hosts_by_reservation( hosts, start_date - datetime.timedelta(minutes=CONF.cleaning_time), end_date + datetime.timedelta(minutes=CONF.cleaning_time), excludes_res) + # See how many free slots available per host available_hosts = [] for host_info in (reserved_hosts + free_hosts): hosts_list = self.get_hosts_list(host_info, cpus, memory, disk) @@ -312,6 +323,8 @@ def pickup_hosts(self, reservation_id, values): # 2. hosts with reservations followed by hosts without reservations # Note that the `candidate_id_list` has already been ordered # satisfying the second requirement. + LOG.debug(f"Old hosts: {candidate_id_list}") + LOG.debug(f"Found candidates: {candidate_id_list}") if affinity: host_id_map = collections.Counter(candidate_id_list) available = {k for k, v in host_id_map.items() if v >= req_amount} From 0b635f1c271d11b6faccb97024410499532a65d5 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 16:11:12 +0100 Subject: [PATCH 42/65] Hack to skip CPUs for slots Change-Id: Ieb3a099c09071c029b86ed78729130d31e46cc2f --- blazar/plugins/instances/instance_plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index e41d38648..9f7e5bded 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -133,7 +133,9 @@ def get_hosts_list(self, host_info, cpus, memory, disk): reservations = host_info['reservations'] max_cpus, max_memory, max_disk = self.max_usages(host, reservations) - LOG.debug(f"checking {host} with " + # TODO: big hack here for pCPUs!!!! + max_cpus = 0 + LOG.debug(f"checking {host['hypervisor_hostname']} with " f"cpu:{max_cpus} mem:{max_memory} disk:{max_disk}" f"\nfor slots for: {cpus} {memory} {disk}") used_cpus, used_memory, used_disk = (cpus, memory, disk) @@ -145,7 +147,7 @@ def get_hosts_list(self, host_info, cpus, memory, disk): used_memory += memory used_disk += disk - LOG.debug(f"For host {host} we have {len(hosts_list)} slots.") + LOG.debug(f"For host {host['hypervisor_hostname']} we have {len(hosts_list)} slots.") return hosts_list def allocation_candidates(self, reservation): From f20dfd9c0dcad2428ddad25da38692969a5f2400 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 16:16:20 +0100 Subject: [PATCH 43/65] Debug to check we get resource inventory Change-Id: I8538def10e14b2b1c2cd5377aa224bba9a7b0116 --- blazar/plugins/instances/instance_plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 9f7e5bded..7fa540913 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -93,7 +93,10 @@ def filter_hosts_by_reservation(self, hosts, start_date, end_date, def max_usages(self, host, reservations): def resource_usage_by_event(event, resource_type): - return event['reservation']['instance_reservation'][resource_type] + instance_reservation = event['reservation']['instance_reservation'] + LOG.debug("Found resource usage: " + f"{instance_reservation['resource_inventory']}") + return instance_reservation[resource_type] events_list = [] for r in reservations: From 404c019e62c53260bcdce33ca3c3d2968812de63 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 17:41:55 +0100 Subject: [PATCH 44/65] Rewrite get_hosts_list to use resource_inventory Move to general list of resources, rather than just cpu,ram,disk. Change-Id: I0d67cc4589bfc856eca33c37bfde92ebc097b92a --- .../2ae105568b6d_add_inventory_traits.py | 2 +- blazar/db/sqlalchemy/models.py | 2 +- blazar/plugins/instances/instance_plugin.py | 134 +++++++++++------- 3 files changed, 87 insertions(+), 51 deletions(-) diff --git a/blazar/db/migration/alembic_migrations/versions/2ae105568b6d_add_inventory_traits.py b/blazar/db/migration/alembic_migrations/versions/2ae105568b6d_add_inventory_traits.py index 5bdb29b56..e9d69db83 100644 --- a/blazar/db/migration/alembic_migrations/versions/2ae105568b6d_add_inventory_traits.py +++ b/blazar/db/migration/alembic_migrations/versions/2ae105568b6d_add_inventory_traits.py @@ -37,7 +37,7 @@ def upgrade(): sa.Column('id', sa.String(length=36), nullable=False), sa.Column('computehost_id', sa.String(length=36), nullable=True), sa.Column('resource_class', sa.String(length=255), nullable=False), - sa.Column('allocation_ratio', sa.Integer(), nullable=False), + sa.Column('allocation_ratio', sa.Float(), nullable=False), sa.Column('total', sa.Integer(), nullable=False), sa.Column('reserved', sa.Integer(), nullable=False), sa.Column('max_unit', sa.Integer(), nullable=False), diff --git a/blazar/db/sqlalchemy/models.py b/blazar/db/sqlalchemy/models.py index 5d695a2d3..61bc02266 100644 --- a/blazar/db/sqlalchemy/models.py +++ b/blazar/db/sqlalchemy/models.py @@ -273,7 +273,7 @@ class ComputeHostResourceInventory(mb.BlazarBase): id = _id_column() computehost_id = sa.Column(sa.String(36), sa.ForeignKey('computehosts.id')) resource_class = sa.Column(sa.String(255), nullable=False) - allocation_ratio = sa.Column(sa.Integer, nullable=False) + allocation_ratio = sa.Column(sa.Float, nullable=False) total = sa.Column(sa.Integer, nullable=False) reserved = sa.Column(sa.Integer, nullable=False) max_unit = sa.Column(sa.Integer, nullable=False) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 7fa540913..4935a87b1 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -91,13 +91,24 @@ def filter_hosts_by_reservation(self, hosts, start_date, end_date, return free, non_free - def max_usages(self, host, reservations): - def resource_usage_by_event(event, resource_type): + def _max_usages(self, reservations): + def resource_usage_by_event(event): instance_reservation = event['reservation']['instance_reservation'] - LOG.debug("Found resource usage: " - f"{instance_reservation['resource_inventory']}") - return instance_reservation[resource_type] - + resource_inventory = instance_reservation['resource_inventory'] + if not resource_inventory: + resource_inventory = json.loads(resource_inventory) + if not resource_inventory: + # backwards compatible with older reservations + # that do not have a resource_inventory populated + resource_inventory = { + "VCPU": instance_reservation['vcpus'], + "MEMORY_MB": instance_reservation['memory_mb'], + "DISK_GB": instance_reservation['disk_gb'], + } + return resource_inventory + + # Get sorted list of events for all reservations + # that exist in the target time window events_list = [] for r in reservations: fetched_events = db_api.event_get_all_sorted_by_filters( @@ -105,52 +116,77 @@ def resource_usage_by_event(event, resource_type): filters={'lease_id': r['lease_id']}) events_list.extend([{'event': e, 'reservation': r} for e in fetched_events]) - events_list.sort(key=lambda x: x['event']['time']) - max_vcpus = max_memory = max_disk = 0 - current_vcpus = current_memory = current_disk = 0 - + current_usage = collections.defaultdict(int) + max_usage = collections.defaultdict(int) for event in events_list: - LOG.debug(f"For {host} we are checking event {event}") + usage = resource_usage_by_event(event) + if event['event']['event_type'] == 'start_lease': - current_vcpus += resource_usage_by_event(event, 'vcpus') - current_memory += resource_usage_by_event(event, 'memory_mb') - current_disk += resource_usage_by_event(event, 'disk_gb') - if max_vcpus < current_vcpus: - max_vcpus = current_vcpus - if max_memory < current_memory: - max_memory = current_memory - if max_disk < current_disk: - max_disk = current_disk + for rc, usage_amount in usage.items(): + current_usage[rc] += usage_amount + # TODO(johngarbutt) what if the max usage is + # actually outside the target time window? + if max_usage[rc] < current_usage[rc]: + max_usage[rc] = current_usage + elif event['event']['event_type'] == 'end_lease': - current_vcpus -= resource_usage_by_event(event, 'vcpus') - current_memory -= resource_usage_by_event(event, 'memory_mb') - current_disk -= resource_usage_by_event(event, 'disk_gb') + for rc, usage_amount in usage.items(): + current_usage[rc] -= usage_amount - return max_vcpus, max_memory, max_disk + return max_usage + + def _get_hosts_list(self, host_info, resource_request): + # For each host, look how many slots are available, + # given the current list of reservations within the + # target time window for this host + + # get high water mark of usage during all reservations + max_usage = self._max_usages(host_info['reservations']) - def get_hosts_list(self, host_info, cpus, memory, disk): - hosts_list = [] host = host_info['host'] - reservations = host_info['reservations'] - max_cpus, max_memory, max_disk = self.max_usages(host, - reservations) - # TODO: big hack here for pCPUs!!!! - max_cpus = 0 - LOG.debug(f"checking {host['hypervisor_hostname']} with " - f"cpu:{max_cpus} mem:{max_memory} disk:{max_disk}" - f"\nfor slots for: {cpus} {memory} {disk}") - used_cpus, used_memory, used_disk = (cpus, memory, disk) - while (max_cpus + used_cpus <= host['vcpus'] and - max_memory + used_memory <= host['memory_mb'] and - max_disk + used_disk <= host['local_gb']): + host_crs = db_api.host_custom_resource_get_all_per_host(host['id']) + host_inventory = {cr['resource_class']: cr for cr in host_crs} + if not host_inventory: + # backwards compat for hosts added before we + # get info from placement + host_inventory = { + "VCPU": dict(total=host['vcpus'], + allocation_ration=1.0), + "MEMORY_MB": dict(total=host['memory_mb'], + allocation_ration=1.0), + "DISK_GB": dict(total=host['local_gb'], + allocation_ration=1.0), + } + + # see how much room for slots we have + hosts_list = [] + current_usage = max_usage.copy() + + def has_free_slot(): + for rc, requested in resource_request.items(): + host_details = host_inventory.get(rc) + if not host_details: + # host doesn't have this sort of resource + return False + usage = current_usage[rc] + + if requested > host_details["max_unit"]: + # requested more than the max allowed by this host + return False + + capacity = ((host_details["total"] - host_details["reserved"]) + * host_details["allocation_ratio"]) + return (usage + requested) <= capacity + + while (has_free_slot()): hosts_list.append(host) - used_cpus += cpus - used_memory += memory - used_disk += disk + for rc, requested in resource_request.items(): + current_usage[rc] += requested - LOG.debug(f"For host {host['hypervisor_hostname']} we have {len(hosts_list)} slots.") + LOG.debug(f"For host {host_info['host']['hypervisor_hostname']} " + "we have {len(hosts_list)} slots.") return hosts_list def allocation_candidates(self, reservation): @@ -232,19 +268,19 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, LOG.debug(f"Found some hosts from db: {hosts}") # Remove hosts without the required custom resources - resource_inventory = resource_inventory.copy() + resource_extras = resource_inventory.copy() # TODO(johngarbutt) can we remove vcpus,disk,etc as a special case? - del resource_inventory["VCPU"] - del resource_inventory["MEMORY_MB"] - del resource_inventory["DISK_GB"] - if resource_inventory: + del resource_extras["VCPU"] + del resource_extras["MEMORY_MB"] + del resource_extras["DISK_GB"] + if resource_extras: cr_hosts = [] for host in hosts: host_crs = db_api.host_custom_resource_get_all_per_host( host['id']) host_inventory = {cr['resource_class']: cr for cr in host_crs} host_is_ok = False - for rc, request in resource_inventory.items(): + for rc, request in resource_extras.items(): host_inventory = host_inventory[rc] host_max = host_inventory['max_unit'] if request <= host_max: @@ -274,7 +310,7 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, # See how many free slots available per host available_hosts = [] for host_info in (reserved_hosts + free_hosts): - hosts_list = self.get_hosts_list(host_info, cpus, memory, disk) + hosts_list = self._get_hosts_list(host_info, resource_inventory) available_hosts.extend(hosts_list) return available_hosts From aba720f89416e74707bcb6416d028dbcbbb84569 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 17:47:32 +0100 Subject: [PATCH 45/65] Fix up resource_usage_by_event Change-Id: I8894ab6e927fbb891138dbc61fe78c31e89dcc7f --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 4935a87b1..54cbea7d0 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -95,7 +95,7 @@ def _max_usages(self, reservations): def resource_usage_by_event(event): instance_reservation = event['reservation']['instance_reservation'] resource_inventory = instance_reservation['resource_inventory'] - if not resource_inventory: + if resource_inventory: resource_inventory = json.loads(resource_inventory) if not resource_inventory: # backwards compatible with older reservations From a9063ff9722ebc7c19c71d139cae9145bade71e5 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 17:52:10 +0100 Subject: [PATCH 46/65] Improve debug for get_hosts_list Change-Id: I5d807fc8206d32a9ce43d5a8fa3a4b005881d584 --- blazar/plugins/instances/instance_plugin.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 54cbea7d0..3991e898b 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -163,21 +163,29 @@ def _get_hosts_list(self, host_info, resource_request): # see how much room for slots we have hosts_list = [] current_usage = max_usage.copy() + LOG.debug(f"Max usage {host_info['host']['hypervisor_hostname']} " + f"is {max_usage}") def has_free_slot(): for rc, requested in resource_request.items(): host_details = host_inventory.get(rc) if not host_details: # host doesn't have this sort of resource + LOG.debug(f"resource not found for {rc} for " + f"{host_info['host']['hypervisor_hostname']}") return False usage = current_usage[rc] if requested > host_details["max_unit"]: # requested more than the max allowed by this host + LOG.debug(f"resource not found for {rc} for " + f"{host_info['host']['hypervisor_hostname']}") return False capacity = ((host_details["total"] - host_details["reserved"]) * host_details["allocation_ratio"]) + LOG.debug(f"Capacity is {capacity} for {rc} for " + f"{host_info['host']['hypervisor_hostname']}") return (usage + requested) <= capacity while (has_free_slot()): @@ -186,7 +194,7 @@ def has_free_slot(): current_usage[rc] += requested LOG.debug(f"For host {host_info['host']['hypervisor_hostname']} " - "we have {len(hosts_list)} slots.") + f"we have {len(hosts_list)} slots.") return hosts_list def allocation_candidates(self, reservation): From 9dd840a62077ad1189603a2aa14f0bac78bc469f Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 17:58:54 +0100 Subject: [PATCH 47/65] Get more debug from max_usages Change-Id: I0951332456e8926e942f88464a432b3ac0cb9f76 --- blazar/plugins/instances/instance_plugin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 3991e898b..432514edc 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -124,6 +124,7 @@ def resource_usage_by_event(event): usage = resource_usage_by_event(event) if event['event']['event_type'] == 'start_lease': + LOG.debug(f"found start {event} with {usage}") for rc, usage_amount in usage.items(): current_usage[rc] += usage_amount # TODO(johngarbutt) what if the max usage is @@ -144,9 +145,14 @@ def _get_hosts_list(self, host_info, resource_request): # get high water mark of usage during all reservations max_usage = self._max_usages(host_info['reservations']) + LOG.debug(f"Max usage {host_info['host']['hypervisor_hostname']} " + f"is {max_usage}") host = host_info['host'] host_crs = db_api.host_custom_resource_get_all_per_host(host['id']) + LOG.debug(f"Inventory for{host_info['host']['hypervisor_hostname']} " + f"is {host_crs}") + host_inventory = {cr['resource_class']: cr for cr in host_crs} if not host_inventory: # backwards compat for hosts added before we @@ -163,8 +169,6 @@ def _get_hosts_list(self, host_info, resource_request): # see how much room for slots we have hosts_list = [] current_usage = max_usage.copy() - LOG.debug(f"Max usage {host_info['host']['hypervisor_hostname']} " - f"is {max_usage}") def has_free_slot(): for rc, requested in resource_request.items(): From 7b16e065fe91539ea6ce8b77b1688d4537d8ba35 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 18:05:29 +0100 Subject: [PATCH 48/65] Add more debug Change-Id: I6640599985d579d9164132b1277b831b6a4d04a8 --- blazar/plugins/instances/instance_plugin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 432514edc..4430afd21 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -124,18 +124,21 @@ def resource_usage_by_event(event): usage = resource_usage_by_event(event) if event['event']['event_type'] == 'start_lease': - LOG.debug(f"found start {event} with {usage}") + LOG.debug(f"found start{event} with {usage}") for rc, usage_amount in usage.items(): current_usage[rc] += usage_amount # TODO(johngarbutt) what if the max usage is # actually outside the target time window? if max_usage[rc] < current_usage[rc]: - max_usage[rc] = current_usage + max_usage[rc] = current_usage[rc] elif event['event']['event_type'] == 'end_lease': for rc, usage_amount in usage.items(): current_usage[rc] -= usage_amount + LOG.debug(f"after {event}\nusage is: {current_usage}\n" + f"max is: {max_usage}") + return max_usage def _get_hosts_list(self, host_info, resource_request): From 687e32e8ae5f5e786e4e55838f917920ecdee411 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 18:09:37 +0100 Subject: [PATCH 49/65] Don't error if not requested anything Change-Id: I4aa6e32ecd0d844b18f456d0951f99752a44a134 --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 4430afd21..c7e039f81 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -176,7 +176,7 @@ def _get_hosts_list(self, host_info, resource_request): def has_free_slot(): for rc, requested in resource_request.items(): host_details = host_inventory.get(rc) - if not host_details: + if requested and not host_details: # host doesn't have this sort of resource LOG.debug(f"resource not found for {rc} for " f"{host_info['host']['hypervisor_hostname']}") From 59762b1707021ccec3ef7d6a0802eb1e21ef055c Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 11 Jul 2023 18:14:29 +0100 Subject: [PATCH 50/65] Skip zero vpus better Change-Id: Ie8b3f8913c5bd3996416f0b66ebe325f922c572d --- blazar/plugins/instances/instance_plugin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index c7e039f81..1fcfeffe1 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -153,9 +153,6 @@ def _get_hosts_list(self, host_info, resource_request): host = host_info['host'] host_crs = db_api.host_custom_resource_get_all_per_host(host['id']) - LOG.debug(f"Inventory for{host_info['host']['hypervisor_hostname']} " - f"is {host_crs}") - host_inventory = {cr['resource_class']: cr for cr in host_crs} if not host_inventory: # backwards compat for hosts added before we @@ -168,6 +165,8 @@ def _get_hosts_list(self, host_info, resource_request): "DISK_GB": dict(total=host['local_gb'], allocation_ration=1.0), } + LOG.debug(f"Inventory for {host_info['host']['hypervisor_hostname']} " + f"is {host_inventory}") # see how much room for slots we have hosts_list = [] @@ -175,8 +174,12 @@ def _get_hosts_list(self, host_info, resource_request): def has_free_slot(): for rc, requested in resource_request.items(): + if not requested: + # skip things like requests for 0 vcpus + continue + host_details = host_inventory.get(rc) - if requested and not host_details: + if not host_details: # host doesn't have this sort of resource LOG.debug(f"resource not found for {rc} for " f"{host_info['host']['hypervisor_hostname']}") From 3991f92e916cc78bf2c14064241d820549769c4b Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 13:24:28 +0100 Subject: [PATCH 51/65] Move instance reservation to use placement aggregates In the hope it helps with ironic nodes Change-Id: I433ddf9c4085fee545f75442a2e306a628653ced --- blazar/plugins/instances/instance_plugin.py | 29 +++++------ blazar/plugins/oshosts/host_plugin.py | 3 +- blazar/utils/openstack/nova.py | 58 ++++++++++++++++++--- blazar/utils/openstack/placement.py | 41 +++++++++++++++ 4 files changed, 106 insertions(+), 25 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 1fcfeffe1..e08498fd7 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -495,7 +495,7 @@ def _create_resources(self, inst_reservation): reserved_group_id, inst_reservation['source_flavor']) - pool = nova.ReservationPool() + pool = nova.PlacementReservationPool() pool_metadata = { RESERVATION_PREFIX: reservation_id, 'filter_tenant_id': ctx.project_id, @@ -519,7 +519,8 @@ def check_and_delete_resource(client, id): check_and_delete_resource(self.nova.nova.server_groups, instance_reservation['server_group_id']) check_and_delete_resource(self.nova.nova.flavors, reservation_id) - check_and_delete_resource(nova.ReservationPool(), reservation_id) + # TODO(johngarbutt): should we remove all aggregates in placement here? + check_and_delete_resource(nova.PlacementReservationPool(), reservation_id) def update_resources(self, reservation_id): """Updates reserved resources in Nova. @@ -532,7 +533,7 @@ def update_resources(self, reservation_id): reservation = db_api.reservation_get(reservation_id) if reservation['status'] == 'active': - pool = nova.ReservationPool() + pool = nova.PlacementReservationPool() # Dict of number of instances to reserve on a host keyed by the # host id @@ -545,9 +546,7 @@ def update_resources(self, reservation_id): for host_id, num in allocation_map.items(): host = db_api.host_get(host_id) try: - pool.add_computehost( - reservation['aggregate_id'], - host['service_name'], stay_in=True) + pool.add_computehost(reservation, host) except mgr_exceptions.AggregateAlreadyHasHost: pass except nova_exceptions.ClientException: @@ -814,7 +813,7 @@ def on_start(self, resource_id): 'project_id': ctx.project_id}) raise mgr_exceptions.EventError() - pool = nova.ReservationPool() + pool = nova.PlacementReservationPool() # Dict of number of instances to reserve on a host keyed by the # host id @@ -826,8 +825,7 @@ def on_start(self, resource_id): for host_id, num in allocation_map.items(): host = db_api.host_get(host_id) - pool.add_computehost(instance_reservation['aggregate_id'], - host['service_name'], stay_in=True) + pool.add_computehost(instance_reservation, host) self.placement_client.update_reservation_inventory( host['hypervisor_hostname'], reservation_id, num) @@ -847,6 +845,8 @@ def on_end(self, resource_id): reservation_id=reservation_id) for allocation in allocations: host = db_api.host_get(allocation['compute_host_id']) + pool = nova.PlacementReservationPool() + pool.remove_computehost(instance_reservation, host) db_api.host_allocation_destroy(allocation['id']) hostnames.append(host['hypervisor_hostname']) @@ -1001,12 +1001,11 @@ def _select_host(self, reservation, lease): def _pre_reallocate(self, reservation, host_id): """Delete the reservation inventory/aggregates for the host.""" - pool = nova.ReservationPool() + pool = nova.PlacementReservationPool() # Remove the failed host from the aggregate. if reservation['status'] == status.reservation.ACTIVE: host = db_api.host_get(host_id) - pool.remove_computehost(reservation['aggregate_id'], - host['service_name']) + pool.remove_computehost(reservation, host) try: self.placement_client.delete_reservation_inventory( host['hypervisor_hostname'], reservation['id']) @@ -1015,13 +1014,11 @@ def _pre_reallocate(self, reservation, host_id): def _post_reallocate(self, reservation, lease, host_id, num): """Add the reservation inventory/aggregates for the host.""" - pool = nova.ReservationPool() + pool = nova.PlacementReservationPool() if reservation['status'] == status.reservation.ACTIVE: # Add the alternative host into the aggregate. new_host = db_api.host_get(host_id) - pool.add_computehost(reservation['aggregate_id'], - new_host['service_name'], - stay_in=True) + pool.add_computehost(reservation, new_host) # Here we use "additional=True" not to break the existing # inventory(allocations) on the new host self.placement_client.update_reservation_inventory( diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 4261bdf89..59c3ceb71 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -366,7 +366,8 @@ def create_computehost(self, host_values): pool = nova.ReservationPool() pool.add_computehost(self.freepool_name, - host_details['service_name']) + host_details['service_name'], + host_details['hypervisor_hostname']) host = None cantaddextracapability = [] diff --git a/blazar/utils/openstack/nova.py b/blazar/utils/openstack/nova.py index 1e87a8dca..6239fb691 100644 --- a/blazar/utils/openstack/nova.py +++ b/blazar/utils/openstack/nova.py @@ -26,6 +26,7 @@ from blazar.manager import exceptions as manager_exceptions from blazar.plugins import oshosts from blazar.utils.openstack import base +from blazar.utils.openstack import placement nova_opts = [ @@ -214,6 +215,9 @@ def __init__(self): self.config = CONF.nova self.freepool_name = self.config.aggregate_freepool_name + # used to manage placement aggregates + self.placement_client = placement.BlazarPlacementClient() + def get_aggregate_from_name_or_id(self, aggregate_obj): """Return an aggregate by name or an id.""" @@ -326,11 +330,12 @@ def get_computehosts(self, pool): try: agg = self.get_aggregate_from_name_or_id(pool) + # TODO get node list from placement? return agg.hosts except manager_exceptions.AggregateNotFound: return [] - def add_computehost(self, pool, hosts, stay_in=False): + def add_computehost(self, pool, hypervisor_hostnames, stay_in=False): """Add compute host(s) to an aggregate. Each host must exist and be in the freepool, otherwise raise an error. @@ -343,20 +348,23 @@ def add_computehost(self, pool, hosts, stay_in=False): Raise an aggregate exception if something wrong. """ - if not isinstance(hosts, list): - hosts = [hosts] + if not isinstance(hypervisor_hostnames, list): + hypervisor_hostnames = [hypervisor_hostnames] added_hosts = [] removed_hosts = [] agg = self.get_aggregate_from_name_or_id(pool) + # TODO get node list from placement? try: freepool_agg = self.get(self.freepool_name) + # TODO get node list from placement? except manager_exceptions.AggregateNotFound: raise manager_exceptions.NoFreePool() try: - for host in hosts: + # TODO!! + for host in hypervisor_hostnames: if freepool_agg.id != agg.id and not stay_in: if host not in freepool_agg.hosts: raise manager_exceptions.HostNotInFreePool( @@ -377,12 +385,13 @@ def add_computehost(self, pool, hosts, stay_in=False): # # NOTE(priteau): Preemptibles should not be used with # instance reservation yet. - self.terminate_preemptibles(host) + # TODO self.terminate_preemptibles(host) LOG.info("adding host '%(host)s' to aggregate %(id)s", {'host': host, 'id': agg.id}) try: self.nova.aggregates.add_host(agg.id, host) + # TODO: do placement instead added_hosts.append(host) except nova_exception.NotFound: raise manager_exceptions.HostNotFound(host=host) @@ -409,11 +418,11 @@ def remove_all_computehosts(self, pool): hosts = self.get_computehosts(pool) self.remove_computehost(pool, hosts) - def remove_computehost(self, pool, hosts): + def remove_computehost(self, pool, hypervisor_hostnames): """Remove compute host(s) from an aggregate.""" - if not isinstance(hosts, list): - hosts = [hosts] + if not isinstance(hypervisor_hostnames, list): + hypervisor_hostnames = [hypervisor_hostnames] agg = self.get_aggregate_from_name_or_id(pool) @@ -455,6 +464,7 @@ def remove_computehost(self, pool, hosts): def add_project(self, pool, project_id): """Add a project to an aggregate.""" + # TODO: we should make this work with the request filter? metadata = {project_id: self.config.project_id_key} agg = self.get_aggregate_from_name_or_id(pool) @@ -471,6 +481,7 @@ def remove_project(self, pool, project_id): def terminate_preemptibles(self, host): """Terminate preemptible instances running on host""" + raise Exception("TODO!") for server in self.nova.servers.list( search_opts={"host": host, "all_tenants": 1}): try: @@ -558,3 +569,34 @@ def get_servers_per_host(self, host): # a list of hosts without 'servers' attribute if no servers # are running on that host return None + + +class PlacementReservationPool(object): + def __init__(self): + self.config = CONF.nova + self.freepool_name = self.config.aggregate_freepool_name + + self.nova_client = ReservationPool() + # used to manage placement aggregates + self.placement_client = placement.BlazarPlacementClient() + + def create(self, name=None, az=None, metadata=None): + return self.nova_client.create(name, az, metadata) + + def delete(self, agregate_id): + # TODO: remove all hosts in placement? its expensive! + return self.nova_client.delete(agregate_id, force=True) + + def add_computehost(self, reservation, computehost): + aggregate_id = reservation["aggregate_id"] + rp = self.placement_client.get_resource_provider( + computehost["hypervisor_hostname"]) + self.placement_client.add_rp_to_aggregate( + aggregate_id, rp["uuid"]) + + def remove_computehost(self, reservation, computehost): + aggregate_id = reservation["aggregate_id"] + rp = self.placement_client.get_resource_provider( + computehost["hypervisor_hostname"]) + self.placement_client.remove_rp_from_aggregate( + aggregate_id, rp["uuid"]) \ No newline at end of file diff --git a/blazar/utils/openstack/placement.py b/blazar/utils/openstack/placement.py index 64c9c0d7e..aa4d3161b 100644 --- a/blazar/utils/openstack/placement.py +++ b/blazar/utils/openstack/placement.py @@ -450,3 +450,44 @@ def delete_reservation_inventory(self, host_name, reserv_uuid): LOG.info("Resource class %s doesn't exist or there is no " "inventory for that resource class on resource provider " "%s. Skipped the deletion", rc_name, rp_name) + + def get_rp_aggregates(self, rp_uuid): + """Call placement to get resource provider aggregates information. + + :param rp_uuid: UUID of the resource provider to get + """ + url = '/resource_providers/%s/aggregates' % rp_uuid + resp = self.get(url) + if resp: + return resp.json() + raise exceptions.ResourceProviderNotFound(resource_provider=rp_uuid) + + def _put_rp_aggregates(self, rp_uuid, aggregate_info): + """Call placement to get resource provider aggregates information. + + :param rp_uuid: UUID of the resource provider to get + """ + url = '/resource_providers/%s/aggregates' % rp_uuid + resp = self.put(url, aggregate_info) + # TODO(johngarbutt): should we retry on confict? + resp.raise_for_status() + return resp.json() + + def add_rp_to_aggregate(self, aggregate_uuid, rp_uuid): + aggregate_info = self.get_rp_aggregates(rp_uuid) + if aggregate_uuid not in aggregate_info["aggregates"]: + aggregate_info["aggregates"].append(aggregate_uuid) + # NOTE: this includes resource_provider_generation + # to ensure an atomic update + return self._put_rp_aggregates(aggregate_info) + + def remove_rp_from_aggregate(self, aggregate_uuid, rp_uuid): + aggregate_info = self.get_rp_aggregates(rp_uuid) + current_aggregates = list(aggregate_info["aggregates"]) + if aggregate_uuid in current_aggregates: + new_aggregates = [agg for agg in current_aggregates + if agg != aggregate_uuid] + aggregate_info["aggregates"] = new_aggregates + # NOTE: this includes resource_provider_generation + # to ensure an atomic update + return self._put_rp_aggregates(aggregate_info) From dc5938a49eda8060fe32dcbfca1417d25b8c85f5 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 14:27:08 +0100 Subject: [PATCH 52/65] Move create_computehost to use placement aggregates Change-Id: Ic13057d21bfaad76e39a155c7b1889bd4279525d --- blazar/plugins/oshosts/host_plugin.py | 10 +++----- blazar/utils/openstack/nova.py | 35 ++++++++++++++++++--------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 59c3ceb71..72fe5da97 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -364,10 +364,9 @@ def create_computehost(self, host_values): self.placement_client.create_reservation_provider(hostname) - pool = nova.ReservationPool() - pool.add_computehost(self.freepool_name, - host_details['service_name'], - host_details['hypervisor_hostname']) + pool = nova.PlacementReservationPool() + freepool_id = pool.get_aggregate_id_from_name(self.freepool_name) + pool.add_computehost(freepool_id, host_details) host = None cantaddextracapability = [] @@ -379,8 +378,7 @@ def create_computehost(self, host_values): # We need to rollback # TODO(sbauza): Investigate use of Taskflow for atomic # transactions - pool.remove_computehost(self.freepool_name, - host_details['service_name']) + pool.remove_computehost(freepool_id, host_details) self.placement_client.delete_reservation_provider(hostname) raise e for key in extra_capabilities: diff --git a/blazar/utils/openstack/nova.py b/blazar/utils/openstack/nova.py index 6239fb691..13d0a3616 100644 --- a/blazar/utils/openstack/nova.py +++ b/blazar/utils/openstack/nova.py @@ -573,30 +573,43 @@ def get_servers_per_host(self, host): class PlacementReservationPool(object): def __init__(self): - self.config = CONF.nova - self.freepool_name = self.config.aggregate_freepool_name - self.nova_client = ReservationPool() # used to manage placement aggregates self.placement_client = placement.BlazarPlacementClient() + def get_aggregate_id_from_name(self, name): + all_aggregates = self.nova_client.nova.aggregates.list() + # TODO(johngarbutt): this is horrible, but the only API way + for agg in all_aggregates: + if name == agg.name: + return agg.id + raise manager_exceptions.AggregateNotFound(pool=name) + def create(self, name=None, az=None, metadata=None): return self.nova_client.create(name, az, metadata) - def delete(self, agregate_id): + def delete(self, nova_aggregate_id): # TODO: remove all hosts in placement? its expensive! - return self.nova_client.delete(agregate_id, force=True) + return self.nova_client.delete(nova_aggregate_id, force=True) - def add_computehost(self, reservation, computehost): - aggregate_id = reservation["aggregate_id"] + def add_computehost(self, nova_aggregate_id, computehost): rp = self.placement_client.get_resource_provider( computehost["hypervisor_hostname"]) + + nova_client = BlazarNovaClient(version="2.41") + nova_agg = nova_client.aggregates.get(nova_aggregate_id) + aggregate_uuid = nova_agg["uuid"] + self.placement_client.add_rp_to_aggregate( - aggregate_id, rp["uuid"]) + aggregate_uuid, rp["uuid"]) - def remove_computehost(self, reservation, computehost): - aggregate_id = reservation["aggregate_id"] + def remove_computehost(self, nova_aggregate_id, computehost): rp = self.placement_client.get_resource_provider( computehost["hypervisor_hostname"]) + + nova_client = BlazarNovaClient(version="2.41") + nova_agg = nova_client.aggregates.get(nova_aggregate_id) + aggregate_uuid = nova_agg["uuid"] + self.placement_client.remove_rp_from_aggregate( - aggregate_id, rp["uuid"]) \ No newline at end of file + aggregate_uuid, rp["uuid"]) From 9d22ba79489dc90ab892cbc40c503e45de1bd2fd Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 14:29:52 +0100 Subject: [PATCH 53/65] Fix getting aggregate uuid Change-Id: If2c6819fc33abdc16f50943bb952cabea907007f --- blazar/utils/openstack/nova.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blazar/utils/openstack/nova.py b/blazar/utils/openstack/nova.py index 13d0a3616..16e5f5705 100644 --- a/blazar/utils/openstack/nova.py +++ b/blazar/utils/openstack/nova.py @@ -598,7 +598,7 @@ def add_computehost(self, nova_aggregate_id, computehost): nova_client = BlazarNovaClient(version="2.41") nova_agg = nova_client.aggregates.get(nova_aggregate_id) - aggregate_uuid = nova_agg["uuid"] + aggregate_uuid = nova_agg.uuid self.placement_client.add_rp_to_aggregate( aggregate_uuid, rp["uuid"]) @@ -609,7 +609,7 @@ def remove_computehost(self, nova_aggregate_id, computehost): nova_client = BlazarNovaClient(version="2.41") nova_agg = nova_client.aggregates.get(nova_aggregate_id) - aggregate_uuid = nova_agg["uuid"] + aggregate_uuid = nova_agg.uuid self.placement_client.remove_rp_from_aggregate( aggregate_uuid, rp["uuid"]) From d7d59506c1c84b2decbed2b8d98cc8342bcef42d Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 14:32:11 +0100 Subject: [PATCH 54/65] Add missing arg for _put_rp_aggregates Change-Id: I68c640b864f0bb910c91948ab45913018ff84c09 --- blazar/utils/openstack/placement.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blazar/utils/openstack/placement.py b/blazar/utils/openstack/placement.py index aa4d3161b..860a86ee0 100644 --- a/blazar/utils/openstack/placement.py +++ b/blazar/utils/openstack/placement.py @@ -479,7 +479,7 @@ def add_rp_to_aggregate(self, aggregate_uuid, rp_uuid): aggregate_info["aggregates"].append(aggregate_uuid) # NOTE: this includes resource_provider_generation # to ensure an atomic update - return self._put_rp_aggregates(aggregate_info) + return self._put_rp_aggregates(rp_uuid, aggregate_info) def remove_rp_from_aggregate(self, aggregate_uuid, rp_uuid): aggregate_info = self.get_rp_aggregates(rp_uuid) @@ -490,4 +490,4 @@ def remove_rp_from_aggregate(self, aggregate_uuid, rp_uuid): aggregate_info["aggregates"] = new_aggregates # NOTE: this includes resource_provider_generation # to ensure an atomic update - return self._put_rp_aggregates(aggregate_info) + return self._put_rp_aggregates(rp_uuid, aggregate_info) From 4e8aa5afa730b40b5f2c08499591e171f0a50879 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 14:45:43 +0100 Subject: [PATCH 55/65] Try to fix up remove_host Change-Id: Iac99a4a39e4d28d3202d4f7dfd77dfe675acbd28 --- blazar/plugins/oshosts/host_plugin.py | 6 +++--- blazar/utils/openstack/nova.py | 23 ++++++++--------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/blazar/plugins/oshosts/host_plugin.py b/blazar/plugins/oshosts/host_plugin.py index 72fe5da97..560054002 100644 --- a/blazar/plugins/oshosts/host_plugin.py +++ b/blazar/plugins/oshosts/host_plugin.py @@ -516,9 +516,9 @@ def delete_computehost(self, host_id): # host=host['hypervisor_hostname'], servers=servers) try: - pool = nova.ReservationPool() - pool.remove_computehost(self.freepool_name, - host['service_name']) + pool = nova.PlacementReservationPool() + freepool_id = pool.get_aggregate_id_from_name(self.freepool_name) + pool.remove_computehost(freepool_id, host) self.placement_client.delete_reservation_provider( host['hypervisor_hostname']) # NOTE(sbauza): Extracapabilities will be destroyed thanks to diff --git a/blazar/utils/openstack/nova.py b/blazar/utils/openstack/nova.py index 16e5f5705..571765b13 100644 --- a/blazar/utils/openstack/nova.py +++ b/blazar/utils/openstack/nova.py @@ -215,9 +215,6 @@ def __init__(self): self.config = CONF.nova self.freepool_name = self.config.aggregate_freepool_name - # used to manage placement aggregates - self.placement_client = placement.BlazarPlacementClient() - def get_aggregate_from_name_or_id(self, aggregate_obj): """Return an aggregate by name or an id.""" @@ -330,12 +327,11 @@ def get_computehosts(self, pool): try: agg = self.get_aggregate_from_name_or_id(pool) - # TODO get node list from placement? return agg.hosts except manager_exceptions.AggregateNotFound: return [] - def add_computehost(self, pool, hypervisor_hostnames, stay_in=False): + def add_computehost(self, pool, hosts, stay_in=False): """Add compute host(s) to an aggregate. Each host must exist and be in the freepool, otherwise raise an error. @@ -348,8 +344,8 @@ def add_computehost(self, pool, hypervisor_hostnames, stay_in=False): Raise an aggregate exception if something wrong. """ - if not isinstance(hypervisor_hostnames, list): - hypervisor_hostnames = [hypervisor_hostnames] + if not isinstance(hosts, list): + hosts = [hosts] added_hosts = [] removed_hosts = [] @@ -363,8 +359,7 @@ def add_computehost(self, pool, hypervisor_hostnames, stay_in=False): raise manager_exceptions.NoFreePool() try: - # TODO!! - for host in hypervisor_hostnames: + for host in hosts: if freepool_agg.id != agg.id and not stay_in: if host not in freepool_agg.hosts: raise manager_exceptions.HostNotInFreePool( @@ -385,7 +380,7 @@ def add_computehost(self, pool, hypervisor_hostnames, stay_in=False): # # NOTE(priteau): Preemptibles should not be used with # instance reservation yet. - # TODO self.terminate_preemptibles(host) + # TODO!! self.terminate_preemptibles(host) LOG.info("adding host '%(host)s' to aggregate %(id)s", {'host': host, 'id': agg.id}) @@ -418,11 +413,11 @@ def remove_all_computehosts(self, pool): hosts = self.get_computehosts(pool) self.remove_computehost(pool, hosts) - def remove_computehost(self, pool, hypervisor_hostnames): + def remove_computehost(self, pool, hosts): """Remove compute host(s) from an aggregate.""" - if not isinstance(hypervisor_hostnames, list): - hypervisor_hostnames = [hypervisor_hostnames] + if not isinstance(hosts, list): + hosts = [hosts] agg = self.get_aggregate_from_name_or_id(pool) @@ -464,7 +459,6 @@ def remove_computehost(self, pool, hypervisor_hostnames): def add_project(self, pool, project_id): """Add a project to an aggregate.""" - # TODO: we should make this work with the request filter? metadata = {project_id: self.config.project_id_key} agg = self.get_aggregate_from_name_or_id(pool) @@ -481,7 +475,6 @@ def remove_project(self, pool, project_id): def terminate_preemptibles(self, host): """Terminate preemptible instances running on host""" - raise Exception("TODO!") for server in self.nova.servers.list( search_opts={"host": host, "all_tenants": 1}): try: From 8be74a7833eecde52d20f796bf8750280fed8ec7 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 14:49:13 +0100 Subject: [PATCH 56/65] Add info on host filters Change-Id: I27e7ee034e2777a42d14493ef03e1dad6c3c57a7 --- blazar/plugins/instances/instance_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index e08498fd7..d6725e7b4 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -281,6 +281,7 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, if resource_properties: filters += plugins_utils.convert_requirements(resource_properties) + LOG.debug(f"Filters are: {filters}") hosts = db_api.reservable_host_get_all_by_queries(filters) LOG.debug(f"Found some hosts from db: {hosts}") From 44bdbee1a0f457cab5ec87fcb31799077d17e900 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 14:58:17 +0100 Subject: [PATCH 57/65] Get more info Change-Id: Ic1f1a32decbd9de46dda4ffd564ba1ceb7d4722f --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index d6725e7b4..2c2f8359c 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -281,7 +281,7 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, if resource_properties: filters += plugins_utils.convert_requirements(resource_properties) - LOG.debug(f"Filters are: {filters}") + LOG.debug(f"Filters are: {filters} {cpus} {resource_inventory}") hosts = db_api.reservable_host_get_all_by_queries(filters) LOG.debug(f"Found some hosts from db: {hosts}") From 3bc0254759091cd7c499b353b047220477de3ef2 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 15:03:53 +0100 Subject: [PATCH 58/65] Fixup finding extra spec resource requests Change-Id: I597f993a85cbed718b13d1340d9ad21ea9b24682 --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 2c2f8359c..df74bc8de 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -664,7 +664,7 @@ def _populate_values_with_flavor_info(self, values): elif value == "forbidden": resource_traits[trait] = "forbidden" - if key.startswith("resource:"): + if key.startswith("resources:"): rc = key.split(":")[1] values[rc] = int(key) From 93687408bf64a4bf2d781548b559bf29666fb73f Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 15:06:34 +0100 Subject: [PATCH 59/65] Fix up the value of extra resources Change-Id: I0411ffa6d2a6595a5ca74d6cf9bdb4ebd7346989 --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index df74bc8de..ad222746d 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -666,7 +666,7 @@ def _populate_values_with_flavor_info(self, values): if key.startswith("resources:"): rc = key.split(":")[1] - values[rc] = int(key) + values[rc] = int(value) values["resource_inventory"] = json.dumps(resource_inventory) values["resource_traits"] = json.dumps(resource_traits) From 51582a050a0601ad264a7bc6872908b7787947e6 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 15:08:57 +0100 Subject: [PATCH 60/65] Nope, try put custom resources in the inventory Change-Id: I5d0b817c289c444c47dcde04332e266f37e6dd1d --- blazar/plugins/instances/instance_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index ad222746d..b947139d6 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -666,7 +666,7 @@ def _populate_values_with_flavor_info(self, values): if key.startswith("resources:"): rc = key.split(":")[1] - values[rc] = int(value) + resource_inventory[rc] = int(value) values["resource_inventory"] = json.dumps(resource_inventory) values["resource_traits"] = json.dumps(resource_traits) From ac291439e0c33823d5b9294e40ec74812adca25f Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 15:15:39 +0100 Subject: [PATCH 61/65] Fix up resource class filtering Change-Id: I79e88a943e2c91d25173b9757b7de4be07a5556f --- blazar/plugins/instances/instance_plugin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index b947139d6..613532392 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -300,13 +300,16 @@ def query_available_hosts(self, cpus=None, memory=None, disk=None, host_inventory = {cr['resource_class']: cr for cr in host_crs} host_is_ok = False for rc, request in resource_extras.items(): - host_inventory = host_inventory[rc] - host_max = host_inventory['max_unit'] - if request <= host_max: + rc_info = host_inventory.get(rc) + if not rc_info: + host_is_ok = False + LOG.debug(f"Filter out, no {rc} for {host}") + break + if rc_info and request <= rc_info['max_unit']: host_is_ok = True else: host_is_ok = False - LOG.debug(f"Filter out becase of {rc} for {host}") + LOG.debug(f"Filter out, too many {rc} for {host}") break if host_is_ok: cr_hosts.append(host) From 4411e629f58b04ff47c01ccf2509b50f68ec633c Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 15:31:50 +0100 Subject: [PATCH 62/65] Fix up use of add/remove compute host Change-Id: I1c57794e8dd12266fe95e5ed3a7c0e18b05deb10 --- blazar/plugins/instances/instance_plugin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 613532392..01184fcaa 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -550,7 +550,7 @@ def update_resources(self, reservation_id): for host_id, num in allocation_map.items(): host = db_api.host_get(host_id) try: - pool.add_computehost(reservation, host) + pool.add_computehost(reservation["aggregate_id"], host) except mgr_exceptions.AggregateAlreadyHasHost: pass except nova_exceptions.ClientException: @@ -829,7 +829,7 @@ def on_start(self, resource_id): for host_id, num in allocation_map.items(): host = db_api.host_get(host_id) - pool.add_computehost(instance_reservation, host) + pool.add_computehost(instance_reservation["aggregate_id"], host) self.placement_client.update_reservation_inventory( host['hypervisor_hostname'], reservation_id, num) @@ -850,7 +850,7 @@ def on_end(self, resource_id): for allocation in allocations: host = db_api.host_get(allocation['compute_host_id']) pool = nova.PlacementReservationPool() - pool.remove_computehost(instance_reservation, host) + pool.remove_computehost(instance_reservation["aggregate_id"], host) db_api.host_allocation_destroy(allocation['id']) hostnames.append(host['hypervisor_hostname']) @@ -1009,7 +1009,7 @@ def _pre_reallocate(self, reservation, host_id): # Remove the failed host from the aggregate. if reservation['status'] == status.reservation.ACTIVE: host = db_api.host_get(host_id) - pool.remove_computehost(reservation, host) + pool.remove_computehost(reservation["aggregate_id"], host) try: self.placement_client.delete_reservation_inventory( host['hypervisor_hostname'], reservation['id']) @@ -1022,7 +1022,7 @@ def _post_reallocate(self, reservation, lease, host_id, num): if reservation['status'] == status.reservation.ACTIVE: # Add the alternative host into the aggregate. new_host = db_api.host_get(host_id) - pool.add_computehost(reservation, new_host) + pool.add_computehost(reservation["aggregate_id"], new_host) # Here we use "additional=True" not to break the existing # inventory(allocations) on the new host self.placement_client.update_reservation_inventory( From 553240d496ebd5c5729b1aee7937bd401dd9f061 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 16:17:52 +0100 Subject: [PATCH 63/65] Fix creating lease as non-admin user Change-Id: Ia4db9d7ca4107b09d26302b2594c5c1953ba7a46 --- blazar/utils/openstack/nova.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/blazar/utils/openstack/nova.py b/blazar/utils/openstack/nova.py index 571765b13..83635ca1f 100644 --- a/blazar/utils/openstack/nova.py +++ b/blazar/utils/openstack/nova.py @@ -566,12 +566,24 @@ def get_servers_per_host(self, host): class PlacementReservationPool(object): def __init__(self): - self.nova_client = ReservationPool() + self.old_pool = ReservationPool() # used to manage placement aggregates self.placement_client = placement.BlazarPlacementClient() + @property + def nova(self): + # 2.41 is the first microversion where we get the aggregate uuid + # and it was available in Ocata + nova = BlazarNovaClient(version="2.41", + username=self.username, + password=self.password, + user_domain_name=self.user_domain_name, + project_name=self.project_name, + project_domain_name=self.project_domain_name) + return nova + def get_aggregate_id_from_name(self, name): - all_aggregates = self.nova_client.nova.aggregates.list() + all_aggregates = self.nova.aggregates.list() # TODO(johngarbutt): this is horrible, but the only API way for agg in all_aggregates: if name == agg.name: @@ -579,18 +591,17 @@ def get_aggregate_id_from_name(self, name): raise manager_exceptions.AggregateNotFound(pool=name) def create(self, name=None, az=None, metadata=None): - return self.nova_client.create(name, az, metadata) + return self.old_pool.create(name, az, metadata) def delete(self, nova_aggregate_id): # TODO: remove all hosts in placement? its expensive! - return self.nova_client.delete(nova_aggregate_id, force=True) + return self.old_pool.delete(nova_aggregate_id, force=True) def add_computehost(self, nova_aggregate_id, computehost): rp = self.placement_client.get_resource_provider( computehost["hypervisor_hostname"]) - nova_client = BlazarNovaClient(version="2.41") - nova_agg = nova_client.aggregates.get(nova_aggregate_id) + nova_agg = self.nova.aggregates.get(nova_aggregate_id) aggregate_uuid = nova_agg.uuid self.placement_client.add_rp_to_aggregate( @@ -600,8 +611,7 @@ def remove_computehost(self, nova_aggregate_id, computehost): rp = self.placement_client.get_resource_provider( computehost["hypervisor_hostname"]) - nova_client = BlazarNovaClient(version="2.41") - nova_agg = nova_client.aggregates.get(nova_aggregate_id) + nova_agg = self.nova.aggregates.get(nova_aggregate_id) aggregate_uuid = nova_agg.uuid self.placement_client.remove_rp_from_aggregate( From ced262b8ceb0a255ae907f91772e2eadc6bd174a Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 14 Jul 2023 16:24:17 +0100 Subject: [PATCH 64/65] Fix up nova client admin creds Change-Id: Ia828115c26fddf8af41523f713c1f23b17240168 --- blazar/utils/openstack/nova.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/blazar/utils/openstack/nova.py b/blazar/utils/openstack/nova.py index 83635ca1f..1e6918636 100644 --- a/blazar/utils/openstack/nova.py +++ b/blazar/utils/openstack/nova.py @@ -574,12 +574,13 @@ def __init__(self): def nova(self): # 2.41 is the first microversion where we get the aggregate uuid # and it was available in Ocata - nova = BlazarNovaClient(version="2.41", - username=self.username, - password=self.password, - user_domain_name=self.user_domain_name, - project_name=self.project_name, - project_domain_name=self.project_domain_name) + nova = BlazarNovaClient( + version="2.41", + username=CONF.os_admin_username, + password=CONF.os_admin_password, + user_domain_name=CONF.os_admin_user_domain_name, + project_name=CONF.os_admin_project_name, + project_domain_name=CONF.os_admin_project_domain_name) return nova def get_aggregate_id_from_name(self, name): From ce25e5333df206427fd3bbf215cdd9b2e29540b7 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Mon, 29 Apr 2024 14:23:19 +0100 Subject: [PATCH 65/65] Revert local tox.ini changes Change-Id: I6bac2986d3530c0076f4c068f17104b31f19c46c --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index da0fa7e45..15f8a17b8 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ basepython = python3 usedevelop = True allowlist_externals = rm deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/yoga} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt setenv = VIRTUAL_ENV={envdir}