Move network manager calls into callbacks

Calls to network manager from object methods have been
moved into callbacks implemented by the network manager
extension.

This commit makes the following new callbacks available
to extensions:

 * on_cluster_create: called when a new cluster is created
 * on_cluster_patch_attributes: called when a cluster's attributes
    are updated
 * on_nodegroup_create: called when a node group is created
 * on_before_deployment_serialization: called before deployment
    serialization begins
 * on_before_provisioning_serialization: called before provisioning
    serialization begins
 * on_remove_node_from_cluster: called when a node is removed from
    a cluster

Blueprint: network-manager-extension
Change-Id: I9a3413f54c881edd098e623ea204d12a86695f87
This commit is contained in:
Ryan Moe 2016-04-26 09:34:07 -07:00
parent c8a3c24ebe
commit 76a0bc226e
20 changed files with 513 additions and 389 deletions

View File

@ -24,7 +24,16 @@ from nailgun.extensions.manager import fire_callback_on_node_collection_delete
from nailgun.extensions.manager import fire_callback_on_node_create
from nailgun.extensions.manager import fire_callback_on_node_update
from nailgun.extensions.manager import fire_callback_on_node_reset
from nailgun.extensions.manager import fire_callback_on_nodegroup_create
from nailgun.extensions.manager import fire_callback_on_cluster_create
from nailgun.extensions.manager import fire_callback_on_cluster_delete
from nailgun.extensions.manager import fire_callback_on_remove_node_from_cluster
from nailgun.extensions.manager import \
fire_callback_on_before_deployment_serialization
from nailgun.extensions.manager import \
fire_callback_on_before_provisioning_serialization
from nailgun.extensions.manager import \
fire_callback_on_cluster_patch_attributes
from nailgun.extensions.manager import \
fire_callback_on_deployment_data_serialization
from nailgun.extensions.manager import \

View File

@ -121,10 +121,30 @@ class BaseExtension(object):
def on_node_delete(cls, node):
"""Callback which gets executed when node is deleted"""
@classmethod
def on_remove_node_from_cluster(cls, node):
"""Callback which gets executed when node is removed from a cluster"""
@classmethod
def on_node_collection_delete(cls, node_ids):
"""Callback which gets executed when node collection is deleted"""
@classmethod
def on_nodegroup_create(cls, nodegroup):
"""Callback which gets executed when node group is created"""
@classmethod
def on_cluster_create(cls, cluster, data):
"""Callback which gets executed when cluster is initially created.
This is called after the cluster object is created, attributes have
been created and the default extensions list has been set.
"""
@classmethod
def on_cluster_patch_attributes(cls, cluster, public_map):
"""Callback which gets executed when cluster attributes are updated"""
@classmethod
def on_cluster_delete(cls, cluster):
"""Callback which gets executed when cluster is deleted"""
@ -132,3 +152,13 @@ class BaseExtension(object):
@classmethod
def on_before_deployment_check(cls, cluster):
"""Callback which gets executed when "before deployment check" runs"""
@classmethod
def on_before_deployment_serialization(cls, cluster, nodes,
ignore_customized):
"""Callback which gets executed before deployment serialization"""
@classmethod
def on_before_provisioning_serialization(cls, cluster, nodes,
ignore_customized):
"""Callback which gets executed before provisioning serialization"""

View File

