Add exception handling when NIC with given name is missing in db

In case NIC names in 'nic_mapping' section of the network template do
not correspond to actually existing NICs of node, to which networks must
be assigned, the error will occur; as lookup in db by name is performed,
result of such lookup will be None, so any attempt to get attributes
from it raises AttributeError.

This change adds handling of the situation by adding of custom exception
and returning proper http response to user who tries to upload network
template. Such handling was added for following API end points, by which
network template is applied to nodes:
* NodeHandler.PUT;
* NodeCollectionHandler.PUT;
* ClusterHandler.PUT;
* TemplateNetworkConfigurationHandler.PUT

Change-Id: Ia9d7264d3885805f91862e408f8059c4e5eed1d6
Closes-Bug: #1539115
(cherry picked from commit 9a261b6dce)
This commit is contained in:
Artem Roma 2016-04-01 12:25:36 +03:00 committed by Alexey Stupnikov
parent 722c0eb965
commit 2637149229
7 changed files with 246 additions and 2 deletions

View File

@ -33,6 +33,7 @@ from nailgun.api.v1.validators.cluster import ClusterStopDeploymentValidator
from nailgun.api.v1.validators.cluster import ClusterValidator
from nailgun.api.v1.validators.cluster import VmwareAttributesValidator
from nailgun.errors import errors
from nailgun.logger import logger
from nailgun import objects
@ -49,6 +50,32 @@ class ClusterHandler(SingleHandler):
single = objects.Cluster
validator = ClusterValidator
@content
def PUT(self, obj_id):
""":returns: JSONized Cluster object.
:http: * 200 (OK)
* 400 (error occured while processing of data)
* 404 (cluster not found in db)
"""
obj = self.get_object_or_404(self.single, obj_id)
data = self.checked_data(
self.validator.validate_update,
instance=obj
)
# NOTE(aroma):if node is being assigned to the cluster, and if network
# template has been set for the cluster, network template will
# also be applied to node; in such case relevant errors might
# occur so they must be handled in order to form proper HTTP
# response for user
try:
self.single.update(obj, data)
except errors.NetworkTemplateCannotBeApplied as exc:
raise self.http(400, exc.message)
return self.single.to_json(obj)
@content
def DELETE(self, obj_id):
""":returns: {}

View File

@ -203,7 +203,12 @@ class TemplateNetworkConfigurationHandler(BaseHandler):
cluster = self.get_object_or_404(objects.Cluster, cluster_id)
self.check_if_template_modification_locked(cluster)
objects.Cluster.set_network_template(cluster, template)
try:
objects.Cluster.set_network_template(cluster, template)
except errors.NetworkTemplateCannotBeApplied as exc:
raise self.http(400, exc.message)
raise self.http(200, template)
def DELETE(self, cluster_id):

View File

@ -50,6 +50,33 @@ class NodeHandler(SingleHandler):
single = objects.Node
validator = NodeValidator
@content
def PUT(self, obj_id):
""":returns: JSONized Node object.
:http: * 200 (OK)
* 400 (error occured while processing of data)
* 404 (Node not found in db)
"""
obj = self.get_object_or_404(self.single, obj_id)
data = self.checked_data(
self.validator.validate_update,
instance=obj
)
# NOTE(aroma):if node is being assigned to the cluster, and if network
# template has been set for the cluster, network template will
# also be applied to node; in such case relevant errors might
# occur so they must be handled in order to form proper HTTP
# response for user
try:
self.single.update(obj, data)
except errors.NetworkTemplateCannotBeApplied as exc:
raise self.http(400, exc.message)
return self.single.to_json(obj)
@content
def DELETE(self, obj_id):
"""Deletes a node from DB and from Cobbler.
@ -114,7 +141,11 @@ class NodeCollectionHandler(CollectionHandler):
if not node:
raise self.http(404, "Can't find node: {0}".format(nd))
self.collection.single.update(node, nd)
try:
self.collection.single.update(node, nd)
except errors.NetworkTemplateCannotBeApplied as exc:
raise self.http(400, exc.message)
nodes_updated.append(node.id)
# we need eagerload everything that is used in render
@ -148,6 +179,7 @@ class NodeCollectionHandler(CollectionHandler):
task_manager = NodeDeletionTaskManager(cluster_id=nodes[0].cluster_id)
# NOTE(aroma): ditto as in comments for NodeHandler's PUT method;
try:
task = task_manager.execute(nodes, mclient_remove=False)
except errors.ControllerInErrorState as e:

View File

