diff --git a/sa/migrations/0210_managedobjectprofile_enable_interface_autocreation.py b/sa/migrations/0210_managedobjectprofile_enable_interface_autocreation.py new file mode 100644 index 0000000000000000000000000000000000000000..b50ae387146ad6919cc596cfb153293a811c91e1 --- /dev/null +++ b/sa/migrations/0210_managedobjectprofile_enable_interface_autocreation.py @@ -0,0 +1,19 @@ +# ---------------------------------------------------------------------- +# ManagedObjectProfile.enable_interface_autocreation +# ---------------------------------------------------------------------- +# Copyright (C) 2007-2020 The NOC Project +# See LICENSE for details +# ---------------------------------------------------------------------- + +# NOC modules +from noc.core.migration.base import BaseMigration +from django.db import models + + +class Migration(BaseMigration): + def migrate(self): + self.db.add_column( + "sa_managedobjectprofile", + "enable_interface_autocreation", + models.BooleanField(default=False), + ) diff --git a/sa/models/managedobjectprofile.py b/sa/models/managedobjectprofile.py index a9671a74bf9c28e36de625b3666a9674bfbb3bd6..65b5a0f166a9e54e4c6a92868d683c6099b8fd99 100644 --- a/sa/models/managedobjectprofile.py +++ b/sa/models/managedobjectprofile.py @@ -359,6 +359,8 @@ class ManagedObjectProfile(NOCModel): # Jinja2 tempplate for segment name # object and interface context variables are exist autosegmentation_segment_name = models.CharField(max_length=255, default="{{object.name}}") + # Auto-create interface to link + enable_interface_autocreation = models.BooleanField(default=False) # Integration with external NRI and TT systems # Reference to remote system object has been imported from remote_system = DocumentReferenceField(RemoteSystem, null=True, blank=True) diff --git a/services/discovery/jobs/box/ifdesc.py b/services/discovery/jobs/box/ifdesc.py index fcefffd6e97bce9986ab41348ea644aea66eae8b..b96f7010af75b162b428618c45dd49e3e7c29c4d 100644 --- a/services/discovery/jobs/box/ifdesc.py +++ b/services/discovery/jobs/box/ifdesc.py @@ -206,6 +206,9 @@ class IfDescCheck(TopologyDiscoveryCheck): ) -> Optional[Interface]: ifaces = self.get_object_interfaces(mo) if not ifaces: + if interface: + # Object has no interfaces but may allow to auto-create one + return self.maybe_create_interface(mo, interface) return None if interface: interface = self.get_remote_interface(mo, interface) @@ -213,6 +216,8 @@ class IfDescCheck(TopologyDiscoveryCheck): iface = ifaces.get(interface) if iface: return iface + # Target interface is not found, but object may allow to auto-create one + return self.maybe_create_interface(mo, interface) if ifindex: ifi = int(ifindex) matched = [x for x in ifaces.values() if x.ifindex == ifi] @@ -229,3 +234,27 @@ class IfDescCheck(TopologyDiscoveryCheck): } self.if_cache[mo.id] = ifaces return ifaces + + def maybe_create_interface(self, mo: ManagedObject, name: str) -> Optional[Interface]: + """ + Auto-create remote interface, if possible + + :param mo: + :param name: + :return: + """ + if self.object.object_profile.ifdesc_symmetric: + return None # Meaningless for symmetric ifdesc + if ( + self.object.object_profile.enable_box_discovery_interface + or not mo.object_profile.enable_interface_autocreation + ): + return None # Auto-creation is disabled + # Create interface + self.logger.info("Auto-creating interface %s:%s", mo.name, name) + iface = Interface(managed_object=mo, type="physical", name=name) + iface.save() + # Adjust cache + if mo.id in self.if_cache: + self.if_cache[mo.id][iface.name] = iface + return iface diff --git a/ui/web/sa/managedobjectprofile/Application.js b/ui/web/sa/managedobjectprofile/Application.js index 904ca025e23dfd5c170ff77d2cab580067281e67..6549c367ea124119d1738ddd2b8493933ac39c87 100644 --- a/ui/web/sa/managedobjectprofile/Application.js +++ b/ui/web/sa/managedobjectprofile/Application.js @@ -836,8 +836,15 @@ Ext.define("NOC.sa.managedobjectprofile.Application", { bind: { disabled: "{!enableBoxDiscoveryInterface.checked}" }, - uiStyle: "medium", - colspan: 2 + uiStyle: "medium" + }, + { + name: "enable_interface_autocreation", + xtype: "checkbox", + boxLabel: __("Enable Autocreation"), + bind: { + disabled: "{enableBoxDiscoveryInterface.checked}" + } }, { name: "enable_box_discovery_id", diff --git a/ui/web/sa/managedobjectprofile/Model.js b/ui/web/sa/managedobjectprofile/Model.js index fe28103e2417d20ec8b4db08c61591b5b4af154a..87c14efeab1f0632b156c6df650d0e78c915a6eb 100644 --- a/ui/web/sa/managedobjectprofile/Model.js +++ b/ui/web/sa/managedobjectprofile/Model.js @@ -875,6 +875,10 @@ Ext.define("NOC.sa.managedobjectprofile.Model", { name: "rca_downlink_merge_window", type: "integer", defaultValue: 120 + }, + { + name: "enable_interface_autocreation", + type: "boolean" } ] });