@ -96,11 +96,31 @@ def fire_callback_on_node_delete(node):
extension.on_node_delete(node)
def fire_callback_on_remove_node_from_cluster(node):
for extension in get_all_extensions():
extension.on_remove_node_from_cluster(node)
def fire_callback_on_node_collection_delete(node_ids):
for extension in get_all_extensions():
extension.on_node_collection_delete(node_ids)
def fire_callback_on_nodegroup_create(nodegroup):
for extension in get_all_extensions():
extension.on_nodegroup_create(nodegroup)
def fire_callback_on_cluster_create(cluster, data):
for extension in get_all_extensions():
extension.on_cluster_create(cluster, data)
def fire_callback_on_cluster_patch_attributes(cluster, public_map):
for extension in get_all_extensions():
extension.on_cluster_patch_attributes(cluster, public_map)
def fire_callback_on_cluster_delete(cluster):
for extension in get_all_extensions():
extension.on_cluster_delete(cluster)
@ -111,6 +131,22 @@ def fire_callback_on_before_deployment_check(cluster):
extension.on_before_deployment_check(cluster)
def fire_callback_on_before_deployment_serialization(cluster, nodes,
ignore_customized):
for extension in get_all_extensions():
extension.on_before_deployment_serialization(
cluster, nodes, ignore_customized
)
def fire_callback_on_before_provisioning_serialization(cluster, nodes,
ignore_customized):
for extension in get_all_extensions():
extension.on_before_provisioning_serialization(
cluster, nodes, ignore_customized
)
def _collect_data_pipelines_for_cluster(cluster):
extensions = set(cluster.extensions)
return chain.from_iterable(e.data_pipelines for e in get_all_extensions()

View File

@ -103,11 +103,11 @@ class NetworkCheck(object):
"checking intersection between them...")
bond_interfaces = (
objects.Cluster.get_bond_interfaces_for_all_nodes(
objects.Bond.get_bond_interfaces_for_all_nodes(
self.cluster,
untagged_nets.keys()))
nic_interfaces = (
objects.Cluster.get_nic_interfaces_for_all_nodes(
objects.NIC.get_nic_interfaces_for_all_nodes(
self.cluster,
untagged_nets.keys()))
found_intersection = []
@ -706,7 +706,7 @@ class NetworkCheck(object):
return
nodes_networks = \
objects.Cluster.get_networks_to_interfaces_mapping_on_all_nodes(
objects.NIC.get_networks_to_interfaces_mapping_on_all_nodes(
self.cluster)
# first, group by hostname

View File

@ -1,3 +1,5 @@
from nailgun import consts
from nailgun import errors
from nailgun.extensions import BaseExtension
from nailgun.extensions.network_manager.handlers.network_configuration import\
NetworkAttributesDeployedHandler
@ -29,6 +31,8 @@ from nailgun.extensions.network_manager.handlers.nic import \
from nailgun.extensions.network_manager.handlers.nic import \
NodeNICsHandler
from nailgun import objects
class NetworkManagerExtension(BaseExtension):
name = 'network_manager'
@ -74,3 +78,82 @@ class NetworkManagerExtension(BaseExtension):
r'deployed?$',
'handler': NetworkAttributesDeployedHandler}
]
@classmethod
def on_cluster_create(cls, cluster, data):
try:
net_manager = objects.Cluster.get_network_manager(cluster)
net_manager.create_network_groups_and_config(cluster, data)
objects.Cluster.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.networks)
net_manager.assign_vips_for_net_groups(cluster)
except (
errors.OutOfVLANs,
errors.OutOfIPs,
errors.NoSuitableCIDR,
# VIP assignment related errors
errors.CanNotFindCommonNodeGroup,
errors.CanNotFindNetworkForNodeGroup,
errors.DuplicatedVIPNames
) as exc:
raise errors.CannotCreate(exc.message)
@classmethod
def on_cluster_patch_attributes(cls, cluster, public_map):
roles_metadata = objects.Cluster.get_roles(cluster)
nm = objects.Cluster.get_network_manager(cluster)
nm.update_restricted_networks(cluster)
objects.NetworkGroup._update_public_network(
cluster, public_map, roles_metadata
)
@classmethod
def on_nodegroup_create(cls, ng):
try:
cluster = objects.Cluster.get_by_uid(ng.cluster_id)
nm = objects.Cluster.get_network_manager(cluster)
nst = cluster.network_config.segmentation_type
# We have two node groups here when user adds the first custom
# node group.
if (objects.NodeGroupCollection.get_by_cluster_id(cluster.id)
.count() == 2):
nm.ensure_gateways_present_in_default_node_group(cluster)
nm.create_network_groups(
cluster, neutron_segment_type=nst, node_group_id=ng.id,
set_all_gateways=True)
nm.create_admin_network_group(ng.cluster_id, ng.id)
except (
errors.OutOfVLANs,
errors.OutOfIPs,
errors.NoSuitableCIDR
) as exc:
raise errors.CannotCreate(exc.message)
@classmethod
def on_before_deployment_serialization(cls, cluster, nodes,
ignore_customized):
# TODO(apply only for specified subset of nodes)
nm = objects.Cluster.get_network_manager(cluster)
nm.prepare_for_deployment(
cluster, cluster.nodes if nodes is None else nodes
)
@classmethod
def on_before_provisioning_serialization(cls, cluster, nodes,
ignore_customized):
nm = objects.Cluster.get_network_manager(cluster)
nm.prepare_for_provisioning(
cluster.nodes if nodes is None else nodes
)
@classmethod
def on_node_reset(cls, node):
objects.IPAddr.delete_by_node(node.id)
@classmethod
def on_remove_node_from_cluster(cls, node):
netmanager = objects.Cluster.get_network_manager(node.cluster)
netmanager.clear_assigned_networks(node)
netmanager.clear_bond_configuration(node)

View File