@ -678,18 +678,22 @@ class NetworkTemplateValidator(BasicValidator):
# present in network_scheme
if not parsed['adv_net_template']:
raise errors.InvalidData("No node groups are defined")
for ng_name, node_group in six.iteritems(parsed['adv_net_template']):
defined_templates = set(six.iterkeys(node_group['network_scheme']))
not_found = set()
for templates_by_role in six.itervalues(
node_group['templates_for_node_role']):
for template in templates_by_role:
if template not in defined_templates:
not_found.add(template)
if not_found:
raise errors.InvalidData(
"Requested templates {0} were not found for node "
"group {1}".format(', '.join(not_found), ng_name))
if not defined_templates:
raise errors.InvalidData(
"No templates are defined for node group {0}".format(

View File

@ -86,6 +86,7 @@ default_messages = {
"NetworkTemplateMissingRoles": "Roles are missing from network template",
"NetworkTemplateMissingNetRoles": "Network roles are missing",
"NetworkTemplateMissingNetworkGroup": "Network group is missing",
"NetworkTemplateCannotBeApplied": "Network template cannot be applied",
"DuplicatedVIPNames": ("Cannot assign VIPs for the cluster due to "
"overlapping of names of the VIPs"),
"UpdateDnsmasqTaskIsRunning": ("update_dnsmasq task is not finished "

View File

@ -22,6 +22,7 @@ from nailgun import consts
from nailgun.db import db
from nailgun.db.sqlalchemy import models
from nailgun.errors import errors
from nailgun.logger import logger
from nailgun.network.manager import AllocateVIPs70Mixin
@ -458,6 +459,19 @@ class NeutronManager70(
if values['type'] == consts.NETWORK_INTERFACE_TYPES.ether \
and not is_sub_iface:
nic = objects.Node.get_nic_by_name(node, iface)
# NIC names in the template, that networks should be
# assigned to, might not be consistent with names of actually
# existing NICs for the node; in this case the queried object
# will not be found and consequently applying of the template
# must be stopped
if nic is None:
raise errors.NetworkTemplateCannotBeApplied(
"Networks cannot be assigned as interface with name "
"{0} does not exist for node {1}"
.format(iface, objects.Node.get_slave_name(node))
)
node_ifaces[iface]['id'] = nic.id
node_data = {

View File

@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from oslo_serialization import jsonutils
import six
from nailgun import objects
from nailgun.test import base
class TestNetTemplateMalformedNICMappingApply(base.BaseIntegrationTest):
def setUp(self):
super(TestNetTemplateMalformedNICMappingApply, self).setUp()
self.cluster = self.env.create(
release_kwargs={'api': False, 'version': 'mitaka-9.0'},
cluster_kwargs={'api': False}
)
self.node = self.create_controller()
self.modified_template = self.mutilate_nic_mapping(
self._prepare_template(), self.node)
def create_controller(self):
node = self.env.create_node(roles=['controller'])
node.roles = ['controller']
self.db.flush()
return node
def _prepare_template(self):
template = self.env.read_fixtures(['network_template_80'])[0]
template.pop('pk') # PK is not needed
return template
def add_node_to_cluster(self, cluster, node, expect_errors=False):
return self.app.put(
base.reverse(
'NodeHandler',
kwargs={'obj_id': node.id}
),
params=jsonutils.dumps({'cluster_id': cluster.id}),
headers=self.default_headers,
expect_errors=expect_errors
)
def upload_template(self, cluster, template, expect_errors=False):
return self.app.put(
base.reverse(
'TemplateNetworkConfigurationHandler',
kwargs={'cluster_id': cluster.id},
),
jsonutils.dumps(template),
headers=self.default_headers,
expect_errors=expect_errors
)
def mutilate_nic_mapping(self, template, node):
# change names of those interfaces in NIC mapping for which networks
# must be assigned after building of networks to nodes mapping; this
# must result in error as look up by NIC name is performed for node
# in such case, and network controllers of that node will have
# different names, thus will not be returned by query
node_nic_names = [nic.name for nic in node.nic_interfaces]
# NOTE(aroma): in order to make this method more general let's use only
# 'default' node group, as in some test cases modified template
# is assigned for cluster before nodes, and consequently their node
# groups are not yet known by the moment
nic_mapping = \
template['adv_net_template']['default']['nic_mapping']
new_mapping = {}
for substitute, iface_name in six.iteritems(nic_mapping['default']):
if iface_name in node_nic_names:
new_mapping[substitute] = uuid.uuid4().hex
else:
new_mapping[substitute] = iface_name
nic_mapping['default'] = new_mapping
return template
def check_err_resp(self, resp):
self.assertEqual(resp.status_code, 400)
self.assertIn('does not exist for node', resp.json_body['message'])
def test_fail_for_cluster_w_nodes(self):
self.add_node_to_cluster(self.cluster, self.node)
# network template is applied for nodes (if any) when it is uploaded
resp = self.upload_template(self.cluster, self.modified_template,
expect_errors=True)
self.check_err_resp(resp)
def test_fail_if_set_node_via_single_handler(self):
# NOTE(aroma): the template contains data pertaining to node groups;
# so if 'nic_mapping' subsection is malformed the template can
# still be uploaded successfully, but only in case cluster does not
# have assigned nodes bound to that particular node group
self.upload_template(self.cluster, self.modified_template)
# network template is applied for node if it is being added to
# cluster (via handler for single objects)
resp = self.add_node_to_cluster(self.cluster, self.node,
expect_errors=True)
self.check_err_resp(resp)
def test_fail_if_set_node_via_collect_handler(self):
# node could be assigned to cluster via NodeCollectionHandler
# too; please, see comments for previous test case as the main idea
# here is the same except different handler must be checked
self.upload_template(self.cluster, self.modified_template)
resp = self.app.put(
base.reverse("NodeCollectionHandler"),
params=jsonutils.dumps(
[{'id': self.node.id, 'cluster_id': self.cluster.id}]
),
headers=self.default_headers,
expect_errors=True
)
self.check_err_resp(resp)
def test_fail_if_set_node_via_cluster_handler(self):
# node could be set via PUT to ClusterHandler; network template is
# applied to the node in such case; ditto as in comments for
# previous test cases
self.upload_template(self.cluster, self.modified_template)
self.node.nodegroup = objects.Cluster.get_default_group(self.cluster)
resp = self.app.put(
base.reverse(
"ClusterHandler",
kwargs={'obj_id': self.cluster.id}
),
params=jsonutils.dumps({'nodes': [self.node.id]}),
headers=self.default_headers,
expect_errors=True
)
self.check_err_resp(resp)