diff --git a/lower-constraints.txt b/lower-constraints.txt index b613ea69f0de..732e9c7e6bcc 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -70,7 +70,7 @@ os-brick==2.6.1 os-client-config==1.29.0 os-resource-classes==0.1.0 os-service-types==1.2.0 -os-traits==0.4.0 +os-traits==0.8.0 os-vif==1.14.0 os-win==3.0.0 os-xenapi==0.3.3 diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py index 47c258c6a494..2efdffab5747 100644 --- a/nova/compute/resource_tracker.py +++ b/nova/compute/resource_tracker.py @@ -917,6 +917,23 @@ class ResourceTracker(object): return True return False + def _get_traits(self, nodename, provider_tree=None): + # Get the traits from the ProviderTree which will be the set + # of virt-owned traits plus any externally defined traits set + # on the provider that aren't owned by the virt driver. + traits = provider_tree.data(nodename).traits + + # Now get the driver's capabilities and add any supported + # traits that are missing, and remove any existing set traits + # that are not currently supported. + for trait, supported in self.driver.capabilities_as_traits().items(): + if supported: + traits.add(trait) + elif trait in traits: + traits.remove(trait) + + return list(traits) + @retrying.retry(stop_max_attempt_number=4, retry_on_exception=lambda e: isinstance( e, exception.ResourceProviderUpdateConflict)) @@ -957,6 +974,21 @@ class ResourceTracker(object): self.driver.update_provider_tree(prov_tree, nodename, allocations=allocs) + # Inject driver capabilities traits into the provider + # tree. We need to determine the traits that the virt + # driver owns - so those that come from the tree itself + # (via the virt driver) plus the compute capabilities + # traits, and then merge those with the traits set + # externally that the driver does not own - and remove any + # set on the provider externally that the virt owns but + # aren't in the current list of supported traits. For + # example, let's say we reported multiattach support as a + # trait at t1 and then at t2 it's not, so we need to + # remove it. But at both t1 and t2 there is a + # CUSTOM_VENDOR_TRAIT_X which we can't touch because it + # was set externally on the provider. + traits = self._get_traits(nodename, provider_tree=prov_tree) + prov_tree.update_traits(nodename, traits) except NotImplementedError: # update_provider_tree isn't implemented yet - try get_inventory try: diff --git a/nova/tests/functional/fixtures.py b/nova/tests/functional/fixtures.py index 419debad0f14..bbabf020ce64 100644 --- a/nova/tests/functional/fixtures.py +++ b/nova/tests/functional/fixtures.py @@ -27,7 +27,8 @@ class PlacementApiClient(object): self.fixture = placement_fixture def get(self, url, **kwargs): - return client.APIResponse(self.fixture._fake_get(None, url, **kwargs)) + return client.APIResponse( + self.fixture._fake_get(None, url, **kwargs)) def put(self, url, body, **kwargs): return client.APIResponse( @@ -37,6 +38,10 @@ class PlacementApiClient(object): return client.APIResponse( self.fixture._fake_post(None, url, body, **kwargs)) + def delete(self, url, **kwargs): + return client.APIResponse( + self.fixture._fake_delete(None, url, **kwargs)) + class PlacementFixture(placement_fixtures.PlacementFixture): """A fixture to placement operations. diff --git a/nova/tests/functional/integrated_helpers.py b/nova/tests/functional/integrated_helpers.py index d67de16d95c6..6073f2e92a2d 100644 --- a/nova/tests/functional/integrated_helpers.py +++ b/nova/tests/functional/integrated_helpers.py @@ -19,9 +19,11 @@ Provides common functionality for integrated unit tests import collections import random +import six import string import time +import os_traits from oslo_log import log as logging from oslo_utils.fixture import uuidsentinel as uuids @@ -375,6 +377,32 @@ class ProviderUsageBaseTestCase(test.TestCase, InstanceHelperMixin): microversion = 'latest' + # These must match the capabilities in + # nova.virt.libvirt.driver.LibvirtDriver.capabilities + expected_libvirt_driver_capability_traits = set([ + six.u(trait) for trait in [ + os_traits.COMPUTE_DEVICE_TAGGING, + os_traits.COMPUTE_NET_ATTACH_INTERFACE, + os_traits.COMPUTE_NET_ATTACH_INTERFACE_WITH_TAG, + os_traits.COMPUTE_VOLUME_ATTACH_WITH_TAG, + os_traits.COMPUTE_VOLUME_EXTEND, + os_traits.COMPUTE_TRUSTED_CERTS, + ] + ]) + + # These must match the capabilities in + # nova.virt.fake.FakeDriver.capabilities + expected_fake_driver_capability_traits = set([ + six.u(trait) for trait in [ + os_traits.COMPUTE_NET_ATTACH_INTERFACE, + os_traits.COMPUTE_NET_ATTACH_INTERFACE_WITH_TAG, + os_traits.COMPUTE_VOLUME_ATTACH_WITH_TAG, + os_traits.COMPUTE_VOLUME_EXTEND, + os_traits.COMPUTE_VOLUME_MULTI_ATTACH, + os_traits.COMPUTE_TRUSTED_CERTS, + ] + ]) + def setUp(self): self.flags(compute_driver=self.compute_driver) super(ProviderUsageBaseTestCase, self).setUp() @@ -446,6 +474,9 @@ class ProviderUsageBaseTestCase(test.TestCase, InstanceHelperMixin): def _create_trait(self, trait): return self.placement_api.put('/traits/%s' % trait, {}, version='1.6') + def _delete_trait(self, trait): + return self.placement_api.delete('/traits/%s' % trait, version='1.6') + def _get_provider_traits(self, provider_uuid): return self.placement_api.get( '/resource_providers/%s/traits' % provider_uuid, diff --git a/nova/tests/functional/libvirt/test_report_cpu_traits.py b/nova/tests/functional/libvirt/test_report_cpu_traits.py index 205ff945ae67..7505835cea7c 100644 --- a/nova/tests/functional/libvirt/test_report_cpu_traits.py +++ b/nova/tests/functional/libvirt/test_report_cpu_traits.py @@ -39,8 +39,9 @@ class LibvirtReportTraitsTests(integrated_helpers.ProviderUsageBaseTestCase): def test_report_cpu_traits(self): # Test CPU traits reported on initial node startup, these specific # trait values are coming from fakelibvirt's baselineCPU result. - self.assertItemsEqual(['HW_CPU_X86_VMX', 'HW_CPU_X86_AESNI'], - self._get_provider_traits(self.host_uuid)) + traits = self._get_provider_traits(self.host_uuid) + for trait in ('HW_CPU_X86_VMX', 'HW_CPU_X86_AESNI'): + self.assertIn(trait, traits) self._create_trait('CUSTOM_TRAITS') new_traits = ['CUSTOM_TRAITS', 'HW_CPU_X86_AVX'] @@ -52,7 +53,8 @@ class LibvirtReportTraitsTests(integrated_helpers.ProviderUsageBaseTestCase): self._run_periodics() # HW_CPU_X86_AVX is filtered out because nova-compute owns CPU traits # and it's not in the baseline for the host. - self.assertItemsEqual( - ['HW_CPU_X86_VMX', 'HW_CPU_X86_AESNI', 'CUSTOM_TRAITS'], - self._get_provider_traits(self.host_uuid) + traits = set(self._get_provider_traits(self.host_uuid)) + expected_traits = self.expected_libvirt_driver_capability_traits.union( + [u'HW_CPU_X86_VMX', u'HW_CPU_X86_AESNI', u'CUSTOM_TRAITS'] ) + self.assertItemsEqual(expected_traits, traits) diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index 3f7dee37adba..6cbc6e3a6eec 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -23,6 +23,7 @@ import zlib from keystoneauth1 import adapter import mock +import os_traits from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import base64 @@ -1755,7 +1756,8 @@ class ProviderTreeTests(integrated_helpers.ProviderUsageBaseTestCase): 'step_size': 1, }, }, self._get_provider_inventory(self.host_uuid)) - self.assertEqual([], self._get_provider_traits(self.host_uuid)) + self.assertItemsEqual(self.expected_fake_driver_capability_traits, + self._get_provider_traits(self.host_uuid)) def _run_update_available_resource(self, startup): self.compute.rt.update_available_resource( @@ -1820,8 +1822,10 @@ class ProviderTreeTests(integrated_helpers.ProviderUsageBaseTestCase): self.assertIn('CUSTOM_BANDWIDTH', self._get_all_resource_classes()) self.assertIn('CUSTOM_GOLD', self._get_all_traits()) self.assertEqual(inv, self._get_provider_inventory(self.host_uuid)) - self.assertEqual(traits, - set(self._get_provider_traits(self.host_uuid))) + self.assertItemsEqual( + traits.union(self.expected_fake_driver_capability_traits), + self._get_provider_traits(self.host_uuid) + ) self.assertEqual(aggs, set(self._get_provider_aggregates(self.host_uuid))) @@ -2039,8 +2043,12 @@ class ProviderTreeTests(integrated_helpers.ProviderUsageBaseTestCase): 4, self._get_provider_inventory(uuids.pf2_2)['SRIOV_NET_VF']['total']) - # Compute and NUMAs don't have any traits - for uuid in (self.host_uuid, uuids.numa1, uuids.numa2): + # Compute don't have any extra traits + self.assertItemsEqual(self.expected_fake_driver_capability_traits, + self._get_provider_traits(self.host_uuid)) + + # NUMAs don't have any traits + for uuid in (uuids.numa1, uuids.numa2): self.assertEqual([], self._get_provider_traits(uuid)) def test_update_provider_tree_multiple_providers(self): @@ -2218,6 +2226,192 @@ class ProviderTreeTests(integrated_helpers.ProviderUsageBaseTestCase): ]) +class TraitsTrackingTests(integrated_helpers.ProviderUsageBaseTestCase): + compute_driver = 'fake.SmallFakeDriver' + + fake_caps = { + 'supports_attach_interface': True, + 'supports_device_tagging': False, + } + + def _mock_upt(self, traits_to_add, traits_to_remove): + """Set up the compute driver with a fake update_provider_tree() + which injects the given traits into the provider tree + """ + original_upt = fake.SmallFakeDriver.update_provider_tree + + def fake_upt(self2, ptree, nodename, allocations=None): + original_upt(self2, ptree, nodename, allocations) + LOG.debug("injecting traits via fake update_provider_tree(): %s", + traits_to_add) + ptree.add_traits(nodename, *traits_to_add) + LOG.debug("removing traits via fake update_provider_tree(): %s", + traits_to_remove) + ptree.remove_traits(nodename, *traits_to_remove) + + self.stub_out('nova.virt.fake.FakeDriver.update_provider_tree', + fake_upt) + + @mock.patch.dict(fake.SmallFakeDriver.capabilities, clear=True, + values=fake_caps) + def test_resource_provider_traits(self): + """Test that the compute service reports traits via driver + capabilities and registers them on the compute host resource + provider in the placement API. + """ + custom_trait = 'CUSTOM_FOO' + ptree_traits = [custom_trait, 'HW_CPU_X86_VMX'] + + global_traits = self._get_all_traits() + self.assertNotIn(custom_trait, global_traits) + self.assertIn(os_traits.COMPUTE_NET_ATTACH_INTERFACE, global_traits) + self.assertIn(os_traits.COMPUTE_DEVICE_TAGGING, global_traits) + self.assertEqual([], self._get_all_providers()) + + self._mock_upt(ptree_traits, []) + + self.compute = self._start_compute(host='host1') + + rp_uuid = self._get_provider_uuid_by_host('host1') + expected_traits = set( + ptree_traits + [os_traits.COMPUTE_NET_ATTACH_INTERFACE] + ) + self.assertItemsEqual(expected_traits, + self._get_provider_traits(rp_uuid)) + global_traits = self._get_all_traits() + # CUSTOM_FOO is now a registered trait because the virt driver + # reported it. + self.assertIn(custom_trait, global_traits) + + # Now simulate user deletion of driver-provided traits from + # the compute node provider. + expected_traits.remove(custom_trait) + expected_traits.remove(os_traits.COMPUTE_NET_ATTACH_INTERFACE) + self._set_provider_traits(rp_uuid, list(expected_traits)) + self.assertItemsEqual(expected_traits, + self._get_provider_traits(rp_uuid)) + + # The above trait deletions are simulations of an out-of-band + # placement operation, as if the operator used the CLI. So + # now we have to "SIGHUP the compute process" to clear the + # report client cache so the subsequent update picks up the + # changes. + self.compute.manager.reset() + + # Add the traits back so that the mock update_provider_tree() + # can reinject them. + expected_traits.update( + [custom_trait, os_traits.COMPUTE_NET_ATTACH_INTERFACE]) + + # Now when we run the periodic update task, the trait should + # reappear in the provider tree and get synced back to + # placement. + self._run_periodics() + + self.assertItemsEqual(expected_traits, + self._get_provider_traits(rp_uuid)) + global_traits = self._get_all_traits() + self.assertIn(custom_trait, global_traits) + self.assertIn(os_traits.COMPUTE_NET_ATTACH_INTERFACE, global_traits) + + @mock.patch.dict(fake.SmallFakeDriver.capabilities, clear=True, + values=fake_caps) + def test_admin_traits_preserved(self): + """Test that if admin externally sets traits on the resource provider + then the compute periodic doesn't remove them from placement. + """ + admin_trait = 'CUSTOM_TRAIT_FROM_ADMIN' + self._create_trait(admin_trait) + global_traits = self._get_all_traits() + self.assertIn(admin_trait, global_traits) + + self.compute = self._start_compute(host='host1') + rp_uuid = self._get_provider_uuid_by_host('host1') + traits = self._get_provider_traits(rp_uuid) + traits.append(admin_trait) + self._set_provider_traits(rp_uuid, traits) + self.assertIn(admin_trait, self._get_provider_traits(rp_uuid)) + + # SIGHUP the compute process to clear the report client + # cache, so the subsequent periodic update recalculates everything. + self.compute.manager.reset() + + self._run_periodics() + self.assertIn(admin_trait, self._get_provider_traits(rp_uuid)) + + @mock.patch.dict(fake.SmallFakeDriver.capabilities, clear=True, + values=fake_caps) + def test_driver_removing_support_for_trait_via_capability(self): + """Test that if a driver initially reports a trait via a supported + capability, then at the next periodic update doesn't report + support for it again, it gets removed from the provider in the + placement service. + """ + self.compute = self._start_compute(host='host1') + rp_uuid = self._get_provider_uuid_by_host('host1') + trait = os_traits.COMPUTE_NET_ATTACH_INTERFACE + self.assertIn(trait, self._get_provider_traits(rp_uuid)) + + new_caps = dict(fake.SmallFakeDriver.capabilities, + **{'supports_attach_interface': False}) + with mock.patch.dict(fake.SmallFakeDriver.capabilities, new_caps): + self._run_periodics() + + self.assertNotIn(trait, self._get_provider_traits(rp_uuid)) + + def test_driver_removing_trait_via_upt(self): + """Test that if a driver reports a trait via update_provider_tree() + initially, but at the next periodic update doesn't report it + again, that it gets removed from placement. + """ + custom_trait = "CUSTOM_TRAIT_FROM_DRIVER" + standard_trait = os_traits.HW_CPU_X86_SGX + self._mock_upt([custom_trait, standard_trait], []) + + self.compute = self._start_compute(host='host1') + rp_uuid = self._get_provider_uuid_by_host('host1') + self.assertIn(custom_trait, self._get_provider_traits(rp_uuid)) + self.assertIn(standard_trait, self._get_provider_traits(rp_uuid)) + + # Now change the fake update_provider_tree() from injecting the + # traits to removing them, and run the periodic update. + self._mock_upt([], [custom_trait, standard_trait]) + self._run_periodics() + + self.assertNotIn(custom_trait, self._get_provider_traits(rp_uuid)) + self.assertNotIn(standard_trait, self._get_provider_traits(rp_uuid)) + + @mock.patch.dict(fake.SmallFakeDriver.capabilities, clear=True, + values=fake_caps) + def test_driver_removes_unsupported_trait_from_admin(self): + """Test that if an admin adds a trait corresponding to a + capability which is unsupported, then if the provider cache is + reset, the driver will remove it during the next update. + """ + self.compute = self._start_compute(host='host1') + rp_uuid = self._get_provider_uuid_by_host('host1') + + traits = self._get_provider_traits(rp_uuid) + trait = os_traits.COMPUTE_DEVICE_TAGGING + self.assertNotIn(trait, traits) + + # Simulate an admin associating the trait with the host via + # the placement API. + traits.append(trait) + self._set_provider_traits(rp_uuid, traits) + + # Check that worked. + traits = self._get_provider_traits(rp_uuid) + self.assertIn(trait, traits) + + # SIGHUP the compute process to clear the report client + # cache, so the subsequent periodic update recalculates everything. + self.compute.manager.reset() + + self._run_periodics() + self.assertNotIn(trait, self._get_provider_traits(rp_uuid)) + + class ServerMovingTests(integrated_helpers.ProviderUsageBaseTestCase): """Tests moving servers while checking the resource allocations and usages diff --git a/nova/tests/unit/compute/test_resource_tracker.py b/nova/tests/unit/compute/test_resource_tracker.py index 4bcb2a27805b..a592fd9e913c 100644 --- a/nova/tests/unit/compute/test_resource_tracker.py +++ b/nova/tests/unit/compute/test_resource_tracker.py @@ -485,6 +485,25 @@ class BaseTestCase(test.NoDBTestCase): self.driver_mock) = setup_rt( _HOSTNAME, virt_resources, estimate_overhead) + def _setup_ptree(self, compute): + """Set up a ProviderTree with a compute node root, and mock the + ReportClient's get_provider_tree_and_ensure_root() to return + it. + + update_traits() is mocked so that tests can specify a return + value. Returns the new ProviderTree so that tests can control + its behaviour further. + """ + ptree = provider_tree.ProviderTree() + ptree.new_root(compute.hypervisor_hostname, compute.uuid) + ptree.update_traits = mock.Mock() + + rc_mock = self.rt.reportclient + gptaer_mock = rc_mock.get_provider_tree_and_ensure_root + gptaer_mock.return_value = ptree + + return ptree + class TestUpdateAvailableResources(BaseTestCase): @@ -1395,6 +1414,34 @@ class TestUpdateComputeNode(BaseTestCase): save_mock.assert_called_once_with() norm_mock.assert_called_once_with(mock.sentinel.inv_data, new_compute) + def test_existing_node_capabilities_as_traits(self): + """The capabilities_as_traits() driver method returns traits + information for a node/provider. + """ + self._setup_rt() + rc = self.rt.reportclient + rc.set_traits_for_provider = mock.MagicMock() + + # Emulate a driver that has implemented the update_from_provider_tree() + # virt driver method + self.driver_mock.update_provider_tree = mock.Mock() + self.driver_mock.capabilities_as_traits.return_value = \ + {mock.sentinel.trait: True} + + orig_compute = _COMPUTE_NODE_FIXTURES[0].obj_clone() + self.rt.compute_nodes[_NODENAME] = orig_compute + self.rt.old_resources[_NODENAME] = orig_compute + new_compute = orig_compute.obj_clone() + + ptree = self._setup_ptree(orig_compute) + + self.rt._update(mock.sentinel.ctx, new_compute) + self.driver_mock.capabilities_as_traits.assert_called_once() + ptree.update_traits.assert_called_once_with( + new_compute.hypervisor_hostname, + [mock.sentinel.trait] + ) + @mock.patch('nova.objects.ComputeNode.save') def test_existing_node_update_provider_tree_implemented(self, save_mock): """The update_provider_tree() virt driver method is only implemented @@ -1451,23 +1498,24 @@ class TestUpdateComputeNode(BaseTestCase): new_compute = orig_compute.obj_clone() new_compute.local_gb = 210000 - rc_mock = self.rt.reportclient - gptaer_mock = rc_mock.get_provider_tree_and_ensure_root - ptree = provider_tree.ProviderTree() - ptree.new_root(orig_compute.hypervisor_hostname, orig_compute.uuid) - gptaer_mock.return_value = ptree + ptree = self._setup_ptree(orig_compute) self.rt._update(mock.sentinel.ctx, new_compute) save_mock.assert_called_once_with() + gptaer_mock = self.rt.reportclient.get_provider_tree_and_ensure_root gptaer_mock.assert_called_once_with( mock.sentinel.ctx, new_compute.uuid, name=new_compute.hypervisor_hostname) self.driver_mock.update_provider_tree.assert_called_once_with( ptree, new_compute.hypervisor_hostname) - rc_mock.update_from_provider_tree.assert_called_once_with( + self.rt.reportclient.update_from_provider_tree.assert_called_once_with( mock.sentinel.ctx, ptree, allocations=None) self.driver_mock.get_inventory.assert_not_called() + ptree.update_traits.assert_called_once_with( + new_compute.hypervisor_hostname, + [] + ) exp_inv = copy.deepcopy(fake_inv) # These ratios and reserved amounts come from fake_upt exp_inv[orc.VCPU]['allocation_ratio'] = 16.0 diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 36c7653d5403..ebeae6076397 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -23,6 +23,7 @@ Driver base-classes: import sys import os_resource_classes as orc +import os_traits from oslo_log import log as logging from oslo_utils import importutils import six @@ -91,6 +92,27 @@ def block_device_info_get_mapping(block_device_info): return block_device_mapping +# NOTE(aspiers): When adding new capabilities, ensure they are +# mirrored in ComputeDriver.capabilities, and that the corresponding +# values should always be standard traits in os_traits. If something +# isn't a standard trait, it doesn't need to be a compute node +# capability trait; and if it needs to be a compute node capability +# trait, it needs to be (made) standard, and must be prefixed with +# "COMPUTE_". +CAPABILITY_TRAITS_MAP = { + # Added in os-traits 0.7.0. + "supports_attach_interface": os_traits.COMPUTE_NET_ATTACH_INTERFACE, + "supports_device_tagging": os_traits.COMPUTE_DEVICE_TAGGING, + "supports_tagged_attach_interface": + os_traits.COMPUTE_NET_ATTACH_INTERFACE_WITH_TAG, + "supports_tagged_attach_volume": os_traits.COMPUTE_VOLUME_ATTACH_WITH_TAG, + "supports_extend_volume": os_traits.COMPUTE_VOLUME_EXTEND, + "supports_multiattach": os_traits.COMPUTE_VOLUME_MULTI_ATTACH, + # Added in os-traits 0.8.0. + "supports_trusted_certs": os_traits.COMPUTE_TRUSTED_CERTS +} + + class ComputeDriver(object): """Base class for compute drivers. @@ -122,6 +144,9 @@ class ComputeDriver(object): """ + # NOTE(mriedem): When adding new capabilities, consider whether they + # should also be added to CAPABILITY_TRAITS_MAP; if so, any new traits + # must also be added to the os-traits library. capabilities = { "has_imagecache": False, "supports_evacuate": False, @@ -996,6 +1021,23 @@ class ComputeDriver(object): """ raise NotImplementedError() + def capabilities_as_traits(self): + """Returns this driver's capabilities dict where the keys are traits + + Traits can only be standard compute capabilities traits from + the os-traits library. + + :returns: dict, keyed by trait, of this driver's capabilities where the + values are booleans indicating if the driver supports the trait + + """ + traits = {} + for capability, supported in self.capabilities.items(): + if capability in CAPABILITY_TRAITS_MAP: + traits[CAPABILITY_TRAITS_MAP[capability]] = supported + + return traits + def get_available_resource(self, nodename): """Retrieve resource information. diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 62143b6f4b7a..655dd6c5ce82 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -123,6 +123,8 @@ class Resources(object): class FakeDriver(driver.ComputeDriver): + # These must match the traits in + # nova.tests.functional.integrated_helpers.ProviderUsageBaseTestCase capabilities = { "has_imagecache": True, "supports_evacuate": True, diff --git a/releasenotes/notes/driver-capabilities-to-traits-152eb851cd016f4d.yaml b/releasenotes/notes/driver-capabilities-to-traits-152eb851cd016f4d.yaml new file mode 100644 index 000000000000..da60ed4a1246 --- /dev/null +++ b/releasenotes/notes/driver-capabilities-to-traits-152eb851cd016f4d.yaml @@ -0,0 +1,26 @@ +--- +features: + - | + Compute drivers now expose capabilities via traits in the + Placement API. Capabilities must map to standard traits defined + in `the os-traits project + `_; for now these + are: + + * ``COMPUTE_NET_ATTACH_INTERFACE`` + * ``COMPUTE_DEVICE_TAGGING`` + * ``COMPUTE_NET_ATTACH_INTERFACE_WITH_TAG`` + * ``COMPUTE_VOLUME_ATTACH_WITH_TAG`` + * ``COMPUTE_VOLUME_EXTEND`` + * ``COMPUTE_VOLUME_MULTI_ATTACH`` + * ``COMPUTE_TRUSTED_CERTS`` + + Any traits provided by the driver will be automatically added + during startup or a periodic update of a compute node. Similarly + any traits later retracted by the driver will be automatically + removed. + + However any traits which are removed by the admin from the compute + node resource provider via the Placement API will not be + reinstated until the compute service's provider cache is reset. + This can be triggered via a ``SIGHUP``. diff --git a/requirements.txt b/requirements.txt index 62a82de20dae..aca623599e1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -57,7 +57,7 @@ psutil>=3.2.2 # BSD oslo.versionedobjects>=1.33.3 # Apache-2.0 os-brick>=2.6.1 # Apache-2.0 os-resource-classes>=0.1.0 # Apache-2.0 -os-traits>=0.4.0 # Apache-2.0 +os-traits>=0.8.0 # Apache-2.0 os-vif>=1.14.0 # Apache-2.0 os-win>=3.0.0 # Apache-2.0 castellan>=0.16.0 # Apache-2.0