@ -95,7 +95,7 @@ class NetworkManager(object):
node_id = node.id
admin_net = objects.NetworkGroup.get_admin_network_group(
node, default_admin_net)
node_admin_ips_count = objects.Node.get_network_ips_count(
node_admin_ips_count = objects.IPAddr.get_network_ips_count(
node_id, admin_net.id)
logger.debug(u"Trying to assign admin ip: node=%s", node_id)
if not node_admin_ips_count:
@ -579,7 +579,7 @@ class NetworkManager(object):
"""
node_group = objects.NodeGroup.get_by_uid(ng.group_id)
for node in node_group.nodes:
objects.Node.assign_network_to_interface(node, ng)
objects.NetworkGroup.assign_network_to_interface(ng, node)
@classmethod
def get_default_interfaces_configuration(cls, node):
@ -1029,9 +1029,26 @@ class NetworkManager(object):
@classmethod
def get_admin_ip_for_node(cls, node=None, admin_net=None):
"""Returns first admin IP address for node."""
"""Returns first admin IP address for node.
When admin_net_id is None the admin network group for the node's
nodegroup will be used.
:param node: return admin IP of this node
:type node: nailgun.db.sqlalchemy.models.Node
:param admin_net_id: Admin NetworkGroup ID
:type admin_net_id: int
:returns: IPAddr instance
"""
admin_net_id = admin_net.id if admin_net else None
return objects.Node.get_admin_ip(node, admin_net_id)
if not admin_net_id:
admin_net = objects.NetworkGroup.get_admin_network_group(node)
admin_net_id = admin_net.id
admin_ip = next((ip for ip in node.ip_addrs
if ip.network == admin_net_id), None)
return getattr(admin_ip, 'ip_addr', None)
@classmethod
def get_admin_interface(cls, node):
@ -1888,7 +1905,9 @@ class AssignIPs70Mixin(object):
nodes_by_id = dict((n.id, n) for n in nodes)
query = objects.Cluster.get_network_groups_and_node_ids(cluster.id)
query = objects.NetworkGroup.get_network_groups_and_node_ids(
cluster.id
)
# Group by NetworkGroup.id
for key, items in groupby(query, lambda x: x[1]):

View File

@ -150,7 +150,7 @@ class NeutronManager70(
if not node.group_id:
return {}
ngs = objects.Node.get_networks_ips(node)
ngs = objects.IPAddr.get_networks_ips(node)
if not ngs:
return {}
@ -433,7 +433,7 @@ class NeutronManager70(
# not an ethernet interface so no NIC will be found.
if values['type'] == consts.NETWORK_INTERFACE_TYPES.ether \
and not is_sub_iface:
nic = objects.Node.get_nic_by_name(node, iface)
nic = objects.NIC.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

View File

@ -15,6 +15,7 @@
# under the License.
from nailgun.db import db
from nailgun.db.sqlalchemy import models
from nailgun.extensions.network_manager.objects.interface import DPDKMixin
from nailgun.extensions.network_manager.objects.interface import NIC
@ -57,6 +58,21 @@ class Bond(DPDKMixin, NailgunObject):
return all(NIC.get_dpdk_driver(iface, dpdk_drivers)
for iface in instance.slaves)
@classmethod
def get_bond_interfaces_for_all_nodes(cls, cluster, networks=None):
bond_interfaces_query = db().query(
models.NodeBondInterface
).join(
models.Node
).filter(
models.Node.cluster_id == cluster.id
)
if networks:
bond_interfaces_query = bond_interfaces_query.join(
models.NodeBondInterface.assigned_networks_list,
aliased=True).filter(models.NetworkGroup.id.in_(networks))
return bond_interfaces_query.all()
class BondCollection(NailgunCollection):

View File

@ -117,6 +117,100 @@ class NIC(DPDKMixin, NailgunObject):
mode["state"] = old_modes_states[mode["name"]]
instance.offloading_modes = new_modes
@classmethod
def get_nic_interfaces_for_all_nodes(cls, cluster, networks=None):
nic_interfaces_query = db().query(
models.NodeNICInterface
).join(
models.Node
).filter(
models.Node.cluster_id == cluster.id
)
if networks:
nic_interfaces_query = nic_interfaces_query.join(
models.NodeNICInterface.assigned_networks_list, aliased=True).\
filter(models.NetworkGroup.id.in_(networks))
return nic_interfaces_query.all()
@classmethod
def get_networks_to_interfaces_mapping_on_all_nodes(cls, cluster):
"""Query networks to interfaces mapping on all nodes in cluster.
Returns combined results for NICs and bonds for every node.
Names are returned for node and interface (NIC or bond),
IDs are returned for networks. Results are sorted by node name then
interface name.
"""
nodes_nics_networks = db().query(
models.Node.hostname,
models.NodeNICInterface.name,
models.NetworkGroup.id,
).join(
models.Node.nic_interfaces,
models.NodeNICInterface.assigned_networks_list
).filter(
models.Node.cluster_id == cluster.id,
)
nodes_bonds_networks = db().query(
models.Node.hostname,
models.NodeBondInterface.name,
models.NetworkGroup.id,
).join(
models.Node.bond_interfaces,
models.NodeBondInterface.assigned_networks_list
).filter(
models.Node.cluster_id == cluster.id,
)
return nodes_nics_networks.union(
nodes_bonds_networks
).order_by(
# column 1 then 2 from the result. cannot call them by name as
# names for column 2 are different in this union
'1', '2'
)
@classmethod
def get_interface_by_net_name(cls, node_id, netname):
"""Get interface with specified network assigned to it.
This method first checks for a NodeNICInterface with the specified
network assigned. If that fails it will look for a NodeBondInterface
with that network assigned.
:param instance_id: Node ID
:param netname: NetworkGroup name
:returns: either NodeNICInterface or NodeBondInterface
"""
iface = db().query(models.NodeNICInterface).join(
(models.NetworkGroup,
models.NodeNICInterface.assigned_networks_list)
).filter(
models.NetworkGroup.name == netname
).filter(
models.NodeNICInterface.node_id == node_id
).first()
if iface:
return iface
return db().query(models.NodeBondInterface).join(
(models.NetworkGroup,
models.NodeBondInterface.assigned_networks_list)
).filter(
models.NetworkGroup.name == netname
).filter(
models.NodeBondInterface.node_id == node_id
).first()
@classmethod
def get_nic_by_name(cls, node, iface_name):
nic = db().query(models.NodeNICInterface).filter_by(
name=iface_name
).filter_by(
node_id=node.id
).first()
return nic
class NICCollection(NailgunCollection):

View File

@ -145,6 +145,61 @@ class IPAddr(NailgunObject):
models.IPAddr.network == network
).delete(synchronize_session='fetch')
@classmethod
def get_networks_ips(cls, node):
"""Get IPs assigned to node grouped by network group.
:param node: Node instance
:returns: query
"""
return db().query(
models.NetworkGroup, models.IPAddr.ip_addr
).filter(
models.NetworkGroup.group_id == node.group_id,
models.IPAddr.network == models.NetworkGroup.id,
models.IPAddr.node == node.id
)
@classmethod
def get_networks_ips_dict(cls, node):
"""Get IPs assigned to node grouped by network group.
:param node: Node instance
:returns: dict mapping network group name to IP addresses
"""
return {ng.name: ip for ng, ip in cls.get_networks_ips(node)}
@classmethod
def get_network_ips_count(cls, node_id, network_id):
"""Get number of IPs from specified network assigned to node.
:param node_id: Node ID
:param network_id: NetworkGroup ID
:returns: count of IP addresses
"""
return db().query(models.IPAddr).filter_by(
node=node_id, network=network_id).count()
@classmethod
def set_networks_ips(cls, node, ips_by_network_name):
"""Set IPs by provided network-to-IP mapping.
:param instance: Node instance
:param ips_by_network_name: dict
:returns: None
"""
ngs = db().query(
models.NetworkGroup.name, models.IPAddr
).filter(
models.NetworkGroup.group_id == node.group_id,
models.IPAddr.network == models.NetworkGroup.id,
models.IPAddr.node == node.id,
models.NetworkGroup.name.in_(ips_by_network_name)
)
for ng_name, ip_addr in ngs:
ip_addr.ip_addr = ips_by_network_name[ng_name]
db().flush()
class IPAddrRange(NailgunObject):
model = models.IPAddrRange

View File

@ -345,6 +345,109 @@ class NetworkGroup(NailgunObject):
return network
@classmethod
def get_network_groups_and_node_ids(cls, cluster_id):
"""Get network group information for the given cluster
The admin network group will not be included.
:param instance: Cluster instance
:type instance: nailgun.db.sqlalchemy.models.Cluster instance
:returns: tuple of Node ID, and NetworkGroup ID, name, meta
"""
query = (db().query(
models.Node.id,
models.NetworkGroup.id,
models.NetworkGroup.name,
models.NetworkGroup.meta)
.join(models.NodeGroup.nodes)
.join(models.NodeGroup.networks)
.filter(models.NodeGroup.cluster_id == cluster_id,
models.NetworkGroup.name != consts.NETWORKS.fuelweb_admin)
)
return query
@classmethod
def _update_public_network(cls, cluster, public_map, roles_metadata):
"""Applies changes to node's public_network checked using public_map.
:param instance: Cluster object
:param public_map: dict of Node.id to should_have_public result.
:param roles_metadata: dict from objects.Cluster.get_roles
"""
if cluster.network_config.configuration_template is not None:
return
from nailgun.objects import Node
for node in cluster.nodes:
should_have_public = Node.should_have_public(
node, roles_metadata)
if public_map.get(node.id) == should_have_public:
continue
if should_have_public:
cls.assign_public_network(node)
else:
cls.unassign_public_network(node)
@classmethod
def assign_public_network(cls, node):
public_net = next(NetworkGroupCollection.filter_by(
node.nodegroup.networks,
name=consts.NETWORKS.public), None)
cls.assign_network_to_interface(public_net, node)
@classmethod
def unassign_public_network(cls, node):
from nailgun import objects
public_net = next(NetworkGroupCollection.filter_by(
node.nodegroup.networks,
name=consts.NETWORKS.public), None)
ifaces = objects.Node.get_interfaces_without_bonds_slaves(node)
for iface in ifaces:
network_list = iface.assigned_networks_list
if public_net in network_list:
network_list.remove(public_net)
objects.NIC.assign_networks(iface, network_list)
@classmethod
def assign_network_to_interface(cls, instance, node):
"""Assign network to interface by default for single node
Assign given network to first available interface.
Checks interface type, if network is already assigned
and already assigned networks.
"""
if instance is None:
return
from nailgun import objects
untagged = cls.is_untagged(instance)
dedicated = instance.meta.get('dedicated_nic')
ifaces = objects.Node.get_interfaces_without_bonds_slaves(node)
for iface in ifaces:
if dedicated and iface.assigned_networks_list:
continue
for net in iface.assigned_networks_list:
if net.meta.get('dedicated_nic'):
break
if net == instance:
return
if untagged and cls.is_untagged(net):
break
else:
assigned_nets = iface.assigned_networks_list + [instance]
objects.NIC.assign_networks(iface, assigned_nets)
break
else:
logger.warning(
"Cannot assign network %r appropriately for "
"node %r. Set unassigned network to the "
"interface %r",
instance.name, node.name, ifaces[0].name
)
assigned_nets = ifaces[0].assigned_networks_list + [instance]
objects.NIC.assign_networks(ifaces[0], assigned_nets)
class NetworkGroupCollection(NailgunCollection):

View File

@ -617,7 +617,7 @@ class TestNetworkManager(BaseIntegrationTest):
(ip.network_data.name, ip.ip_addr) for ip in node.ip_addrs
)
self.assertEquals(node_net_ips,
objects.Node.get_networks_ips_dict(node))
objects.IPAddr.get_networks_ips_dict(node))
def test_get_admin_ip_for_node(self):
cluster = self.env.create(api=False)
@ -649,9 +649,9 @@ class TestNetworkManager(BaseIntegrationTest):
node_net_ips = \
dict((net.name, self.env.network_manager.get_free_ips(net)[0])
for net in node.networks)
objects.Node.set_networks_ips(node, node_net_ips)
objects.IPAddr.set_networks_ips(node, node_net_ips)
self.assertEquals(node_net_ips,
objects.Node.get_networks_ips_dict(node))
objects.IPAddr.get_networks_ips_dict(node))
def test_set_netgroups_ids(self):
cluster = self.env.create_cluster(api=False)

View File

@ -19,6 +19,7 @@ import mock
from netaddr import IPNetwork
from nailgun import consts
from nailgun.db.sqlalchemy.models import NodeBondInterface
from nailgun import objects
from nailgun.test.base import BaseTestCase
@ -106,6 +107,16 @@ class TestBondObject(BaseTestCase):
objects.Bond.update(bond, data)
self.assertEqual(data['offloading_modes'], bond.offloading_modes)
def test_get_bond_interfaces_for_all_nodes(self):
node = self.env.nodes[0]
node.bond_interfaces.append(
NodeBondInterface(name='ovs-bond0',
slaves=node.nic_interfaces))
self.db.flush()
bond_interfaces = objects.Bond.get_bond_interfaces_for_all_nodes(
self.env.clusters[0])
self.assertEqual(len(bond_interfaces), 1)
class TestNICObject(BaseTestCase):
@ -145,6 +156,16 @@ class TestNICObject(BaseTestCase):
self.assertEqual(len(mac_list), 1)
self.assertEqual(mac_list[0], expected_mac)
def test_get_nic_interfaces_for_all_nodes(self):
nodes = self.env.nodes
interfaces = []
for node in nodes:
for inf in node.nic_interfaces:
interfaces.append(inf)
nic_interfaces = objects.NIC.get_nic_interfaces_for_all_nodes(
self.env.clusters[0])
self.assertEqual(len(nic_interfaces), len(interfaces))
class TestIPAddrObject(BaseTestCase):

View File

@ -33,7 +33,9 @@ from nailgun import consts
from nailgun.db import db
from nailgun.db.sqlalchemy import models
from nailgun import errors
from nailgun.extensions import fire_callback_on_cluster_create
from nailgun.extensions import fire_callback_on_cluster_delete
from nailgun.extensions import fire_callback_on_cluster_patch_attributes
from nailgun.extensions import fire_callback_on_node_collection_delete
from nailgun.logger import logger
from nailgun.objects import DeploymentGraph
@ -177,36 +179,18 @@ class Cluster(NailgunObject):
# default graph should be created in any case
DeploymentGraph.create_for_model({"tasks": deployment_tasks}, cluster)
try:
net_manager = cls.get_network_manager(cluster)
net_manager.create_network_groups_and_config(cluster, data)
cls.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.attributes)
cls.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.vmware_attributes)
cls.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.attributes)
cls.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.networks)
cls.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.vmware_attributes)
ClusterPlugins.add_compatible_plugins(cluster)
PluginManager.enable_plugins_by_components(cluster)
if assign_nodes:
cls.update_nodes(cluster, assign_nodes)
fire_callback_on_cluster_create(cluster, data)
ClusterPlugins.add_compatible_plugins(cluster)
PluginManager.enable_plugins_by_components(cluster)
net_manager.assign_vips_for_net_groups(cluster)
except (
errors.OutOfVLANs,
errors.OutOfIPs,
errors.NoSuitableCIDR,
# VIP assignment related errors
errors.CanNotFindCommonNodeGroup,
errors.CanNotFindNetworkForNodeGroup,
errors.DuplicatedVIPNames
) as exc:
raise errors.CannotCreate(exc.message)
if assign_nodes:
cls.update_nodes(cluster, assign_nodes)
return cluster
@ -405,28 +389,6 @@ class Cluster(NailgunObject):
node, roles_metadata)
return public_map
@classmethod
def _update_public_network(cls, instance, public_map, roles_metadata):
"""Applies changes to node's public_network checked using public_map.
:param instance: Cluster object
:param public_map: dict of Node.id to should_have_public result.
:param roles_metadata: dict from objects.Cluster.get_roles
"""
if instance.network_config.configuration_template is not None:
return
from nailgun import objects
for node in instance.nodes:
should_have_public = objects.Node.should_have_public(
node, roles_metadata)
if public_map.get(node.id) == should_have_public:
continue
if should_have_public:
objects.Node.assign_public_network(node)
else:
objects.Node.unassign_public_network(node)
@classmethod
def patch_attributes(cls, instance, data):
"""Applyes changes to Cluster attributes and updates networks.
@ -434,7 +396,6 @@ class Cluster(NailgunObject):
:param instance: Cluster object
:param data: dict
"""
roles_metadata = Cluster.get_roles(instance)
# Note(kszukielojc): We need to create status map of public networks
# to avoid updating networks if there was no change to node public
@ -445,8 +406,8 @@ class Cluster(NailgunObject):
instance.attributes.editable = dict_merge(
instance.attributes.editable, data['editable'])
cls.add_pending_changes(instance, "attributes")
cls.get_network_manager(instance).update_restricted_networks(instance)
cls._update_public_network(instance, public_map, roles_metadata)
fire_callback_on_cluster_patch_attributes(instance, public_map)
db().flush()
@classmethod
@ -696,7 +657,7 @@ class Cluster(NailgunObject):
db().flush()
@classmethod
def get_ifaces_for_network_in_cluster(cls, instance, net):
def get_ifaces_for_network_in_cluster(cls, cluster, net):
"""Method for receiving node_id:iface pairs for all nodes in cluster
:param instance: Cluster instance
@ -708,14 +669,14 @@ class Cluster(NailgunObject):
models.NodeNICInterface.node_id,
models.NodeNICInterface.name
).filter(
models.NodeNICInterface.node.has(cluster_id=instance.id),
models.NodeNICInterface.node.has(cluster_id=cluster.id),
models.NodeNICInterface.assigned_networks_list.any(name=net)
)
bonds_db = db().query(
models.NodeBondInterface.node_id,
models.NodeBondInterface.name
).filter(
models.NodeBondInterface.node.has(cluster_id=instance.id),
models.NodeBondInterface.node.has(cluster_id=cluster.id),
models.NodeBondInterface.assigned_networks_list.any(name=net)
)
return nics_db.union(bonds_db)
@ -999,26 +960,6 @@ class Cluster(NailgunObject):
return nodegroups
@classmethod
def get_bond_interfaces_for_all_nodes(cls, instance, networks=None):
bond_interfaces_query = db().query(models.NodeBondInterface).\
join(models.Node).filter(models.Node.cluster_id == instance.id)
if networks:
bond_interfaces_query = bond_interfaces_query.join(
models.NodeBondInterface.assigned_networks_list,
aliased=True).filter(models.NetworkGroup.id.in_(networks))
return bond_interfaces_query.all()
@classmethod
def get_nic_interfaces_for_all_nodes(cls, instance, networks=None):
nic_interfaces_query = db().query(models.NodeNICInterface).\
join(models.Node).filter(models.Node.cluster_id == instance.id)
if networks:
nic_interfaces_query = nic_interfaces_query.join(
models.NodeNICInterface.assigned_networks_list, aliased=True).\
filter(models.NetworkGroup.id.in_(networks))
return nic_interfaces_query.all()
@classmethod
def get_default_group(cls, instance):
return next(g for g in instance.node_groups if g.is_default)
@ -1398,43 +1339,6 @@ class Cluster(NailgunObject):
return bool(instance.attributes.editable['additional_components'].
get((component), {}).get('value'))
@classmethod
def get_networks_to_interfaces_mapping_on_all_nodes(cls, instance):
"""Query networks to interfaces mapping on all nodes in cluster.
Returns combined results for NICs and bonds for every node.
Names are returned for node and interface (NIC or bond),
IDs are returned for networks. Results are sorted by node name then
interface name.
"""
nodes_nics_networks = db().query(
models.Node.hostname,
models.NodeNICInterface.name,
models.NetworkGroup.id,
).join(
models.Node.nic_interfaces,
models.NodeNICInterface.assigned_networks_list
).filter(
models.Node.cluster_id == instance.id,
)
nodes_bonds_networks = db().query(
models.Node.hostname,
models.NodeBondInterface.name,
models.NetworkGroup.id,
).join(
models.Node.bond_interfaces,
models.NodeBondInterface.assigned_networks_list
).filter(
models.Node.cluster_id == instance.id,
)
return nodes_nics_networks.union(
nodes_bonds_networks
).order_by(
# column 1 then 2 from the result. cannot call them by name as
# names for column 2 are different in this union
'1', '2'
)
@classmethod
def get_nodes_to_update_config(cls, cluster, node_ids=None, node_role=None,
only_ready_nodes=True):
@ -1470,17 +1374,6 @@ class Cluster(NailgunObject):
instance, instance.nodes if nodes is None else nodes
)
@classmethod
def prepare_for_provisioning(cls, instance, nodes=None):
"""Shortcut for NetworkManager.prepare_for_provisioning.
:param instance: nailgun.db.sqlalchemy.models.Cluster instance
:param nodes: the list of Nodes, None means for all nodes
"""
cls.get_network_manager(instance).prepare_for_provisioning(
instance.nodes if nodes is None else nodes
)
@classmethod
def has_compute_vmware_changes(cls, instance):
"""Checks if any 'compute-vmware' nodes are waiting for deployment.
@ -1566,29 +1459,6 @@ class Cluster(NailgunObject):
q = db().query(models.Node).filter_by(cluster_id=instance.id)
return q.filter(models.Node.status != status).count()
@classmethod
def get_network_groups_and_node_ids(cls, instance_id):
"""Get network group information for the given cluster
The admin network group will not be included.
:param instance: Cluster instance
:type instance: nailgun.db.sqlalchemy.models.Cluster instance
:returns: tuple of Node ID, and NetworkGroup ID, name, meta
"""
query = (db().query(
models.Node.id,
models.NetworkGroup.id,
models.NetworkGroup.name,
models.NetworkGroup.meta)
.join(models.NodeGroup.nodes)
.join(models.NodeGroup.networks)
.filter(models.NodeGroup.cluster_id == instance_id,
models.NetworkGroup.name != consts.NETWORKS.fuelweb_admin)
)
return query
@classmethod
def get_network_attributes(cls, instance):
# use local import to avoid recursive imports

View File

@ -42,16 +42,15 @@ from nailgun.extensions import fire_callback_on_node_create
from nailgun.extensions import fire_callback_on_node_delete
from nailgun.extensions import fire_callback_on_node_reset
from nailgun.extensions import fire_callback_on_node_update
from nailgun.extensions import fire_callback_on_remove_node_from_cluster
from nailgun.extensions.network_manager.template import NetworkTemplate
from nailgun.extensions.network_manager import utils as network_utils
from nailgun.logger import logger
from nailgun.objects import Bond
from nailgun.objects import Cluster
from nailgun.objects import IPAddr
from nailgun.objects import NailgunCollection
from nailgun.objects import NailgunObject
from nailgun.objects import NetworkGroup
from nailgun.objects import NetworkGroupCollection
from nailgun.objects import NIC
from nailgun.objects import Notification
from nailgun.objects import Release
@ -76,61 +75,6 @@ class Node(NailgunObject):
fire_callback_on_node_delete(instance)
super(Node, cls).delete(instance)
@classmethod
def get_network_ips_count(cls, node_id, network_id):
"""Get number of IPs from specified network assigned to node.
:param node_id: Node ID
:param network_id: NetworkGroup ID
:returns: count of IP addresses
"""
return db().query(models.IPAddr).filter_by(
node=node_id, network=network_id).count()
@classmethod
def get_networks_ips(cls, instance):
"""Get IPs assigned to node grouped by network group.
:param node: Node instance
:returns: query
"""
return db().query(
models.NetworkGroup, models.IPAddr.ip_addr
).filter(
models.NetworkGroup.group_id == instance.group_id,
models.IPAddr.network == models.NetworkGroup.id,
models.IPAddr.node == instance.id
)
@classmethod
def get_networks_ips_dict(cls, instance):
"""Get IPs assigned to node grouped by network group.
:param node: Node instance
:returns: dict mapping network group name to IP addresses
"""
return {ng.name: ip for ng, ip in cls.get_networks_ips(instance)}
@classmethod
def set_networks_ips(cls, instance, ips_by_network_name):
"""Set IPs by provided network-to-IP mapping.
:param instance: Node instance
:param ips_by_network_name: dict
:returns: None
"""
ngs = db().query(
models.NetworkGroup.name, models.IPAddr
).filter(
models.NetworkGroup.group_id == instance.group_id,
models.IPAddr.network == models.NetworkGroup.id,
models.IPAddr.node == instance.id,
models.NetworkGroup.name.in_(ips_by_network_name)
)
for ng_name, ip_addr in ngs:
ip_addr.ip_addr = ips_by_network_name[ng_name]
db().flush()
@classmethod
def set_netgroups_ids(cls, instance, netgroups_id_mapping):
"""Set IP network group assignment based on provided mapping.
@ -178,38 +122,6 @@ class Node(NailgunObject):
bond_assignment.network_id = \
netgroups_id_mapping[bond_assignment.network_id]
@classmethod
def get_interface_by_net_name(cls, instance_id, netname):
"""Get interface with specified network assigned to it.
This method first checks for a NodeNICInterface with the specified
network assigned. If that fails it will look for a NodeBondInterface
with that network assigned.
:param instance_id: Node ID
:param netname: NetworkGroup name
:returns: either NodeNICInterface or NodeBondInterface
"""
iface = db().query(models.NodeNICInterface).join(
(models.NetworkGroup,
models.NodeNICInterface.assigned_networks_list)
).filter(
models.NetworkGroup.name == netname
).filter(
models.NodeNICInterface.node_id == instance_id
).first()
if iface:
return iface
return db().query(models.NodeBondInterface).join(
(models.NetworkGroup,
models.NodeBondInterface.assigned_networks_list)
).filter(
models.NetworkGroup.name == netname
).filter(
models.NodeBondInterface.node_id == instance_id
).first()
@classmethod
def get_interface_by_mac_or_name(cls, instance, mac=None, name=None):
# try to get interface by mac address
@ -223,33 +135,6 @@ class Node(NailgunObject):
n for n in instance.nic_interfaces if n.name == name), None)
return interface
@classmethod
def get_admin_ip(cls, node, admin_net_id=None):
"""Get admin IP for specified node.
When admin_net_id is None the admin network group for the node's
nodegroup will be used.
If all_ips is True it will return all IPs from the admin network
assigned to that node. Otherwise it just returns a single IP.
:param node: return admin IP of this node
:type node: nailgun.db.sqlalchemy.models.Node
:param admin_net_id: Admin NetworkGroup ID
:type admin_net_id: int
:param all_ips:
:type all_ips: boolean
:returns: IPAddr instance
"""
if not admin_net_id:
admin_net = NetworkGroup.get_admin_network_group(node)
admin_net_id = admin_net.id
admin_ip = next((ip for ip in node.ip_addrs
if ip.network == admin_net_id), None)
return getattr(admin_ip, 'ip_addr', None)
@classmethod
def get_all_node_ips(cls):
"""Get all Admin IPs assigned to nodes.
@ -375,13 +260,6 @@ class Node(NailgunObject):
return False
@classmethod
def assign_public_network(cls, node):
public_net = next(NetworkGroupCollection.filter_by(
node.nodegroup.networks,
name=consts.NETWORKS.public), None)
cls.assign_network_to_interface(node, public_net)
@staticmethod
def get_interfaces_without_bonds_slaves(node):
ifaces = set(node.interfaces)
@ -389,55 +267,6 @@ class Node(NailgunObject):
ifaces ^= set(bond.slaves)
return sorted(ifaces, key=operator.attrgetter('name'))
@classmethod
def assign_network_to_interface(cls, node, network):
"""Assign network to interface by default for single node
Assign given network to first available interface.
Checks interface type, if network is already assigned
and already assigned networks.
"""
if network is None:
return
untagged = NetworkGroup.is_untagged(network)
dedicated = network.meta.get('dedicated_nic')
ifaces = cls.get_interfaces_without_bonds_slaves(node)
for iface in ifaces:
if dedicated and iface.assigned_networks_list:
continue
for net in iface.assigned_networks_list:
if net.meta.get('dedicated_nic'):
break
if net == network:
return
if untagged and NetworkGroup.is_untagged(net):
break
else:
assigned_nets = iface.assigned_networks_list + [network]
NIC.assign_networks(iface, assigned_nets)
break
else:
logger.warning(
"Cannot assign network %r appropriately for "
"node %r. Set unassigned network to the "
"interface %r",
network.name, node.name, ifaces[0].name
)
assigned_nets = ifaces[0].assigned_networks_list + [network]
NIC.assign_networks(ifaces[0], assigned_nets)
@classmethod
def unassign_public_network(cls, node):
public_net = next(NetworkGroupCollection.filter_by(
node.nodegroup.networks,
name=consts.NETWORKS.public), None)
ifaces = cls.get_interfaces_without_bonds_slaves(node)
for iface in ifaces:
network_list = iface.assigned_networks_list
if public_net in network_list:
network_list.remove(public_net)
NIC.assign_networks(iface, network_list)
@classmethod
def create(cls, data):
"""Create Node instance with specified parameters in DB.
@ -841,7 +670,6 @@ class Node(NailgunObject):
# - mac to ip mapping from dnsmasq.conf is deleted
# imho we need to revert node to original state, as it was
# added to cluster (without any additonal state in database)
IPAddr.delete_by_node(instance.id)
fire_callback_on_node_reset(instance)
db().flush()
@ -1117,16 +945,13 @@ class Node(NailgunObject):
:param instance: Node instance
:returns: None
"""
fire_callback_on_remove_node_from_cluster(instance)
if instance.cluster:
Cluster.clear_pending_changes(
instance.cluster,
node_id=instance.id
)
netmanager = Cluster.get_network_manager(
instance.cluster
)
netmanager.clear_assigned_networks(instance)
netmanager.clear_bond_configuration(instance)
cls.update_roles(instance, [])
cls.update_pending_roles(instance, [])
cls.remove_replaced_params(instance)
@ -1280,16 +1105,6 @@ class Node(NailgunObject):
hostname = 'node-{0}'.format(node.uuid)
return hostname
@classmethod
def get_nic_by_name(cls, instance, iface_name):
nic = db().query(models.NodeNICInterface).filter_by(
name=iface_name
).filter_by(
node_id=instance.id
).first()
return nic
@classmethod
def reset_vms_created_state(cls, node):
if consts.VIRTUAL_NODE_TYPES.virt not in node.all_roles:

View File

@ -20,6 +20,7 @@ from nailgun.objects.serializers.node_group import NodeGroupSerializer
from nailgun.db import db
from nailgun.db.sqlalchemy import models
from nailgun import errors
from nailgun.extensions import fire_callback_on_nodegroup_create
from nailgun.objects import Cluster
from nailgun.objects import NailgunCollection
from nailgun.objects import NailgunObject
@ -33,25 +34,11 @@ class NodeGroup(NailgunObject):
@classmethod
def create(cls, data):
new_group = super(NodeGroup, cls).create(data)
cluster = Cluster.get_by_uid(new_group.cluster_id)
try:
cluster = Cluster.get_by_uid(new_group.cluster_id)
nm = Cluster.get_network_manager(cluster)
nst = cluster.network_config.segmentation_type
# We have two node groups here when user adds the first custom
# node group.
if NodeGroupCollection.get_by_cluster_id(cluster.id).count() == 2:
nm.ensure_gateways_present_in_default_node_group(cluster)
nm.create_network_groups(
cluster, neutron_segment_type=nst, node_group_id=new_group.id,
set_all_gateways=True)
nm.create_admin_network_group(new_group.cluster_id, new_group.id)
except (
errors.OutOfVLANs,
errors.OutOfIPs,
errors.NoSuitableCIDR
) as exc:
fire_callback_on_nodegroup_create(new_group)
except errors.CannotCreate:
db().delete(new_group)
raise errors.CannotCreate(exc.message)
db().flush()
db().refresh(cluster)

View File

@ -22,6 +22,7 @@ from distutils.version import StrictVersion
import six
from nailgun import consts
from nailgun.extensions import fire_callback_on_before_deployment_serialization
from nailgun.extensions import fire_callback_on_deployment_data_serialization
from nailgun.logger import logger
from nailgun import objects
@ -836,9 +837,11 @@ def _execute_pipeline(data, cluster, nodes, ignore_customized):
def _invoke_serializer(serializer, cluster, nodes, ignore_customized):
fire_callback_on_before_deployment_serialization(
cluster, cluster.nodes, ignore_customized
)
objects.Cluster.set_primary_roles(cluster, nodes)
# TODO(apply only for specified subset of nodes)
objects.Cluster.prepare_for_deployment(cluster, cluster.nodes)
data = serializer.serialize(
cluster, nodes, ignore_customized=ignore_customized
)

View File

@ -253,7 +253,7 @@ class NeutronNetworkDeploymentSerializer(
attrs['transformations'].append({
'action': 'add-patch',
'bridges': [
'br-%s' % objects.Node.get_interface_by_net_name(
'br-%s' % objects.NIC.get_interface_by_net_name(
node.id,
'private'
).name,

View File

@ -22,6 +22,8 @@ import netaddr
import six
from nailgun import consts
from nailgun.extensions import \
fire_callback_on_before_provisioning_serialization
from nailgun.extensions import fire_callback_on_provisioning_data_serialization
from nailgun.logger import logger
from nailgun import objects
@ -381,7 +383,9 @@ def _execute_pipeline(data, cluster, nodes, ignore_customized):
def serialize(cluster, nodes, ignore_customized=False):
"""Serialize cluster for provisioning."""
objects.Cluster.prepare_for_provisioning(cluster, nodes)
fire_callback_on_before_provisioning_serialization(
cluster, nodes, ignore_customized
)
serializer = get_serializer_for_cluster(cluster)
data = serializer.serialize(

View File

@ -42,7 +42,6 @@ from nailgun import errors
from nailgun import consts
from nailgun import plugins
from nailgun.db.sqlalchemy.models import NodeBondInterface
from nailgun.db.sqlalchemy.models import NodeGroup
from nailgun.db.sqlalchemy.models import Task
@ -1313,26 +1312,6 @@ class TestClusterObject(BaseTestCase):
self.cluster,
['controller', 'cinder'])
def test_get_nic_interfaces_for_all_nodes(self):
nodes = self.env.nodes
interfaces = []
for node in nodes:
for inf in node.nic_interfaces:
interfaces.append(inf)
nic_interfaces = objects.Cluster.get_nic_interfaces_for_all_nodes(
self.cluster)
self.assertEqual(len(nic_interfaces), len(interfaces))
def test_get_bond_interfaces_for_all_nodes(self):
node = self.env.nodes[0]
node.bond_interfaces.append(
NodeBondInterface(name='ovs-bond0',
slaves=node.nic_interfaces))
self.db.flush()
bond_interfaces = objects.Cluster.get_bond_interfaces_for_all_nodes(
self.cluster)
self.assertEqual(len(bond_interfaces), 1)
def test_get_network_roles(self):
self.assertItemsEqual(
objects.Cluster.get_network_roles(self.cluster),