Merge "Implement mechanism for plugin node attributes"

This commit is contained in:
Jenkins 2016-10-25 09:09:41 +00:00 committed by Gerrit Code Review
commit 1a8eddf49e
17 changed files with 884 additions and 42 deletions

View File

@ -292,3 +292,20 @@ class NodeAttributesHandler(BaseHandler):
objects.Node.update_attributes(node, data)
return objects.Node.get_attributes(node)
class NodeAttributesDefaultsHandler(BaseHandler):
"""Node default attributes handler"""
@handle_errors
@validate
@serialize
def GET(self, node_id):
""":returns: JSONized Node default attributes.
:http: * 200 (OK)
* 404 (node not found in db)
"""
node = self.get_object_or_404(objects.Node, node_id)
return objects.Node.get_default_attributes(node)

View File

@ -64,10 +64,12 @@ from nailgun.api.v1.handlers.logs import LogPackageHandler
from nailgun.api.v1.handlers.logs import LogSourceByNodeCollectionHandler
from nailgun.api.v1.handlers.logs import LogSourceCollectionHandler
from nailgun.api.v1.handlers.logs import SnapshotDownloadHandler
from nailgun.api.v1.handlers.node_group import NodeGroupCollectionHandler
from nailgun.api.v1.handlers.node_group import NodeGroupHandler
from nailgun.api.v1.handlers.node import NodeAgentHandler
from nailgun.api.v1.handlers.node import NodeAttributesDefaultsHandler
from nailgun.api.v1.handlers.node import NodeAttributesHandler
from nailgun.api.v1.handlers.node import NodeCollectionHandler
from nailgun.api.v1.handlers.node import NodeHandler
@ -79,6 +81,7 @@ from nailgun.api.v1.handlers.plugin import \
from nailgun.api.v1.handlers.plugin import PluginDeploymentGraphHandler
from nailgun.api.v1.handlers.plugin import PluginHandler
from nailgun.api.v1.handlers.plugin import PluginSyncHandler
from nailgun.api.v1.handlers.plugin_link import PluginLinkCollectionHandler
from nailgun.api.v1.handlers.plugin_link import PluginLinkHandler
@ -117,13 +120,13 @@ from nailgun.api.v1.handlers.tag import TagOwnerHandler
from nailgun.api.v1.handlers.tasks import TaskCollectionHandler
from nailgun.api.v1.handlers.tasks import TaskHandler
from nailgun.api.v1.handlers.transactions import TransactionClusterSettings
from nailgun.api.v1.handlers.transactions import TransactionCollectionHandler
from nailgun.api.v1.handlers.transactions import TransactionDeploymentInfo
from nailgun.api.v1.handlers.transactions import TransactionHandler
from nailgun.api.v1.handlers.transactions import TransactionNetworkSettings
from nailgun.api.v1.handlers.version import VersionHandler
from nailgun.api.v1.handlers.vms import NodeVMsHandler
@ -291,6 +294,8 @@ urls = (
NodeHandler,
r'/nodes/(?P<node_id>\d+)/attributes/?$',
NodeAttributesHandler,
r'/nodes/(?P<node_id>\d+)/attributes/defaults/?$',
NodeAttributesDefaultsHandler,
r'/nodes/allocation/stats/?$',
NodesAllocationStatsHandler,
r'/nodes/(?P<node_id>\d+)/tags/?$',

View File

@ -453,7 +453,7 @@ class NodeAttributesValidator(base.BasicAttributesValidator):
data = cls.validate_json(data)
full_data = utils.dict_merge(objects.Node.get_attributes(node), data)
models = objects.Cluster.get_restrictions_models(cluster)
models = objects.Node.get_restrictions_models(node)
attrs = cls.validate_attributes(full_data, models=models)

View File

@ -83,6 +83,7 @@ from nailgun.objects.node_group import NodeGroupCollection
from nailgun.objects.plugin import Plugin
from nailgun.objects.plugin import PluginCollection
from nailgun.objects.plugin import ClusterPlugin
from nailgun.objects.plugin import NodeClusterPlugin
from nailgun.objects.plugin_link import PluginLink
from nailgun.objects.plugin_link import PluginLinkCollection

View File

@ -56,6 +56,7 @@ from nailgun.objects import Notification
from nailgun.objects import Release
from nailgun.objects.serializers.node import NodeSerializer
from nailgun.objects import TagCollection
from nailgun.plugins.manager import PluginManager
from nailgun.policy import cpu_distribution
from nailgun.policy import hugepages_distribution
from nailgun.settings import settings
@ -1248,6 +1249,17 @@ class Node(NailgunObject):
hostname = 'node-{0}'.format(node.uuid)
return hostname
@classmethod
def get_restrictions_models(cls, instance):
"""Return models which are used in restrictions mechanism
:param instance: nailgun.db.sqlalchemy.models.Node instance
:return: dict with models
"""
models = {'node_attributes': cls.get_attributes(instance)}
models.update(Cluster.get_restrictions_models(instance.cluster))
return models
@classmethod
def reset_vms_created_state(cls, node):
if consts.VIRTUAL_NODE_TYPES.virt not in node.all_roles:
@ -1269,6 +1281,7 @@ class Node(NailgunObject):
instance.attributes = copy.deepcopy(
instance.cluster.release.node_attributes)
NodeAttributes.set_default_hugepages(instance)
PluginManager.add_plugin_attributes_for_node(instance)
@classmethod
def dpdk_enabled(cls, instance):
@ -1286,12 +1299,37 @@ class Node(NailgunObject):
@classmethod
def get_attributes(cls, instance):
return copy.deepcopy(instance.attributes)
attributes = copy.deepcopy(instance.attributes)
attributes.update(PluginManager.get_plugin_node_attributes(instance))
return attributes
@classmethod
def update_attributes(cls, instance, attrs):
PluginManager.update_plugin_node_attributes(attrs)
instance.attributes = utils.dict_merge(instance.attributes, attrs)
@classmethod
def get_default_attributes(cls, instance):
"""Get default attributes for Node.
:param instance: Node instance
:type instance: models.Node
:returns: dict -- Dict object of Node attributes
"""
if not instance.cluster_id:
logger.warning(
u"Attempting to update attributes of node "
u"'{0}' which isn't added to any cluster".format(
instance.full_name))
return {}
cluster = instance.cluster
attributes = copy.deepcopy(instance.cluster.release.node_attributes)
attributes.update(
PluginManager.get_plugins_node_default_attributes(cluster))
return attributes
@classmethod
def refresh_dpdk_properties(cls, instance):
if not instance.cluster:

View File

@ -253,12 +253,15 @@ class ClusterPlugin(NailgunObject):
plugin_attributes = dict(plugin.attributes_metadata)
plugin_attributes.pop('metadata', None)
for cluster in cls.get_compatible_clusters(plugin):
cls.create({
cluster_plugin = cls.create({
'cluster_id': cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': plugin_attributes
})
NodeClusterPlugin.add_nodes_for_cluster_plugin(cluster_plugin)
db().flush()
@classmethod
def set_attributes(cls, cluster_id, plugin_id, enabled=None, attrs=None):
@ -372,3 +375,105 @@ class ClusterPlugin(NailgunObject):
.filter(cls.model.enabled.is_(True))
return db().query(q.exists()).scalar()
class BasicNodeClusterPlugin(NailgunObject):
@classmethod
def set_attributes(cls, instance_id, attrs=None):
"""Update plugin NIC|Bond|Node attributes
:param instance_id: NIC|Bond|Node instance id
:type instance: int
:returns: None
"""
if attrs:
db().query(cls.model) \
.filter_by(
id=instance_id) \
.update({'attributes': attrs}, synchronize_session='fetch')
db().flush()
class NodeClusterPlugin(BasicNodeClusterPlugin):
model = models.NodeClusterPlugin
@classmethod
def get_all_enabled_attributes_by_node(cls, node):
"""Returns node attributes from enabled plugins
:param node: target node instance
:type node: models.Node
:returns: object with plugin Node attributes
:rtype: dict
"""
node_attributes = {}
node_plugin_attributes_query = db().query(
cls.model.id,
cls.model.attributes
).join(
models.ClusterPlugin,
models.Plugin
).filter(
cls.model.node_id == node.id,
models.ClusterPlugin.enabled.is_(True)
)
for node_plugin_id, attributes in node_plugin_attributes_query:
for section_name, section_attributes in six.iteritems(attributes):
# TODO(apopovych): resolve conflicts of same attribute names
# for different plugins
section_attributes.setdefault('metadata', {}).update({
'node_plugin_id': node_plugin_id,
'class': 'plugin'
})
node_attributes[section_name] = section_attributes
return node_attributes
@classmethod
def add_nodes_for_cluster_plugin(cls, cluster_plugin):
"""Populates 'node_cluster_plugins' table with nodes.
:param cluster_plugin: ClusterPlugin instance
:type cluster_plugin: models.ClusterPlugin
:returns: None
"""
node_attributes = dict(
cluster_plugin.plugin.node_attributes_metadata)
for node in cluster_plugin.cluster.nodes:
if node_attributes:
cls.create({
'cluster_plugin_id': cluster_plugin.id,
'node_id': node.id,
'attributes': node_attributes
})
db().flush()
@classmethod
def add_cluster_plugins_for_node(cls, node):
"""Populates 'node_cluster_plugins' table.
:param node: target node instance
:type node: models.Node
"""
node_cluster_plugin_ids = set(
item.id for item in node.node_cluster_plugins)
# TODO(ekosareva): rethink, move it in another place
# remove old relations for nodes
cls.bulk_delete(node_cluster_plugin_ids)
for cluster_plugin in node.cluster.cluster_plugins:
node_attributes = dict(
cluster_plugin.plugin.node_attributes_metadata)
if node_attributes:
cls.create({
'cluster_plugin_id': cluster_plugin.id,
'node_id': node.id,
'attributes': node_attributes
})
db().flush()

View File

@ -824,6 +824,21 @@ class DeploymentLCMSerializer(DeploymentHASerializer90):
)
utils.dict_update(data.setdefault('provision', {}), info)
@classmethod
def serialize_node_for_node_list(cls, node, role):
serialized_node = super(
DeploymentLCMSerializer,
cls).serialize_node_for_node_list(node, role)
for section_name, section_attributes in six.iteritems(
plugins.manager.PluginManager.
get_plugin_node_attributes(node)):
section_attributes.pop('metadata', None)
serialized_node[section_name] = {
k: v.get('value') for k, v in six.iteritems(section_attributes)
}
return serialized_node
def get_serializer_for_cluster(cluster):
"""Returns a serializer depends on a given `cluster`.

View File

@ -28,6 +28,7 @@ from nailgun import consts
from nailgun import errors
from nailgun.logger import logger
from nailgun.objects.plugin import ClusterPlugin
from nailgun.objects.plugin import NodeClusterPlugin
from nailgun.objects.plugin import Plugin
from nailgun.objects.plugin import PluginCollection
from nailgun.settings import settings
@ -409,6 +410,69 @@ class PluginManager(object):
return components
@classmethod
def get_plugins_node_default_attributes(cls, cluster):
"""Get node attributes metadata for enabled plugins of the cluster.
:param cluster: A cluster instance
:type cluster: models.Cluster
:returns: dict -- Object with node attributes
"""
plugins_node_metadata = {}
enabled_plugins = ClusterPlugin.get_enabled(cluster.id)
for plugin_adapter in map(wrap_plugin, enabled_plugins):
metadata = plugin_adapter.node_attributes_metadata
# TODO(ekosareva): resolve conflicts of same attribute names
# for different plugins
plugins_node_metadata.update(metadata)
return plugins_node_metadata
@classmethod
def get_plugin_node_attributes(cls, node):
"""Return plugin related attributes for Node.
:param node: A Node instance
:type node: models.Node
:returns: dict object with plugin Node attributes
"""
return NodeClusterPlugin.get_all_enabled_attributes_by_node(node)
@classmethod
def update_plugin_node_attributes(cls, attributes):
"""Update plugin related node attributes.
:param attributes: new attributes data
:type attributes: dict
:returns: None
"""
plugins_attributes = {}
for k in list(attributes):
if cls.is_plugin_data(attributes[k]):
attribute_data = attributes.pop(k)
attribute_data['metadata'].pop('class')
node_plugin_id = \
attribute_data['metadata'].pop('node_plugin_id')
plugins_attributes.setdefault(
node_plugin_id, {}).update({k: attribute_data})
# TODO(ekosareva): think about changed metadata or sections set
for plugin_id, plugin_attributes in six.iteritems(plugins_attributes):
NodeClusterPlugin.set_attributes(
plugin_id,
plugin_attributes
)
@classmethod
def add_plugin_attributes_for_node(cls, node):
"""Add plugin related attributes for Node.
:param node: A Node instance
:type node: models.Node
:returns: None
"""
NodeClusterPlugin.add_cluster_plugins_for_node(node)
# ENTRY POINT
@classmethod
def sync_plugins_metadata(cls, plugin_ids=None):

View File

@ -803,12 +803,17 @@ class EnvironmentManager(object):
def get_default_plugin_node_config(self, **kwargs):
node_attributes = {
'plugin_name_text': {
'value': 'value',
'type': 'text',
'description': 'Some description',
'weight': 25,
'label': 'label'
'plugin_a_section': {
'metadata': {
'label': 'Plugin A Section'
},
'plugin_attr_key': {
'value': 'plugin_attr_val',
'type': 'text',
'description': 'Some description',
'weight': 25,
'label': 'label'
}
}
}
@ -927,6 +932,10 @@ class EnvironmentManager(object):
'version': 'mitaka-9.0', 'os': 'ubuntu',
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'},
{'repository_path': 'repositories/ubuntu',
'version': 'newton-10.0', 'os': 'ubuntu',
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}
]
}

View File

@ -410,8 +410,8 @@ class TestHandlers(BaseIntegrationTest):
if net['name'] == consts.NETWORKS.fuelweb_admin), None))
def test_get_node_attributes(self):
node = self.env.create_node(api=False)
fake_attributes = {
fake_plugin_attributes = self.env.get_default_plugin_node_config()
fake_release_attributes = {
'group1': {
'metadata': {},
'comp1': {
@ -419,17 +419,39 @@ class TestHandlers(BaseIntegrationTest):
}
}
}
node.attributes.update(fake_attributes)
cluster = self.env.create(
release_kwargs={
'version': 'newton-10.0',
'operating_system': 'Ubuntu',
'node_attributes': fake_release_attributes
},
nodes_kwargs=[
{'role': 'controller'}
]
)
node = self.env.nodes[-1]
plugin = self.env.create_plugin(
name='plugin_a',
cluster=cluster,
package_version='5.0.0',
node_attributes_metadata=fake_plugin_attributes)
resp = self.app.get(
reverse('NodeAttributesHandler', kwargs={'node_id': node.id}),
headers=self.default_headers)
self.assertEqual(200, resp.status_code)
self.assertEqual(fake_attributes, resp.json_body)
fake_plugin_attributes['plugin_a_section']['metadata'].update({
'class': 'plugin',
'node_plugin_id': [
item.id for item in node.node_cluster_plugins if
item.cluster_plugin_id == plugin.cluster_plugins[0].id][0]
})
fake_release_attributes.update(fake_plugin_attributes)
self.assertDictEqual(fake_release_attributes, resp.json_body)
def test_put_node_attributes(self):
self.env.create(nodes_kwargs=[{}])
node = self.env.nodes[-1]
fake_attributes = {
fake_release_attributes = {
'group1': {
'metadata': {},
'comp1': {
@ -451,20 +473,148 @@ class TestHandlers(BaseIntegrationTest):
},
},
}
node.attributes.update(fake_attributes)
cluster = self.env.create(
release_kwargs={
'version': 'newton-10.0',
'operating_system': 'Ubuntu',
'node_attributes': fake_release_attributes
},
nodes_kwargs=[
{'role': 'controller'}
]
)
node = self.env.nodes[0]
plugin = self.env.create_plugin(
name='plugin_a',
cluster=cluster,
package_version='5.0.0',
node_attributes_metadata={
'plugin_a_section': {
'metadata': {
'label': 'Section A'
},
'plugin_attr_key_1': {
'type': 'checkbox',
'value': True
},
'plugin_attr_key_2': {
'type': 'text',
'value': 'plugin_attr_val',
'restrictions': [{
'condition': 'node_attributes:plugin_a_section.'
'plugin_attr_key_2 == false',
'action': 'hide'
}]
}
}
})
node_cluster_plugin_id = [
item.id for item in node.node_cluster_plugins if
item.cluster_plugin_id == plugin.cluster_plugins[0].id][0]
update_attributes = {
'group1': {
'comp1': {
'type': 'text',
'value': '41'
}
},
'plugin_a_section': {
'plugin_attr_key_1': {
'type': 'checkbox',
'value': True
},
'plugin_attr_key_2': {
'type': 'text',
'value': 'new_plugin_attr_val',
'restrictions': [{
'condition': 'node_attributes:plugin_a_section.'
'plugin_attr_key_2 == false',
'action': 'hide'
}]
},
'metadata': {
'class': 'plugin',
'label': 'Section A',
'node_plugin_id': node_cluster_plugin_id
}
}
}
resp = self.app.put(
reverse('NodeAttributesHandler', kwargs={'node_id': node.id}),
jsonutils.dumps(update_attributes),
headers=self.default_headers)
fake_attributes['group1']['comp1']['value'] = '41'
fake_release_attributes['group1']['comp1']['value'] = '41'
fake_release_attributes['plugin_a_section'] = {
'plugin_attr_key_1': {
'type': 'checkbox',
'value': True
},
'plugin_attr_key_2': {
'type': 'text',
'value': 'new_plugin_attr_val',
'restrictions': [{
'condition': 'node_attributes:plugin_a_section.'
'plugin_attr_key_2 == false',
'action': 'hide'
}]
},
'metadata': {
'class': 'plugin',
'label': 'Section A',
'node_plugin_id': node_cluster_plugin_id
}
}
self.assertEqual(200, resp.status_code)
self.assertEqual(fake_attributes, resp.json_body)
self.assertDictEqual(fake_release_attributes, resp.json_body)
class TestNodeAttributesDefaultsHandler(BaseIntegrationTest):
def setUp(self):
super(TestNodeAttributesDefaultsHandler, self).setUp()
self.node_attributes = {'test_attr_key': 'test_attr_val'}
self.cluster = self.env.create(
release_kwargs={
'version': 'newton-10.0',
'operating_system': 'Ubuntu',
'node_attributes': self.node_attributes
},
nodes_kwargs=[
{'role': 'controller'}
]
)
self.node = self.env.nodes[0]
def test_get_node_default_attributes(self):
resp = self.app.get(
reverse(
'NodeAttributesDefaultsHandler',
kwargs={'node_id': self.node.id}),
headers=self.default_headers)
self.assertEqual(200, resp.status_code)
self.assertEqual(self.node_attributes, resp.json_body)
def test_get_node_default_attributes_with_enabled_plugin(self):
self.env.create_plugin(
name='plugin_a',
cluster=self.cluster,
package_version='5.0.0',
node_attributes_metadata={
'plugin_section': {'plugin_attr_key': 'plugin_attr_val'}
})
resp = self.app.get(
reverse(
'NodeAttributesDefaultsHandler',
kwargs={'node_id': self.node.id}),
headers=self.default_headers)
expected_attributes = {
'test_attr_key': 'test_attr_val',
'plugin_section': {'plugin_attr_key': 'plugin_attr_val'}
}
self.assertEqual(200, resp.status_code)
self.assertDictEqual(expected_attributes, resp.json_body)

View File

@ -650,6 +650,40 @@ class TestDeploymentLCMSerialization90(
if x['uid'] == self.node.uid)
self.assertNotIn('deleted', node_info)
def test_plugin_node_attributes_serialization(self):
node = self.env.create_node(
cluster_id=self.cluster_db.id,
roles=['compute']
)
self.env.create_plugin(
name='test_plugin',
package_version='5.0.0',
cluster=self.cluster,
node_attributes_metadata={
'test_plugin_section': {
'attribute_a': {
'label': 'Node attribute A',
'value': 'attribute_a_val'
},
'attribute_b': {
'label': 'Node attribute B',
'value': 'attribute_b_val'
}
}
}
)
objects.Cluster.prepare_for_deployment(self.cluster_db)
serialized_for_astute = self.serializer.serialize(
self.cluster_db, [node])['common']['nodes'][0]
self.assertIn('test_plugin_section', serialized_for_astute)
self.assertDictEqual(
{
'attribute_a': 'attribute_a_val',
'attribute_b': 'attribute_b_val'
},
serialized_for_astute.get('test_plugin_section', {})
)
class TestDeploymentHASerializer90(
TestSerializer90Mixin,

View File

@ -567,3 +567,148 @@ class TestClusterPluginIntegration(base.BaseTestCase):
enabled_plugins = ClusterPlugin.get_enabled(self.cluster.id)
self.assertItemsEqual(enabled_plugins, [plugin_a])
class TestNodeClusterPluginIntegration(base.BaseTestCase):
def setUp(self):
super(TestNodeClusterPluginIntegration, self).setUp()
self.cluster = self.env.create(
release_kwargs={
'version': 'newton-10.0',
'operating_system': 'Ubuntu',
},
nodes_kwargs=[
{'role': 'controller'}
]
)
self.node = self.env.nodes[0]
self.plugin = self.env.create_plugin(
name='plugin_a',
cluster=self.cluster,
package_version='5.0.0',
enabled=True,
title='Plugin A Title',
node_attributes_metadata={
'plugin_a_section_1': {
'metadata': {'label': 'Section 1 of Plugin A'},
'attr_1': {'value': 'test_1'}
},
'plugin_a_section_2': {
'attr_2': {'value': 'test_2'}
}
})
def test_get_node_default_attributes(self):
self.env.create_plugin(
name='plugin_b',
cluster=self.cluster,
enabled=True,
package_version='5.0.0',
node_attributes_metadata={
'section_plugin_b': {
'attr_b': {'value': 'test_b'}
}
})
self.env.create_plugin(
name='plugin_c',
cluster=self.cluster,
enabled=False,
package_version='5.0.0',
node_attributes_metadata={
'plugin_c_section': {
'attr_c': {'value': 'test_c'}
}
})
for node_cluster_plugin in self.cluster.nodes[0].node_cluster_plugins:
node_cluster_plugin.attributes = {}
self.db.flush()
default_attributes = PluginManager.get_plugins_node_default_attributes(
self.cluster)
self.assertDictEqual(
{
'plugin_a_section_1': {
'metadata': {'label': 'Section 1 of Plugin A'},
'attr_1': {'value': 'test_1'}},
'plugin_a_section_2': {
'attr_2': {'value': 'test_2'}},
'section_plugin_b': {
'attr_b': {'value': 'test_b'}}
},
default_attributes
)
def test_get_plugin_node_attributes(self):
attributes = PluginManager.get_plugin_node_attributes(self.node)
del attributes['plugin_a_section_1']['metadata']['node_plugin_id']
del attributes['plugin_a_section_2']['metadata']['node_plugin_id']
self.assertDictEqual(
{
'plugin_a_section_1': {
'metadata': {'label': 'Section 1 of Plugin A',
'class': 'plugin'},
'attr_1': {'value': 'test_1'}},
'plugin_a_section_2': {
'metadata': {'class': 'plugin'},
'attr_2': {'value': 'test_2'}}
},
attributes
)
def test_update_plugin_node_attributes(self):
self.env.create_plugin(
name='plugin_b',
cluster=self.cluster,
enabled=True,
package_version='5.0.0',
node_attributes_metadata={
'section_plugin_b': {
'attr_b': {'value': 'test_b'}
}
})
new_attrs = PluginManager.get_plugin_node_attributes(self.node)
new_attrs['plugin_a_section_1']['attr_1']['value'] = 'new_test_1'
new_attrs['section_plugin_b']['attr_b']['value'] = 'new_test_b'
PluginManager.update_plugin_node_attributes(new_attrs)
attributes = PluginManager.get_plugin_node_attributes(self.node)
for attribute in attributes:
del attributes[attribute]['metadata']['node_plugin_id']
self.assertDictEqual(
{
'plugin_a_section_1': {
'metadata': {'label': 'Section 1 of Plugin A',
'class': 'plugin'},
'attr_1': {'value': 'new_test_1'}},
'plugin_a_section_2': {
'metadata': {'class': 'plugin'},
'attr_2': {'value': 'test_2'}},
'section_plugin_b': {
'metadata': {'class': 'plugin'},
'attr_b': {'value': 'new_test_b'}}
},
attributes
)
def test_add_plugin_attributes_for_node(self):
new_cluster_node = self.env.create_node(
cluster_id=self.cluster.id,
roles=['controller']
)
PluginManager.add_plugin_attributes_for_node(new_cluster_node)
node_cluster_plugins = new_cluster_node.node_cluster_plugins
self.assertEqual(len(node_cluster_plugins), 1)
attributes = node_cluster_plugins[0].attributes
self.assertDictEqual(
{
'plugin_a_section_1': {
'metadata': {'label': 'Section 1 of Plugin A'},
'attr_1': {'value': 'test_1'}},
'plugin_a_section_2': {
'attr_2': {'value': 'test_2'}}
},
attributes
)

View File

@ -53,14 +53,15 @@ class TestNodeAttributes(base.BaseUnitTest):
fake_numa_nodes, comp_entity, [0, 1])
def test_node_cpu_pinning_info(self):
node = mock.Mock(attributes={
'cpu_pinning': {
'meta': {
'some': 'info'},
'comp1': {
'value': 1},
'comp2': {
'value': 3}}})
node = mock.Mock(
id=1,
attributes={
'cpu_pinning': {
'meta': {'some': 'info'},
'comp1': {'value': 1},
'comp2': {'value': 3}}
}
)
self.assertEquals(
{'total_required_cpus': 4,
'components': {
@ -72,6 +73,7 @@ class TestNodeAttributes(base.BaseUnitTest):
def test_total_hugepages(self):
node = mock.Mock(
id=1,
attributes={
'hugepages': {
'comp1': {
@ -92,6 +94,7 @@ class TestNodeAttributes(base.BaseUnitTest):
def test_hugepages_kernel_opts(self):
node = mock.Mock(
id=1,
attributes={
'hugepages': {
'comp1': {
@ -109,6 +112,7 @@ class TestNodeAttributes(base.BaseUnitTest):
def _make_hugepages_node(self):
return mock.Mock(
id=1,
attributes={
'hugepages': {
'comp1': {
@ -164,6 +168,7 @@ class TestNodeAttributes(base.BaseUnitTest):
def test_set_default_hugepages(self):
fake_hugepages = ['0', '1', '2', '3']
node = mock.Mock(
id=1,
attributes={
'hugepages': {
'nova': {

View File

@ -82,7 +82,7 @@ class BaseNodeAttributeValidatorTest(base.BaseTestCase):
}
}
}
self.node = mock.Mock(meta=meta, attributes=attributes)
self.node = mock.Mock(id=1, meta=meta, attributes=attributes)
self.cluster = mock.Mock()

View File

@ -13,13 +13,19 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
import sqlalchemy as sa
from oslo_serialization import jsonutils
from nailgun import consts
from nailgun.objects import ClusterPlugin
from nailgun.objects import NodeClusterPlugin
from nailgun.objects import Plugin
from nailgun.objects import PluginCollection
from nailgun.test import base
import sqlalchemy as sa
import uuid
class ExtraFunctions(base.BaseTestCase):
@ -168,3 +174,187 @@ class TestClusterPlugin(ExtraFunctions):
self.assertFalse(ClusterPlugin.is_plugin_used(plugin.id))
ClusterPlugin.set_attributes(cluster.id, plugin.id, enabled=True)
self.assertTrue(ClusterPlugin.is_plugin_used(plugin.id))
class TestNodeClusterPlugin(ExtraFunctions):
def setUp(self):
super(TestNodeClusterPlugin, self).setUp()
self.node_attributes = self.env.get_default_plugin_node_config()
self.cluster = self.env.create(
release_kwargs={
'version': 'newton-10.0',
'operating_system': 'Ubuntu',
},
nodes_kwargs=[
{'role': 'controller'}
]
)
self.node = self.env.nodes[0]
def test_get_all_enabled_attributes_by_node(self):
plugin_b_node_attributes = {
'plugin_b_section_1': {
'plugin_b_attr1_key': 'plugin_b_attr1_val',
'metadata': {'group': 'plugin_group',
'label': 'Plugin B Section 1'}
},
'plugin_b_section_2': {
'plugin_b_attr2_key': 'plugin_b_attr2_val',
'metadata': {'group': 'plugin_group',
'label': 'Plugin B Section 2'}
}
}
plugin_a = self.env.create_plugin(
name='plugin_a_with_node_attributes',
cluster=self.cluster,
package_version='5.0.0',
node_attributes_metadata=self.node_attributes)
plugin_b = self.env.create_plugin(
name='plugin_b_with_nic_attributes',
cluster=self.cluster,
package_version='5.0.0',
node_attributes_metadata=plugin_b_node_attributes)
attributes = NodeClusterPlugin. \
get_all_enabled_attributes_by_node(self.node)
node_cluster_plugin_a_id = [
item.id for item in self.node.node_cluster_plugins if
item.cluster_plugin_id == plugin_a.cluster_plugins[0].id][0]
node_cluster_plugin_b_id = [
item.id for item in self.node.node_cluster_plugins if
item.cluster_plugin_id == plugin_b.cluster_plugins[0].id][0]
expected_attributes = self.node_attributes
expected_attributes.update(plugin_b_node_attributes)
expected_attributes['plugin_a_section']['metadata'].update({
'node_plugin_id': node_cluster_plugin_a_id,
'class': 'plugin'
})
expected_attributes['plugin_b_section_1']['metadata'].update({
'node_plugin_id': node_cluster_plugin_b_id,
'class': 'plugin'
})
expected_attributes['plugin_b_section_2']['metadata'].update({
'node_plugin_id': node_cluster_plugin_b_id,
'class': 'plugin'
})
self.assertDictEqual(expected_attributes, attributes)
def test_get_all_enabled_attributes_by_node_with_disabled_plugin(self):
self.env.create_plugin(
name='plugin_a_with_node_attributes',
package_version='5.0.0',
enabled=False,
node_attributes_metadata=self.node_attributes)
attributes = NodeClusterPlugin. \
get_all_enabled_attributes_by_node(self.node)
self.assertDictEqual({}, attributes)
def test_add_cluster_plugins_for_node(self):
self.env.create_plugin(
name='plugin_a_with_node_attributes',
package_version='5.0.0',
node_attributes_metadata=self.node_attributes)
self.env.create_plugin(
name='plugin_b_with_nic_attributes',
package_version='5.0.0',
node_attributes_metadata={})
self.env.create_plugin(
name='plugin_c_with_nic_attributes',
package_version='5.0.0',
node_attributes_metadata=self.node_attributes)
new_node = self.env.create_node(
cluster_id=self.cluster.id,
roles=['compute']
)
NodeClusterPlugin.add_cluster_plugins_for_node(new_node)
self.assertEqual(2, len(new_node.node_cluster_plugins))
for item in new_node.node_cluster_plugins:
self.assertDictEqual(self.node_attributes, item.attributes)
def test_add_nodes_for_cluster_plugin(self):
meta = base.reflect_db_metadata()
self.env.create_node(
cluster_id=self.cluster.id,
roles=['compute']
)
plugin = Plugin.create({
'name': 'plugin_a_with_node_attributes',
'title': 'Test Plugin',
'package_version': '5.0.0',
'version': '1.0.0',
'node_attributes_metadata': self.node_attributes
})
cluster_plugin = ClusterPlugin.create({
'cluster_id': self.cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': self.node_attributes
})
NodeClusterPlugin.add_nodes_for_cluster_plugin(cluster_plugin)
node_cluster_plugins = self.db.execute(
meta.tables['node_cluster_plugins'].select()
).fetchall()
self.assertEqual(2, len(node_cluster_plugins))
for item in node_cluster_plugins:
self.assertDictEqual(self.node_attributes,
jsonutils.loads(item.attributes))
def test_add_nodes_for_cluster_plugin_with_empty_attributes(self):
meta = base.reflect_db_metadata()
self.env.create_node(
cluster_id=self.cluster.id,
roles=['compute']
)
plugin = Plugin.create({
'name': 'plugin_a_with_node_attributes',
'title': 'Test Plugin',
'package_version': '5.0.0',
'version': '1.0.0',
'node_attributes_metadata': {}
})
cluster_plugin = ClusterPlugin.create({
'cluster_id': self.cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': plugin.node_attributes_metadata
})
NodeClusterPlugin.add_nodes_for_cluster_plugin(cluster_plugin)
node_cluster_plugins = self.db.execute(
meta.tables['node_cluster_plugins'].select()
).fetchall()
self.assertEqual(0, len(node_cluster_plugins))
def test_set_attributes(self):
meta = base.reflect_db_metadata()
self.env.create_plugin(
cluster=self.cluster,
name='plugin_a_with_node_attributes',
package_version='5.0.0',
node_attributes_metadata=self.node_attributes)
node_attributes_cluster_plugin = self.db.execute(
meta.tables['node_cluster_plugins'].select()
).fetchall()[0]
_id = node_attributes_cluster_plugin.id
attributes = {'test_attr': 'a'}
NodeClusterPlugin.set_attributes(_id, attributes)
node_attributes_cluster_plugin = self.db.execute(
meta.tables['node_cluster_plugins'].select()
).fetchall()[0]
self.assertDictEqual(
attributes,
jsonutils.loads(node_attributes_cluster_plugin[1]))

View File

@ -712,15 +712,21 @@ class TestNodeObject(BaseIntegrationTest):
errors.CannotUpdate, objects.Node.update, node_0, data)
def test_get_attributes(self):
node = self.env.create_node()
fake_attributes = {
'fake_attributes': {'fake_key_1': 'fake_value_1',
'fake_key_2': 'fake_value_2'}
}
node.attributes = fake_attributes
self.assertDictEqual(fake_attributes,
objects.Node.get_attributes(node))
fake_plugin_attributes = {
'plugin_a_section': {'plugin_attr_key': 'plugin_attr_val'}
}
node = self.env.create_node(attributes=fake_attributes)
with mock.patch('nailgun.plugins.manager.PluginManager.'
'get_plugin_node_attributes',
return_value=fake_plugin_attributes):
fake_attributes.update(fake_plugin_attributes)
self.assertDictEqual(fake_attributes,
objects.Node.get_attributes(node))
def test_update_attributes(self):
node = self.env.create_node()
@ -732,8 +738,13 @@ class TestNodeObject(BaseIntegrationTest):
objects.Node.update_attributes(
node,
{
'fake_attributes':
{'fake_key_1': {'key': 'new_value'}}
'fake_attributes': {
'fake_key_1': {'key': 'new_value'}
},
'plugin_a_section': {
'plugin_attr_key': {'value': 'new_attr_val'},
'metadata': {'class': 'plugin', 'node_plugin_id': 1}
}
}
)
@ -741,7 +752,37 @@ class TestNodeObject(BaseIntegrationTest):
'fake_attributes': {'fake_key_1': {'key': 'new_value'},
'fake_key_2': 'fake_value_2'}
}
self.assertEqual(expected_attributes, node.attributes)
self.assertDictEqual(expected_attributes, node.attributes)
def test_get_default_attributes(self):
release_node_attributes = {'release_attr_a': 'release_attr_a_val'}
cluster = self.env.create(
release_kwargs={
'version': 'newton-10.0',
'operating_system': 'Ubuntu',
'node_attributes': release_node_attributes
},
nodes_kwargs=[
{'role': 'controller'}
]
)
plugin_node_attributes = self.env.get_default_plugin_node_config()
self.env.create_plugin(
name='plugin_a',
cluster=cluster,
package_version='5.0.0',
node_attributes_metadata=plugin_node_attributes)
node = cluster.nodes[0]
node.node_cluster_plugins[0].attributes = {}
node.attributes = {}
self.db.flush()
default_attributes = objects.Node.get_default_attributes(node)
expected_attributes = copy.deepcopy(plugin_node_attributes)
expected_attributes.update(release_node_attributes)
self.assertDictEqual(expected_attributes, default_attributes)
def test_update_tags(self):
self.env.create(
@ -772,6 +813,29 @@ class TestNodeObject(BaseIntegrationTest):
objects.Node.update_roles(node, [])
self.assertEquals(['test'], objects.Node.all_tags(node))
@mock.patch.object(objects.Cluster, 'get_editable_attributes')
def test_get_restrictions_models(self, get_cluster_attributes):
mocked_node_attributes = {
'plugin_section_a': 'some_attributes',
'cpu_pinning': {}
}
mocked_cluster_attributes = {'some': {'fake': 'attributes'}}
get_cluster_attributes.return_value = mocked_cluster_attributes
cluster = mock.Mock()
node = mock.Mock(cluster=cluster)
with mock.patch.object(objects.Node, 'get_attributes',
return_value=mocked_node_attributes):
node_models = objects.Node.get_restrictions_models(node)
expected_models = {
'settings': mocked_cluster_attributes,
'cluster': cluster,
'version': settings.VERSION,
'networking_parameters': cluster.network_config,
'node_attributes': mocked_node_attributes
}
self.assertEqual(expected_models, node_models)
class TestTaskObject(BaseIntegrationTest):

View File

@ -83,7 +83,7 @@ class TestPluginBase(base.BaseTestCase):
"""Should return set of all versions this plugin is applicable to"""
self.assertEqual(
self.plugin_adapter.plugin_release_versions,
set(['2014.2-6.0', '2015.1-8.0', 'mitaka-9.0'])
set(['2014.2-6.0', '2015.1-8.0', 'mitaka-9.0', 'newton-10.0'])
)
def test_full_name(self):
@ -552,7 +552,7 @@ class TestPluginV5(TestPluginBase):
bond_attributes_metadata)
self.assertEqual(
self.plugin.node_attributes_metadata,
bond_attributes_metadata)
node_attributes_metadata)
# check custom graph
dg = DeploymentGraph.get_for_model(