diff --git a/blazar/db/sqlalchemy/api.py b/blazar/db/sqlalchemy/api.py index 516736b60..0eaa22c2d 100644 --- a/blazar/db/sqlalchemy/api.py +++ b/blazar/db/sqlalchemy/api.py @@ -2045,6 +2045,7 @@ def resource_properties_list(resource_type): models.ExtraCapability.capability_name, models.ExtraCapability.private, resource_model.capability_value, + models.ExtraCapability.is_unique, ).join(resource_model), resource_model, deleted=False).distinct() return query.all() diff --git a/blazar/db/sqlalchemy/models.py b/blazar/db/sqlalchemy/models.py index b9fd4bcfe..72ca0d405 100644 --- a/blazar/db/sqlalchemy/models.py +++ b/blazar/db/sqlalchemy/models.py @@ -316,16 +316,19 @@ def to_dict(self): @validates('capability_value') def validate_capability_value(self, key, capability_value): - extra_capability = ExtraCapability.query.filter_by(id=self.capability_id).first() + from blazar.db.sqlalchemy import facade_wrapper + session = facade_wrapper.get_session() + extra_capability = session.query(ExtraCapability).filter_by(id=self.capability_id).first() if extra_capability and extra_capability.is_unique: existing_capability = ( - self.query.filter_by(computehost_id=self.computehost_id, + session.query(ComputeHostExtraCapability).filter_by(computehost_id=self.computehost_id, capability_id=self.capability_id).first() ) if existing_capability: raise ValueError( - f"{extra_capability.capability_name} is not unique " - f"for compute_host {self.computehost_id}" + f"{extra_capability.capability_name} must be unique. " + f"Please select unique {extra_capability.capability_name} for " + f"{self.computehost_id}" ) return capability_value diff --git a/blazar/plugins/base.py b/blazar/plugins/base.py index ae247f674..fd2ca3784 100644 --- a/blazar/plugins/base.py +++ b/blazar/plugins/base.py @@ -105,16 +105,20 @@ def on_start(self, resource_id, lease=None): def list_resource_properties(self, query): detail = False if not query else query.get('detail', False) resource_properties = collections.defaultdict(list) + is_property_unique = {} - for name, private, value in db_api.resource_properties_list( + for name, private, value, is_unique in db_api.resource_properties_list( self.resource_type): - if not private: resource_properties[name].append(value) + is_property_unique[name] = is_unique if detail: return [ - dict(property=k, private=False, values=v) + dict( + property=k, private=False, values=v, + is_unique=is_property_unique[k] + ) for k, v in resource_properties.items()] else: return [dict(property=k) for k, v in resource_properties.items()] diff --git a/blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py b/blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py index b11592320..da0d89738 100644 --- a/blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py +++ b/blazar/tests/db/sqlalchemy/test_sqlalchemy_api.py @@ -551,7 +551,7 @@ def test_resource_properties_list(self): result = db_api.resource_properties_list('physical:host') - self.assertListEqual(result, [('vgpu', False, '2')]) + self.assertListEqual(result, [('vgpu', False, '2', False)]) def test_search_for_hosts_by_composed_queries(self): """Create one host and test composed queries.""" diff --git a/blazar/tests/plugins/networks/test_network_plugin.py b/blazar/tests/plugins/networks/test_network_plugin.py index cb877335e..fc3c2f9a5 100644 --- a/blazar/tests/plugins/networks/test_network_plugin.py +++ b/blazar/tests/plugins/networks/test_network_plugin.py @@ -1036,11 +1036,11 @@ def test_list_resource_properties(self): # Expecting a list of (Reservation, Allocation) self.db_list_resource_properties.return_value = [ - ('prop1', False, 'aaa'), - ('prop1', False, 'bbb'), - ('prop2', False, 'aaa'), - ('prop2', False, 'aaa'), - ('prop3', True, 'aaa') + ('prop1', False, 'aaa', False), + ('prop1', False, 'bbb', False), + ('prop2', False, 'aaa', False), + ('prop2', False, 'aaa', False), + ('prop3', True, 'aaa', False) ] expected = [ @@ -1063,15 +1063,15 @@ def test_list_resource_properties_with_detail(self): # Expecting a list of (Reservation, Allocation) self.db_list_resource_properties.return_value = [ - ('prop1', False, 'aaa'), - ('prop1', False, 'bbb'), - ('prop2', False, 'ccc'), - ('prop3', True, 'aaa') + ('prop1', False, 'aaa', False), + ('prop1', False, 'bbb', False), + ('prop2', False, 'ccc', False), + ('prop3', True, 'aaa', False) ] expected = [ - {'property': 'prop1', 'private': False, 'values': ['aaa', 'bbb']}, - {'property': 'prop2', 'private': False, 'values': ['ccc']} + {'property': 'prop1', 'private': False, 'values': ['aaa', 'bbb'], 'is_unique': False}, + {'property': 'prop2', 'private': False, 'values': ['ccc'], 'is_unique': False} ] ret = self.fake_network_plugin.list_resource_properties( diff --git a/blazar/tests/plugins/oshosts/test_physical_host_plugin.py b/blazar/tests/plugins/oshosts/test_physical_host_plugin.py index 3128b3b81..7e447117b 100644 --- a/blazar/tests/plugins/oshosts/test_physical_host_plugin.py +++ b/blazar/tests/plugins/oshosts/test_physical_host_plugin.py @@ -2549,16 +2549,16 @@ def test_list_resource_properties(self): # Expecting a list of (Reservation, Allocation) self.db_list_resource_properties.return_value = [ - ('prop1', False, 'aaa'), - ('prop1', False, 'bbb'), - ('prop2', False, 'aaa'), - ('prop2', False, 'aaa'), - ('prop3', True, 'aaa') + ('prop1', False, 'aaa', False), + ('prop1', False, 'bbb', False), + ('prop2', False, 'aaa', False), + ('prop2', False, 'aaa', False), + ('prop3', True, 'aaa', False) ] expected = [ - {'property': 'prop1'}, - {'property': 'prop2'} + {'property': 'prop1',}, + {'property': 'prop2',} ] ret = self.fake_phys_plugin.list_resource_properties( @@ -2577,15 +2577,15 @@ def test_list_resource_properties_with_detail(self): # Expecting a list of (Reservation, Allocation) self.db_list_resource_properties.return_value = [ - ('prop1', False, 'aaa'), - ('prop1', False, 'bbb'), - ('prop2', False, 'ccc'), - ('prop3', True, 'aaa') + ('prop1', False, 'aaa', False), + ('prop1', False, 'bbb', False), + ('prop2', False, 'ccc', False), + ('prop3', True, 'aaa', False) ] expected = [ - {'property': 'prop1', 'private': False, 'values': ['aaa', 'bbb']}, - {'property': 'prop2', 'private': False, 'values': ['ccc']} + {'property': 'prop1', 'private': False, 'values': ['aaa', 'bbb'], 'is_unique': False}, + {'property': 'prop2', 'private': False, 'values': ['ccc'], 'is_unique': False} ] ret = self.fake_phys_plugin.list_resource_properties(