Skip to content

Commit

Permalink
Modified validation rules to make sure L3 services only appear once f…
Browse files Browse the repository at this point in the history
…or each site
  • Loading branch information
Ilya Baldin committed Nov 9, 2023
1 parent 762f5a4 commit 8d4b095
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 17 deletions.
11 changes: 5 additions & 6 deletions fim/slivers/network_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def __str__(self):
'min_interfaces',
'num_interfaces',
'num_sites',
'num_instances',
'num_instances', # instances **per site**
'required_properties',
'forbidden_properties',
'required_interface_types'])
Expand Down Expand Up @@ -207,7 +207,7 @@ class NetworkServiceSliver(BaseSliver):
required_interface_types=[]),
ServiceType.FABNetv4: ServiceConstraintRecord(layer=NSLayer.L3, min_interfaces=1,
num_interfaces=NO_LIMIT, num_sites=1,
num_instances=NO_LIMIT,
num_instances=1,
desc='A routed IPv4 (RFC1918 addressed) FABRIC network.',
required_properties=[],
forbidden_properties=['mirror_port',
Expand All @@ -216,7 +216,7 @@ class NetworkServiceSliver(BaseSliver):
required_interface_types=[]),
ServiceType.FABNetv6: ServiceConstraintRecord(layer=NSLayer.L3, min_interfaces=1,
num_interfaces=NO_LIMIT, num_sites=1,
num_instances=NO_LIMIT,
num_instances=1,
desc='A routed IPv6 (publicly addressed) FABRIC network.',
required_properties=[],
forbidden_properties=['mirror_port',
Expand All @@ -242,7 +242,7 @@ class NetworkServiceSliver(BaseSliver):
required_interface_types=[]),
ServiceType.FABNetv4Ext: ServiceConstraintRecord(layer=NSLayer.L3, min_interfaces=1,
num_interfaces=NO_LIMIT, num_sites=1,
num_instances=NO_LIMIT,
num_instances=1,
desc='A routed IPv4 publicly addressed FABRIC '
'network capable of external connectivity.',
required_properties=[],
Expand All @@ -252,7 +252,7 @@ class NetworkServiceSliver(BaseSliver):
required_interface_types=[]),
ServiceType.FABNetv6Ext: ServiceConstraintRecord(layer=NSLayer.L3, min_interfaces=1,
num_interfaces=NO_LIMIT, num_sites=1,
num_instances=NO_LIMIT,
num_instances=1,
desc='A routed IPv6 publicly addressed FABRIC network '
'capable of external connectivity.',
required_properties=[],
Expand All @@ -276,7 +276,6 @@ def __init__(self):
self.mirror_port = None
self.mirror_direction = None


#
# Setters are only needed for things we want users to be able to set
#
Expand Down
12 changes: 2 additions & 10 deletions fim/user/network_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
# Author: Ilya Baldin (ibaldin@renci.org)
import logging
from typing import Tuple, Any, List, Set
from collections import defaultdict

import uuid

Expand Down Expand Up @@ -213,23 +214,14 @@ def get_sliver(self) -> NetworkServiceSliver:
"""
return self.topo.graph_model.build_deep_ns_sliver(node_id=self.node_id)

def __validate_nstype_constraints(self, nstype, interfaces) -> Set[str]:
def __validate_nstype_constraints(self, nstype, interfaces):
"""
Validate service constraints as encoded for each services (number of interfaces, instances, sites).
Note that interfaces is a list of interfaces belonging to nodes!
:param nstype:
:param interfaces:
:return: a list of sites the service covers
"""

# check the number of instances of this service
if NetworkServiceSliver.ServiceConstraints[nstype].num_instances != NetworkServiceSliver.NO_LIMIT:
services = self.topo.graph_model.get_all_nodes_by_class_and_type(label=ABCPropertyGraph.CLASS_NetworkService,
ntype=str(nstype))
if len(services) + 1 > NetworkServiceSliver.ServiceConstraints[self.type].num_instances:
raise TopologyException(f"Service {self.name} type {nstype} cannot have {len(services) + 1} instances. "
f"Limit: {NetworkServiceSliver.ServiceConstraints[nstype].num_instances}")

# check the number of interfaces only for experiment topologies
# in e.g. advertisements network services with no interfaces hang off dataplane switches
if isinstance(self.topo, fim.user.topology.ExperimentTopology):
Expand Down
33 changes: 32 additions & 1 deletion fim/user/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from .model_element import ModelElement, TopologyException
from ..slivers.network_node import NodeType
from ..slivers.network_link import LinkType
from ..slivers.network_service import NSLayer, ServiceType, MirrorDirection
from ..slivers.network_service import NSLayer, ServiceType, MirrorDirection, NetworkServiceSliver
from ..graph.slices.abc_asm import ABCASMPropertyGraph
from ..graph.slices.networkx_asm import NetworkxASM
from ..graph.slices.neo4j_asm import Neo4jASM
Expand Down Expand Up @@ -550,8 +550,14 @@ def validate(self):
for n in self.nodes.values():
n.validate_constraints()

check_num_instances = set()
# check network services, interfaces, sites
for s in self.network_services.values():
# check if the service type is one that requires num_instance per site validation
if NetworkServiceSliver.ServiceConstraints[s.type].num_instances != NetworkServiceSliver.NO_LIMIT:
# add this type into validation set for later
check_num_instances.add(s.type)
# perform other interface-based validations
service_interfaces = s.interface_list
node_interfaces = list()
# some services like OVS have node ports, others like Bridge, STS, PTP
Expand All @@ -569,6 +575,31 @@ def validate(self):
node_interfaces.append(si)
s.validate_constraints(node_interfaces)

# some constraints are for the entire model, like num_instances per site for NetworkServices
for nstype in check_num_instances:
# get services of this type in the model
services_of_type = set()
for s in self.network_services.values():
if s.type == nstype:
services_of_type.add(s)
# number of services of this type per site
services_per_site = defaultdict(int)
for s in services_of_type:
if s.site:
services_per_site[s.site] += 1
else:
for interface in s.interfaces:
owner = self.get_owner_node(interface)
if owner:
services_per_site[owner.site] += 1

# raise exception if needed
for site, count in services_per_site.items():
if count > NetworkServiceSliver.ServiceConstraints[nstype].num_instances:
raise TopologyException(f"Services of type {nstype} cannot have more than "
f"{NetworkServiceSliver.ServiceConstraints[nstype].num_instances} instances "
f"in each site (site {site} violates that and has {count})")


class ExperimentTopology(Topology):
"""
Expand Down
33 changes: 33 additions & 0 deletions test/slice_topology_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,39 @@ def testL3Service(self):
asm_graph.validate_graph()
self.n4j_imp.delete_all_graphs()

def testL3ServiceFail(self):
"""
Test validaton of max 1 L3 service per site of a given type
"""
self.topo.add_node(name='n1', site='RENC', ntype=f.NodeType.VM)
self.topo.add_node(name='n2', site='RENC')
self.topo.add_node(name='n3', site='UKY')
self.topo.nodes['n1'].add_component(model_type=f.ComponentModelType.SharedNIC_ConnectX_6, name='nic1')
self.topo.nodes['n2'].add_component(model_type=f.ComponentModelType.SmartNIC_ConnectX_6, name='nic1')
self.topo.nodes['n3'].add_component(model_type=f.ComponentModelType.SmartNIC_ConnectX_5, name='nic1')

s1 = self.topo.add_network_service(name='v4UKY', nstype=f.ServiceType.FABNetv4,
interfaces=self.topo.nodes['n3'].interface_list)
s2 = self.topo.add_network_service(name='v4RENC', nstype=f.ServiceType.FABNetv4,
interfaces=[self.topo.nodes['n1'].interface_list[0]])
# this one is a no-no - should attach to s2 instead
s3 = self.topo.add_network_service(name='v4RENCbad', nstype=f.ServiceType.FABNetv4,
interfaces=[self.topo.nodes['n2'].interface_list[0]])

# site property is set automagically by validate
with self.assertRaises(TopologyException):
self.topo.validate()

slice_graph = self.topo.serialize()

# Import it in the neo4j as ASM
generic_graph = self.n4j_imp.import_graph_from_string(graph_string=slice_graph)
asm_graph = Neo4jASMFactory.create(generic_graph)
# the following validation just uses cypher or networkx_query and is not as capable
# as self.topo.validate() but is much faster
asm_graph.validate_graph()
self.n4j_imp.delete_all_graphs()

def testPortMirrorService(self):
t = self.topo

Expand Down

0 comments on commit 8d4b095

Please sign in to comment.