{"id":332007,"date":"2022-04-16T09:00:15","date_gmt":"2022-04-16T09:00:15","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=332007"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=332007","title":{"rendered":"<span>\u0421\u0432\u043e\u0439 plugin \u0434\u043b\u044f Nautobot \u2014 \u044d\u0442\u043e \u0441\u043e\u0432\u0441\u0435\u043c \u043d\u0435 \u0441\u043b\u043e\u0436\u043d\u043e<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0415\u0441\u043b\u0438 \u043a\u0442\u043e-\u0442\u043e \u043d\u0435 \u0432 \u043a\u0443\u0440\u0441\u0435, Nautobot &#8212; \u0444\u043e\u0440\u043a Netbox, \u0435\u0433\u043e \u043f\u0440\u043e\u0434\u0432\u0438\u0433\u0430\u0435\u0442 \u0448\u0438\u0440\u043e\u043a\u043e \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0432 \u0443\u0437\u043a\u0438\u0445 \u043a\u0440\u0443\u0433\u0430\u0445 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 NTC (Network to code). \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043f\u043e\u0440\u044b\u0432\u0448\u0438\u0441\u044c \u0432 \u043f\u0430\u043c\u044f\u0442\u0438, \u0432\u044b \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u0442\u0435 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043a\u0440\u0430\u0441\u0438\u0432\u0443\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e \u0433\u043e\u0434\u0430 \u043f\u0440\u043e \u0437\u0430\u0442\u0438\u0440\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u043e\u0432 Netbox &#8212; \u0434\u0435\u043b\u043e \u043a\u0430\u043a \u0440\u0430\u0437 \u043a\u0430\u0441\u0430\u043b\u043e\u0441\u044c Nautobot \u0438 NTC. \u041d\u043e \u0440\u0435\u0447\u044c \u043f\u043e\u0439\u0434\u0435\u0442 \u043d\u0435 \u043e \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0445 \u043f\u0440\u0438\u043b\u0438\u0447\u0438\u044f \u043e\u043f\u0435\u043d\u0441\u043e\u0440\u0441\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430, \u0430 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043b\u0435\u0433\u043a\u043e \u0438 \u043d\u0435\u043f\u0440\u0438\u043d\u0443\u0436\u0434\u0435\u043d\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u043f\u043b\u0430\u0433\u0438\u043d \u0442\u043e\u043c\u0443, \u043a\u0442\u043e \u0432\u044b\u0431\u0440\u0430\u043b \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u0438\u0441\u0442\u0438\u043d\u044b \u0438 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 Nautobot, \u0430 \u043d\u0435 Netbox. \u0425\u043e\u0442\u044f \u044f \u0443\u0432\u0435\u0440\u0435\u043d, \u0447\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043b\u0430\u0433\u0438\u043d\u0430, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u043e\u0439\u0434\u0435\u0442 \u0440\u0435\u0447\u044c \u0434\u0430\u043b\u044c\u0448\u0435, \u0442\u0430\u043a \u0436\u0435 \u043b\u0435\u0433\u043a\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0438 \u0434\u043b\u044f Netbox.<\/p>\n<h2>\u041f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0438 <\/h2>\n<p>\u0412 \u043d\u0430\u0448\u0435\u0439 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u0435\u0442\u0438 \u0438\u043c\u0435\u0435\u0442\u0441\u044f ~50 \u0444\u0438\u043b\u0438\u0430\u043b\u043e\u0432, \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u044b \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u0441 \u043e\u0431\u0449\u0438\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0440\u0442\u043e\u0432 \u043e\u0442 300 \u0434\u043e 500. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u0444\u0438\u043b\u0438\u0430\u043b\u043e\u0432 \u0431\u043e\u043b\u044c\u0448\u0435, \u043d\u043e \u043d\u0430\u0447\u043d\u0435\u043c \u043c\u044b \u0438\u043c\u0435\u043d\u043d\u043e \u0441 \u044d\u0442\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0432\u043e \u0432\u0441\u0435\u0445 \u0438\u0437 \u043d\u0438\u0445 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u0435 Cisco. \u042d\u0442\u043e \u0432\u0430\u0436\u043d\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0430\u0448 \u043f\u043b\u0430\u0433\u0438\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043a\u0430 \u043e\u0434\u043d\u043e\u0432\u0435\u043d\u0434\u043e\u0440\u043d\u044b\u043c.<\/p>\n<p>\u041f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430 \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0437\u043d\u0430\u0442\u044c: <\/p>\n<ol>\n<li>\n<p>\u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0440\u0430\u0437 \u0431\u044b\u043b\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443<\/p>\n<\/li>\n<li>\n<p>\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441 \u043a\u0430\u043a\u0438\u043c MAC\/IP \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0438 \u0432 \u043a\u0430\u043a\u043e\u043c VLAN \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443<\/p>\n<\/li>\n<\/ol>\n<p>\u201c\u041f\u043e\u0441\u0442\u043e\u0439-\u043a\u0430, \u0430\u0432\u0442\u043e\u0440\u201d &#8212; \u0441\u043a\u0430\u0436\u0435\u0442\u0435 \u0432\u044b &#8212; \u201c\u0442\u0430\u043a\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u044f \u0443\u0436\u0435 \u0433\u0434\u0435-\u0442\u043e \u0432\u0438\u0434\u0435\u043b\u201d. \u0421\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0442\u043e\u0447\u043d\u043e, \u044d\u0442\u043e switchmap (\u0433\u0443\u0433\u043b\u0438\u0442\u0441\u044f \u043f\u043e \u0441\u043b\u043e\u0432\u043e\u0441\u043e\u0447\u0435\u0442\u0430\u043d\u0438\u044e cisco switchmap). \u0427\u0442\u043e \u0436, \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u043e\u0431\u0435\u0449\u0430\u043b, \u0447\u0442\u043e \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u043d\u043e\u0432\u0430\u044f \u0438\u0434\u0435\u044f \u0434\u043b\u044f \u043f\u043b\u0430\u0433\u0438\u043d\u0430. \u041d\u043e \u044d\u0442\u0430 \u0438\u0434\u0435\u044f \u043e\u0447\u0435\u043d\u044c \u0445\u043e\u0440\u043e\u0448\u043e \u043b\u043e\u0436\u0438\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Nautobot \u043a\u0430\u043a \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 SoT.<\/p>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u0445\u043e\u0442\u0435\u043b \u043e\u0442\u0434\u0430\u0442\u044c \u0434\u0430\u043d\u044c \u0443\u0432\u0430\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c switchmap \u0438 \u0443\u0432\u0435\u043a\u043e\u0432\u0435\u0447\u0438\u0442\u044c \u0438\u0445 \u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0435 \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438 \u0441\u0432\u043e\u0435\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430. \u041d\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u044e\u0449\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438 &#8212; nautobot-porthistory-plugin (Sorry guys)<\/p>\n<h2>\u0421\u0442\u0435\u043a  <\/h2>\n<p>\u041d\u0443 \u0442\u0443\u0442 \u0432\u0441\u0451 \u043f\u0440\u043e\u0441\u0442\u043e. \u0420\u0430\u0437 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c Nautobot, \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 Python \u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0434\u0443\u043b\u0438 Django. \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432, \u0432\u0441\u0435 \u0432\u043d\u0443\u0442\u0440\u0438 Nautobot. \u0417\u0430\u0431\u0438\u0440\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0431\u0443\u0434\u0435\u043c \u043f\u043e SNMP. \u041a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u044b &#8212; Cisco, \u0432\u0441\u0435 \u0437\u0430\u0432\u0435\u0434\u0435\u043d\u044b \u0432 Nautobot, \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f primary address \u0438 \u0432\u0441\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b, \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0444\u0438\u043b\u0438\u0430\u043b\u0430 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u0432 \u0438 VLAN.<\/p>\n<h2>\u0427\u0442\u043e \u043d\u0430\u0441\u0447\u0435\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438?<\/h2>\n<p>\u0423 Nautobot \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f. \u0415\u0449\u0435 \u0431\u044b, \u0432\u0435\u0434\u044c \u043e\u043d\u0430 \u043f\u043e\u0447\u0442\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0443 Netbox. \u041d\u043e \u0432\u0441\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0438 \u043e\u0442\u043b\u0438\u0447\u0438\u044f \u043e\u0442 Netbox \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u0445\u043e\u0440\u043e\u0448\u043e, \u0437\u0430 \u044d\u0442\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043f\u0430\u0441\u0438\u0431\u043e. \u0420\u0430\u0437\u0434\u0435\u043b \u043f\u0440\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u043e\u0439 &#8212; <a href=\"https:\/\/nautobot.readthedocs.io\/en\/stable\/plugins\/development\/\" rel=\"noopener noreferrer nofollow\">https:\/\/nautobot.readthedocs.io\/en\/stable\/plugins\/development\/<\/a> <\/p>\n<p>\u041d\u043e \u0432\u043e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0435\u0440\u0432\u043e\u0435 \u043f\u0440\u043e\u0447\u0442\u0435\u043d\u0438\u0435 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430 \u043d\u0435 \u0434\u0430\u043b\u043e \u043e\u0442\u0432\u0435\u0442\u044b \u043d\u0430 \u043c\u043d\u043e\u0433\u0438\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b (\u0432\u043f\u0440\u043e\u0447\u0435\u043c, \u043a\u0430\u043a \u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435). \u041c\u043d\u043e\u0433\u0438\u0435 \u0437\u043d\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0432\u044b\u0434\u0435\u0440\u0433\u0438\u0432\u0430\u0442\u044c \u0438\u0437 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0441\u0430\u043c\u043e\u0433\u043e Nautobot, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u043a\u043e\u0438\u0445 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0432\u0441\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0438 \u0431\u043e\u043b\u044c\u0448\u0435. <\/p>\n<h2>\u0418\u0442\u0430\u043a, \u043d\u0430\u0447\u043d\u0435\u043c<\/h2>\n<p>\u0412\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438. \u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0447\u0442\u043e \u043f\u043e\u043b\u0435\u0433\u0447\u0435 &#8212; \u043d\u0430\u0443\u0447\u0438\u043c \u043f\u043b\u0430\u0433\u0438\u043d \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0440\u0442\u043e\u0432 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430, \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u044d\u0442\u0443 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c.<\/p>\n<p>\u041f\u043e \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u044d\u0442\u0430\u043f\u0430 \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u043d\u0430\u0448 \u043f\u043b\u0430\u0433\u0438\u043d \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ul>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0432 \u0411\u0414 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u0445;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0441\u0442 job, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441 \u043d\u0443\u0436\u043d\u043e\u0439 \u043d\u0430\u043c \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c\u044e \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u0443\u044e \u0442\u0430\u0431\u043b\u0438\u0446\u0443;<\/p>\n<\/li>\n<li>\n<p>\u0438\u0437\u043c\u0435\u043d\u0438\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432\u044b\u0432\u043e\u0434\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0435 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u0432\u0438\u0434\u043d\u043e, \u043a\u0430\u043a\u0438\u0435 \u043f\u043e\u0440\u0442\u044b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0434\u043d\u0435\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043a\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u043c \u0441\u0442\u0440\u043e\u0433\u043e \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435 nautobot-plugin-porthistory \u0441\u043a\u0435\u043b\u0435\u0442 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 (\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043f\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c nautobot).<\/p>\n<blockquote>\n<p><em>Nautobot includes a command to help create the plugin directory: nautobot-server startplugin [app_name]<\/em>  <\/p>\n<\/blockquote>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 <code>nautobot-server startplugin nautobot_porthistory_plugin<\/code> \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043d\u0430\u0431\u043e\u0440 \u043f\u0430\u043f\u043e\u043a \u0438 \u0444\u0430\u0439\u043b\u043e\u0432:  <\/p>\n<pre><code class=\"bash\">. \u2514\u2500\u2500 nautobot_porthistory_plugin     \u251c\u2500\u2500 __init__.py     \u251c\u2500\u2500 migrations     \u2502   \u2514\u2500\u2500 __init__.py     \u251c\u2500\u2500 models.py     \u251c\u2500\u2500 navigation.py     \u251c\u2500\u2500 tests     \u2502   \u251c\u2500\u2500 __init__.py     \u2502   \u251c\u2500\u2500 test_models.py     \u2502   \u2514\u2500\u2500 test_views.py     \u251c\u2500\u2500 urls.py     \u2514\u2500\u2500 views.py<\/code><\/pre>\n<p>\u041d\u0443 \u0447\u0442\u043e, \u0444\u0430\u0439\u043b\u044b \u0435\u0441\u0442\u044c, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u043e\u043b\u043d\u0438\u043c \u0438\u0445 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u043c \u043a\u043e\u0434\u043e\u043c.<\/p>\n<p>\u0424\u0430\u0439\u043b \u043f\u0430\u043a\u0435\u0442\u0430\u00a0 __init__.py \u0443\u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043d\u0443\u0436\u043d\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432. \u0412\u0441\u0451, \u0447\u0442\u043e \u043d\u0430\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f, \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u043e\u043f\u0446\u0438\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b \u0434\u043b\u044f \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e.<\/p>\n<pre><code class=\"python\">__init__.py \"\"\"nautobot_porthistory_plugin Plugin Initilization.\"\"\"  from nautobot.extras.plugins import PluginConfig  class NautobotPorthistoryPluginConfig(PluginConfig):     \"\"\"Plugin configuration for the nautobot_porthistory_plugin plugin.\"\"\"      name = \"nautobot_porthistory_plugin\"  # Raw plugin name; same as the plugin's source directory.     verbose_name = \"nautobot_porthistory_plugin\"  # Human-friendly name for the plugin.     base_url = \"nautobot_porthistory_plugin\"  # (Optional) Base path to use for plugin URLs. Defaulting to app_name.     required_settings = []  # A list of any configuration parameters that must be defined by the user.     min_version = \"1.0.0\"  # Minimum version of Nautobot with which the plugin is compatible.     max_version = \"1.999\"  # Maximum version of Nautobot with which the plugin is compatible.     default_settings = {}  # A dictionary of configuration parameters and their default values.     caching_config = {}  # Plugin-specific cache configuration.      # \u0410 \u0432\u043e\u0442 \u0438 \u043d\u0443\u0436\u043d\u0430\u044f \u043d\u0430\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f     required_settings = ['switches_role_slug']     default_settings = {         'min_idle_days': 14,         'snmp_community': 'public',         'workers': 50,     }  config = NautobotPorthistoryPluginConfig<\/code><\/pre>\n<p>\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0432 \u043f\u043b\u0430\u0433\u0438\u043d:<\/p>\n<ul>\n<li>\n<p><code>switches_role_slug<\/code> &#8212; \u0420\u043e\u043b\u044c, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b<\/p>\n<\/li>\n<li>\n<p><code>min_idle_days<\/code> &#8212; \u0415\u0441\u043b\u0438 \u043f\u043e\u0440\u0442 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u0435\u043d\u044c\u0448\u0435 \u0434\u043d\u0435\u0439, \u043e\u043d \u043d\u0430\u0441 \u043d\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442<\/p>\n<\/li>\n<li>\n<p><code>workers<\/code> &#8212; \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044e<\/p>\n<\/li>\n<\/ul>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0432 models.py. \u0412 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 <em>UnusedPorts<\/em> \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f, \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e ID \u0441\u0430\u043c\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430. \u041a\u0430\u043a \u0432\u0438\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e.<\/p>\n<pre><code class=\"python\">models.py \"\"\"Model definition for nautobot_porthistory_plugin.\"\"\"  from django.db import models  from nautobot.core.models import BaseModel from nautobot.dcim.fields import MACAddressCharField  class UnusedPorts(BaseModel):     # \u0414\u0430\u0442\u0430\/\u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e output \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430       updated = models.DateTimeField(auto_now=True)     last_output = models.DateTimeField()     interface = models.ForeignKey(         to=\"dcim.Interface\",         on_delete=models.CASCADE,         blank=False,     )          def __str__(self):         return f'{self.interface.name} - {self.last_output}'<\/code><\/pre>\n<p>\u0410\u0440\u0433\u0443\u043c\u0435\u043d\u0442 <code>auto_now=True<\/code><em> <\/em>\u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u043f\u043e\u043b\u0435 <em>updated <\/em>\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438<\/p>\n<p>\u0424\u0430\u0439\u043b\u044b navigation.py, urls.py \u0438 views.py \u043f\u043e\u043a\u0430 \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u044b, \u043e\u0441\u0442\u0430\u0432\u0438\u043c \u0438\u0445 \u0432 \u043f\u0435\u0440\u0432\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435. \u042d\u0442\u0438 \u0442\u0440\u0438 \u0444\u0430\u0439\u043b\u0430 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u044d\u0442\u0430\u043f\u0435, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u043f\u043b\u0430\u0433\u0438\u043d \u0432 \u043c\u0435\u043d\u044e. \u041d\u043e, \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e, \u0435\u0441\u0442\u044c \u043d\u044e\u0430\u043d\u0441. \u0418\u043c\u043f\u043e\u0440\u0442\u044b \u0432 views.py \u201c\u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438\u201d \u0441\u0441\u044b\u043b\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043d\u0435\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 django, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043b\u043e\u0432\u0438\u0442\u044c \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435, \u0437\u0430\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u043c \u0441\u0442\u0440\u043e\u0447\u043a\u0443 <code>from django.views.generic import views<\/code><\/p>\n<p>\u0418\u0442\u0430\u043a, \u0443 \u043d\u0430\u0441 \u0432 \u0411\u0414 \u0435\u0441\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0430, \u043d\u043e \u0432 \u043d\u0435\u0439 \u043d\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0445. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0434\u0436\u043e\u0431 (job \u0432 Nautobot &#8212; \u043a\u0430\u043a custom script \u0438 report \u0432 Netbox), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0431\u0435\u0440\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0441 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u043e\u0432, \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0435\u0435 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442 \u0432 \u043d\u0430\u0448\u0435\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0444\u0430\u0439\u043b jobs.py \u0441 \u043a\u043b\u0430\u0441\u0441\u043e\u043c (\u0434\u0436\u043e\u0431\u043e\u043c) <em>UnusedPortsUpdate<\/em><\/p>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0435\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u043e\u0441\u0442\u0430:<\/p>\n<ol>\n<li>\n<p>\u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u043e\u0432 &#8212; \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440 \u043f\u043e d<em>evice_role.slug = switches_role_slug<\/em> \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430<\/p>\n<\/li>\n<li>\n<p>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u043f\u043e SNMP &#8212; uptime \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430<\/p>\n<\/li>\n<li>\n<p>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u043f\u043e SNMP &#8212; \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 ifindex \u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439 \u043f\u043e\u0440\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u043f\u043e SNMP &#8212; Last output \u043d\u0430 \u043f\u043e\u0440\u0442\u0430\u0445<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 Last output \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u0435\u0441\u0442\u044c \u043b\u0438 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043f\u043e \u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443. \u0415\u0441\u043b\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u0442\u0443 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0430\u0432\u043d\u043e\u0439 \u0434\u0430\u0442\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 (\u043f.2). \u0415\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0435\u0441\u0442\u044c, \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u0442\u0443 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 Last output, \u043f\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432 \u0434\u043d\u0438,\u00a0 \u043c\u0435\u043d\u044c\u0448\u0435, \u0447\u0435\u043c <em>min_idle_days<\/em> \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430, \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0437\u0430\u043f\u0438\u0441\u044c \u043e \u043f\u043e\u0440\u0442\u0435 \u0438\u0437 \u0442\u0430\u0431\u043b\u0438\u0446\u044b. \u0418\u043d\u0430\u0447\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c\/\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u043f\u0438\u0441\u044c.<\/p>\n<\/li>\n<\/ol>\n<p>\u041d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0432 \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c Site, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u0444\u0438\u043b\u0438\u0430\u043b\u0443.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 jobs.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">jobs.py  from nautobot.dcim.models import Device, DeviceRole, Site, Interface from nautobot.extras.jobs import Job, ObjectVar from nautobot.extras.models import Status from django.conf import settings  from nautobot_porthistory_plugin.models import UnusedPorts  import asyncio import aiosnmp  from collections import defaultdict from netutils.interface import canonical_interface_name from datetime import datetime, timedelta  class UnusedPortsUpdate(Job):      class Meta:         name = \"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043d\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u0445\"      site = ObjectVar(         model=Site,         label='\u0411\u042e',         required=False     )      async def bulk_snmp(self, device, oid_list, community):         oid_results = {}         try:             async with aiosnmp.Snmp(                 host=device,                 port=161,                 community=community,                 timeout=5,                 retries=3,                 max_repetitions=10,             ) as snmp:                 oid_bulk_result = {}                 for oid in oid_list:                     reply = await snmp.bulk_walk(oid)                     for index in reply:                         oid_bulk_result[index.oid] = index.value                     oid_results[oid] = oid_bulk_result                  return (device, oid_results)          except Exception as error:             return (device, error)         return (device, None)      async def bulk_snmp_with_semaphore(self, semaphore, function, *args, **kwargs):         async with semaphore:             return await function(*args, **kwargs)      async def async_bulk_snmp(self, devices, oid_list, community, workers):         semaphore = asyncio.Semaphore(workers)         coroutines = [             self.bulk_snmp_with_semaphore(semaphore, self.bulk_snmp, device, oid_list, community)             for device in devices         ]         result = []         for future in asyncio.as_completed(coroutines):             result.append(await future)         return result      def round_datetime(self, date):         date_tuple = date.timetuple()         return datetime(year=date_tuple.tm_year,                         month=date_tuple.tm_mon,                         day=date_tuple.tm_mon,                         hour=date_tuple.tm_hour, minute=0, second=0, microsecond=0                         )      def run(self, data, commit):         # \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c job \u043c\u043e\u0433\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 is_superuser         if not self.request.user.is_superuser:             self.log_info(message='\u041d\u0435\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a')             return          PLUGIN_CFG = settings.PLUGINS_CONFIG['nautobot_porthistory_plugin']         COMMUNITY = PLUGIN_CFG['snmp_community']         MIN_IDLE_DAYS = PLUGIN_CFG.get('min_idle_days', 14)         SWITCHES_ROLE_SLUG = PLUGIN_CFG['switches_role_slug']         WORKERS = PLUGIN_CFG['workers']         STATUS_ACTIVE = Status.objects.get(slug='active')          # \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432         devices = [] #\u044d\u0442\u043e\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c \u0432 \u043c\u043e\u0434\u0443\u043b\u044c snmp         device_dict = defaultdict(dict)         device_role = DeviceRole.objects.filter(slug__in=SWITCHES_ROLE_SLUG)         if data['site']:             nb_devices = Device.objects.filter(site=data['site'], device_role__in=device_role, status=STATUS_ACTIVE)         else:             nb_devices = Device.objects.filter(device_role__in=device_role, status=STATUS_ACTIVE)                      for nb_device in nb_devices:             if nb_device.platform and nb_device.platform.napalm_driver and nb_device.platform.napalm_driver == 'cisco_iosxe' and nb_device.primary_ip4:                 primary_ip = str(nb_device.primary_ip4).split('\/')[0]                 devices.append(primary_ip)                 device_dict[primary_ip]['device'] = nb_device                 device_dict[primary_ip]['interfaces'] = {}                 device_dict[primary_ip]['ifindexes'] = {}                 device_interfaces = Interface.objects.filter(device_id=nb_device)                 for intf in device_interfaces:                     device_dict[primary_ip]['interfaces'][intf.name] = [intf]          # \u043f\u043e\u043b\u0443\u0447\u0438\u043c uptime \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e SNMP (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)         # \u0438 \u0437\u0430\u043d\u0435\u0441\u0435\u043c \u044d\u0442\u0443 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a         oid_list = ['.1.3.6.1.6.3.10.2.1.3']         results = asyncio.run(self.async_bulk_snmp(devices, oid_list, COMMUNITY, WORKERS))         for device_ip, device_result in results:             if type(device_result) != dict:                 self.log_warning(obj=device_dict[device_ip]['device'],message=f'\u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e SNMP - {device_result}')                 continue             for oid, oid_result in device_result.items():                 for uptime in oid_result.values():                     device_dict[device_ip]['uptime'] = uptime                     boottime = datetime.now() - timedelta(seconds=uptime)                     device_dict[device_ip]['boottime'] = boottime              # \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u0438 \u0438\u0445 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0441 \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e SNMP         # \u0438 \u0437\u0430\u043d\u0435\u0441\u0435\u043c \u044d\u0442\u0443 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a         oid_list = ['.1.3.6.1.2.1.31.1.1.1.1']         results = asyncio.run(self.async_bulk_snmp(devices, oid_list, COMMUNITY, WORKERS))         for device_ip, device_result in results:             if type(device_result) != dict or 'uptime' not in device_dict[device_ip]:                 continue             for oid, oid_result in device_result.items():                 for index, index_result in oid_result.items():                     ifindex = index.split('.')[-1]                     canonical_intf_name = canonical_interface_name(index_result.decode(\"utf-8\"))                     if canonical_intf_name in device_dict[device_ip]['interfaces']:                         device_dict[device_ip]['ifindexes'][ifindex] = canonical_intf_name          # \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e output \u043f\u043e SNMP         oid_list = ['.1.3.6.1.4.1.9.2.2.1.1.4']         results = asyncio.run(self.async_bulk_snmp(devices, oid_list, COMMUNITY, WORKERS))         output = ''         for device_ip, device_result in results:             if type(device_result) != dict or 'uptime' not in device_dict[device_ip]:                 continue             nb_device = device_dict[device_ip]['device']             boottime = device_dict[device_ip]['boottime']             uptime = device_dict[device_ip]['uptime']             output += f'{nb_device.name} - power on {boottime}\\n'             unused_port_count = 0             for oid, oid_result in device_result.items():                 for index, time_from_last_output in oid_result.items():                     ifindex = index.split('.')[-1]                     if ifindex in device_dict[device_ip]['ifindexes']:                         intf_name = device_dict[device_ip]['ifindexes'][ifindex]                         nb_interface = device_dict[device_ip]['interfaces'][intf_name][0]                         if time_from_last_output &lt; 0 or time_from_last_output \/ 1000 > uptime - 300:                             unused_port_count += 1                             unused_port, created = UnusedPorts.objects.get_or_create(                                 interface=nb_interface,                                 defaults={                                     'last_output': boottime                                 }                             )                             unused_port.save()                         else:                             last_output = datetime.now() - timedelta(seconds=round(time_from_last_output\/1000))                             if 1000 * 60 * 60 * 24 * MIN_IDLE_DAYS > time_from_last_output:                                 # \u043f\u0440\u043e\u0448\u043b\u043e \u043c\u0435\u043d\u044c\u0448\u0435 MIN_IDLE_DAYS \u0434\u043d\u0435\u0439                                 UnusedPorts.objects.filter(interface=nb_interface).delete()                             else:                                 unused_port_count += 1                                 unused_port, created = UnusedPorts.objects.get_or_create(                                     interface=nb_interface,                                     defaults={                                         'last_output': last_output                                     }                                 )                                 if not created:                                     unused_port.last_output = last_output                                     unused_port.save()             output += f'\u043d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0438 {MIN_IDLE_DAYS} \u0434\u043d. \u043f\u043e\u0440\u0442\u043e\u0432 - {unused_port_count}\\n'          return output  jobs = [UnusedPortsUpdate] <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0412 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0434\u0436\u043e\u0431\u0430, \u0447\u0442\u043e\u0431\u044b \u0432\u0441\u0435\u0433\u0434\u0430 \u0438\u043c\u0435\u0442\u044c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u043d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u043f\u043e\u0440\u0442\u043e\u0432. \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0432 Nautobot \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0435\u0441\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0448\u0435\u0434\u0443\u043b\u0435\u0440 \u0434\u0436\u043e\u0431\u043e\u0432. \u0412\u0441\u0435\u0433\u043e \u043b\u0438\u0448\u044c \u043d\u0430\u0434\u043e \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0434\u0436\u043e\u0431\u0430 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0443\u0447\u0438\u043c \u043f\u043b\u0430\u0433\u0438\u043d \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0444\u0430\u0439\u043b\u044b template_content.py \u0438 templates\/unused_ports.html<\/p>\n<p>\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0433\u043e\u0432\u043e\u0440\u044f\u0442 \u0441\u0430\u043c\u0438 \u0437\u0430 \u0441\u0435\u0431\u044f &#8212; \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0438 \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u044c \u0431\u043b\u043e\u043a HTML. \u041e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0435\u043d\u043d\u044b\u0439 \u0431\u043b\u043e\u043a \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 Device, \u0432 \u043f\u0440\u0430\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438.<\/p>\n<pre><code class=\"python\">template_content.py  from nautobot.extras.plugins import PluginTemplateExtension from django.conf import settings  from .models import UnusedPorts  class DeviceUnusedPorts(PluginTemplateExtension):     \"\"\"Template extension to display unused ports on the right side of the page.\"\"\"      model = 'dcim.device'      def right_page(self):         PLUGIN_CFG = settings.PLUGINS_CONFIG['nautobot_porthistory_plugin']         SWITCHES_ROLE_SLUG = PLUGIN_CFG['switches_role_slug']         MIN_IDLE_DAYS = PLUGIN_CFG.get('min_idle_days', 14)         device = self.context['object']          if device.device_role.slug in SWITCHES_ROLE_SLUG:             device_intefaces = device.interfaces.values_list(\"id\", flat=True)             unused_ports = UnusedPorts.objects.filter(interface_id__in=device_intefaces)             unused_ports_with_delta = []             for port in unused_ports:                 unused_ports_with_delta.append({                     'interface_name': port.interface.name,                     'last_output': port.last_output.strftime(\"%d.%m.%Y %H:%M\"),                     'updated': port.updated.strftime(\"%d.%m.%Y %H:%M\"),                     'delta': str(port.updated - port.last_output).split()[0]                 })             return self.render('unused_ ports.html', extra_context={                 'unused_ports': unused_ports_with_delta,                 'min_idle_days': MIN_IDLE_DAYS             })         else:             return ''  template_extensions = [DeviceUnusedPorts] <\/code><\/pre>\n<pre><code class=\"python\">templates\/unused_ports.html  &lt;div class=\"panel panel-default\"> &lt;div class=\"panel-heading\">&lt;strong>\u041d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u043e\u0440\u0442\u044b (\u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 {{ min_idle_days }} \u0434\u043d.)&lt;\/strong>&lt;\/div> &lt;table class=\"table table-hover panel-body attr-table\"> &lt;tr> &lt;td>\u041f\u043e\u0440\u0442&lt;\/td> &lt;td>\u0412\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438&lt;\/td> &lt;td>\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e (UTC)&lt;\/td> &lt;\/tr> {% for item in unused_ports %} &lt;tr> &lt;td>{{ item.interface_name }}&lt;\/td> &lt;td>{{ item.last_output }} (\u043f\u0440\u043e\u0448\u043b\u043e ~{{ item.delta }} \u0434\u043d.)&lt;\/td> &lt;td>{{ item.updated }}&lt;\/td> &lt;\/tr> {% endfor %} &lt;\/table> &lt;\/div> <\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0441\u043c\u0435\u0448\u0430\u0442\u044c \u0432\u0441\u0435 \u0438\u043d\u0433\u0440\u0435\u0434\u0438\u0435\u043d\u0442\u044b \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c, \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c:<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 &#8212; \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c Django \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u0432\u0435\u0440\u0441\u0438\u043e\u043d\u043d\u043e\u0441\u0442\u0438 \u0411\u0414 \u0432 \u043e\u0431\u0435\u0440\u0442\u043a\u0435 Nautobot. <br \/><code>nautobot-server makemigrations<\/code> (\u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0438\u0437 \u043f\u0430\u043f\u043a\u0438 \u043f\u043b\u0430\u0433\u0438\u043d\u0430)<\/p>\n<\/li>\n<li>\n<p>\u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0444\u0430\u0439\u043b\u044b setup.py \u0438 MANIFEST.in<\/p>\n<\/li>\n<li>\n<p>\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u043f\u043b\u0430\u0433\u0438\u043d \u043f\u0440\u043e\u0441\u0442\u044b\u043c <code>pip3 install .<\/code> \u0438\u0437 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u043f\u043b\u0430\u0433\u0438\u043d\u0430<\/p>\n<\/li>\n<li>\n<p>\u0432\u043a\u043b\u044e\u0447\u0438\u043c \u043f\u043b\u0430\u0433\u0438\u043d \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u043c \u0444\u0430\u0439\u043b\u0435 nautobot \u0438 \u0442\u0430\u043c \u0436\u0435 \u0443\u043a\u0430\u0436\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043b\u0430\u0433\u0438\u043d\u0430<br \/><code>PLUGINS = [<br \/>    'nautobot_porthistory_plugin',<br \/> ]<br \/>PLUGINS_CONFIG = {<br \/>    'nautobot_porthistory_plugin': {<br \/>    'switches_role_slug': ['Access-switch'],<br \/>    'min_idle_days': 14,<br \/>    }<br \/> }<\/code><\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043f\u043e\u0441\u0442-\u0438\u043d\u0441\u0442\u0430\u043b\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0441\u043a\u0440\u0438\u043f\u0442 <code>nautobot-server post_upgrade<\/code>. \u041e\u043d \u043e\u0431\u043d\u043e\u0432\u0438\u0442 \u0411\u0414 \u0438 \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 HTML \u0444\u0430\u0439\u043b\u044b \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043f\u0430\u043f\u043a\u0443.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u044b: <code>sudo systemctl restart nautobot nautobot-worker<\/code><\/p>\n<\/li>\n<\/ol>\n<p>\u0413\u043e\u0442\u043e\u0432\u043e! \u041c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u0436\u043e\u0431 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0435\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/611\/1bc\/fb3\/6111bcfb3bc201b66b9e6fe862f715f9.png\" alt=\"\u041d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u043e\u0440\u0442\u044b \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430\" title=\"\u041d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u043e\u0440\u0442\u044b \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430\" width=\"1349\" height=\"708\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/611\/1bc\/fb3\/6111bcfb3bc201b66b9e6fe862f715f9.png\"\/><figcaption>\u041d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u043e\u0440\u0442\u044b \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430<\/figcaption><\/figure>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u043b\u0430\u0433\u0438\u043d\u0430, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043d\u0430 \u044d\u0442\u043e\u043c \u044d\u0442\u0430\u043f\u0435, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 &#8212; <a href=\"https:\/\/github.com\/iontzev\/nautobot-porthistory-plugin\/tree\/part_1\" rel=\"noopener noreferrer nofollow\"><u>https:\/\/github.com\/iontzev\/nautobot-porthistory-plugin\/tree\/part_1<\/u><\/a><\/p>\n<h2>\u042d\u0442\u0430\u043f \u0432\u0442\u043e\u0440\u043e\u0439 &#8212; \u043f\u0440\u043e\u043a\u0430\u0447\u043a\u0430 \u043f\u043b\u0430\u0433\u0438\u043d\u0430<\/h2>\n<p>\u0412\u0442\u043e\u0440\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u0422\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0435\u0435, \u0432\u0435\u0434\u044c \u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u043d\u044f\u0442\u044c, \u0447\u0442\u043e \u0435\u0449\u0435 \u043c\u043e\u0433\u0443\u0442 \u043f\u043b\u0430\u0433\u0438\u043d\u044b \u0432 Nautobot. \u0421\u043f\u0435\u0440\u0432\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u0449\u0435 \u043e\u0434\u043d\u0443 \u043c\u043e\u0434\u0435\u043b\u044c \u0432 \u0411\u0414 &#8212; \u0442\u0430\u0431\u043b\u0438\u0446\u0443, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c MAC \u0438 IP \u0430\u0434\u0440\u0435\u0441\u0430 \u0441 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u043e\u0439 \u043a \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443. \u0421\u043f\u043e\u0441\u043e\u0431 \u0443\u0436\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b\u0439 &#8212; \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c models.py \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044e <code>nautobot-server makemigrations<\/code><\/p>\n<pre><code class=\"python\">from nautobot.dcim.fields import MACAddressCharField  class MAConPorts(BaseModel):     # MAC \u0438 IP \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430       updated = models.DateTimeField(auto_now=True)     mac = MACAddressCharField(blank=False, verbose_name=\"MAC Address\")     vlan = models.ForeignKey(         to=\"ipam.VLAN\",         on_delete=models.CASCADE,         blank=False,     )     ipaddress = models.ForeignKey(         to=\"ipam.IPAddress\",         on_delete=models.SET_NULL,         default=None,         blank=True,         null=True,     )     interface = models.ForeignKey(         to=\"dcim.Interface\",         on_delete=models.CASCADE,         blank=False,     )     device = models.ForeignKey(         to=\"dcim.Device\",         on_delete=models.CASCADE,         blank=False,     )          def __str__(self):         return f'{self.intervace} - VLAN {seld.vlan.vid} MAC {self.mac}'      class Meta:         verbose_name_plural = 'MAC and IP on switches ports'<\/code><\/pre>\n<p>\u0412 jobs.py \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u043a\u043b\u0430\u0441\u0441 &#8212; \u043e\u043d \u0436\u0435 \u0434\u0436\u043e\u0431. \u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0435\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u044b \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435\u0441\u043b\u043e\u0436\u043d\u044b\u0439:<\/p>\n<ul>\n<li>\n<p>\u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e SNMP \u0432\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u0440\u043e\u043b\u044c\u044e\u00a0<em>device_role.slug = switches_role_slug<\/em> \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 &#8212; \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c mac address table \u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044f MAC \u0438\u043c\u0435\u043d\u0430\u043c \u043f\u043e\u0440\u0442\u043e\u0432.<\/p>\n<\/li>\n<li>\n<p>\u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u043e SNMP \u0432\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u0440\u043e\u043b\u044c\u044e <em>device_role.slug = routers_role_slug<\/em> \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 &#8212; \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c ARP \u0442\u0430\u0431\u043b\u0438\u0446\u0443. \u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u0435\u043c, \u0447\u0442\u043e \u043a\u043e\u043d\u0444\u0438\u0433 \u0442\u043e\u0436\u0435 \u043d\u0430\u0434\u043e \u043f\u043e\u0434\u043f\u0440\u0430\u0432\u0438\u0442\u044c &#8212; \u043e\u043d \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 __init__.py<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c hostname \u043f\u043e IP<\/p>\n<\/li>\n<li>\n<p>\u0435\u0441\u043b\u0438 IP \u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u0432\u043d\u0435\u0441\u0435\u043d \u0432 Nautobot, \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0435 MAC \u0438 IP \u043a \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432<\/p>\n<\/li>\n<\/ul>\n<p>\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 MAC \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 \u043f\u0440\u043e\u043f\u0430\u0434\u0435\u0442 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438, \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u0435\u0433\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439, \u0435\u0441\u043b\u0438 \u043d\u0430 \u044d\u0442\u043e\u043c \u043f\u043e\u0440\u0442\u0443 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u043b\u043e\u0441\u044c.<\/p>\n<details class=\"spoiler\">\n<summary>\u041d\u043e\u0432\u044b\u0439 \u0434\u0436\u043e\u0431 \u0432 jobs.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">class MAConPortsUpdate(Job):      class Meta:         name = \"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445\"      site = ObjectVar(         model=Site,         label='\u0411\u042e',         required=False     )      async def bulk_snmp(self, device, oid_list, community):         oid_results = {}         try:             async with aiosnmp.Snmp(                 host=device,                 port=161,                 community=community,                 timeout=5,                 retries=3,                 max_repetitions=10,             ) as snmp:                 oid_bulk_result = {}                 for oid in oid_list:                     reply = await snmp.bulk_walk(oid)                     for index in reply:                         oid_bulk_result[index.oid] = index.value                     oid_results[oid] = oid_bulk_result                  return (device, oid_results)          except Exception as error:             return (device, error)         return (device, None)      async def bulk_snmp_with_semaphore(self, semaphore, function, *args, **kwargs):         async with semaphore:             return await function(*args, **kwargs)      async def async_bulk_snmp(self, devices, oid_list, community, workers):         semaphore = asyncio.Semaphore(workers)         coroutines = [             self.bulk_snmp_with_semaphore(semaphore, self.bulk_snmp, device, oid_list, community)             for device in devices         ]         result = []         for future in asyncio.as_completed(coroutines):             result.append(await future)         return result      def run(self, data, commit):         # \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c job \u043c\u043e\u0433\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 is_superuser         if not self.request.user.is_superuser:             self.log_info(message='\u041d\u0435\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a')             return          PLUGIN_CFG = settings.PLUGINS_CONFIG['nautobot_porthistory_plugin']         COMMUNITY = PLUGIN_CFG['snmp_community']         SWITCHES_ROLE_SLUG = PLUGIN_CFG['switches_role_slug']         ROUTERS_ROLE_SLUG = PLUGIN_CFG['routers_role_slug']         WORKERS = PLUGIN_CFG['workers']         STATUS_ACTIVE = Status.objects.get(slug='active')         STATUS_STATIC = Status.objects.get(slug='static')         STATUS_DHCP = Status.objects.get(slug='dhcp')          device_role = DeviceRole.objects.filter(slug__in=SWITCHES_ROLE_SLUG)          devices = defaultdict(dict)         devices_list = []         vlans = defaultdict(list)          # \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u0441\u0435\u0445 \u0441\u0432\u044f\u0437\u0435\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0442\u043e\u043c \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0437 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043b\u0438\u043d\u043a\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u0432\u0438\u0447\u0430\u043c\u0438         cable_set = defaultdict(set)         all_cables = Cable.objects.all()         for cable in all_cables:             if cable.termination_a_type == ContentType.objects.get(app_label='dcim', model='interface'):                 if not data['site'] or cable.termination_a.device.site == data['site']:                     cable_set[cable.termination_a.device.name].add(cable.termination_a.name)             if cable.termination_b_type == ContentType.objects.get(app_label='dcim', model='interface'):                 if not data['site'] or cable.termination_b.device.site == data['site']:                     cable_set[cable.termination_b.device.name].add(cable.termination_b.name)          # \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0432\u043b\u0430\u043d\u043e\u0432 \u0441 \u0440\u0430\u0437\u0431\u0438\u0432\u043a\u043e\u0439 \u043f\u043e \u0441\u0430\u0439\u0442\u0430\u043c         vlans_by_site = defaultdict(list)         if data['site']:             nb_vlans = VLAN.objects.filter(site=data['site'], status=STATUS_ACTIVE, _custom_field_data={'flag-porthistory':True})         else:             nb_vlans = VLAN.objects.filter(status=STATUS_ACTIVE, _custom_field_data={'flag-porthistory':True})         for nb_vlan in nb_vlans:             vlans_by_site[nb_vlan.site.name].append(nb_vlan.vid)          # \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432         for site in vlans_by_site:             site_id = Site.objects.get(name=site)             nb_devices_in_site = Device.objects.filter(                 site=site_id,                  device_role__in=device_role,                  status=STATUS_ACTIVE,             )             for nb_device in nb_devices_in_site:                 if (nb_device.platform and                              nb_device.platform.napalm_driver and                              nb_device.platform.napalm_driver == 'cisco_iosxe' and                              nb_device.primary_ip4):                      primary_ip = str(nb_device.primary_ip4).split('\/')[0]                     devices_list.append(primary_ip)                     device = devices[primary_ip] = {}                     device['device'] = nb_device                     device['site'] = nb_device.site                     device['interfaces'] = {}                     device['ifindexes'] = {}                     device['bridge_ports'] = {}                     device['vlans'] = vlans_by_site[site]                     for intf in Interface.objects.filter(device_id=nb_device):                         device['interfaces'][intf.name] = intf                     for vlan in vlans_by_site[site]:                         vlans[vlan].append(primary_ip)          # \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u0438 \u0438\u0445 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0441 \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e SNMP         oid_list = ['.1.3.6.1.2.1.31.1.1.1.1']         results = asyncio.run(self.async_bulk_snmp(devices_list, oid_list, COMMUNITY, WORKERS))         for device_ip, device_result in results:             if type(device_result) != dict:                 self.log_warning(obj=devices[device_ip]['device'],message=f'\u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e SNMP')                 del devices[device_ip]                 devices_list.remove(device_ip)                 continue             for oid, oid_result in device_result.items():                 for index, index_result in oid_result.items():                     ifindex = index.split('.')[-1]                     canonical_intf_name = canonical_interface_name(index_result.decode(\"utf-8\"))                     if canonical_intf_name in devices[device_ip]['interfaces']:                         devices[device_ip]['ifindexes'][ifindex] = canonical_intf_name          # \u043f\u0440\u043e\u0439\u0434\u0435\u043c\u0441\u044f \u043f\u043e \u0441\u043f\u0438\u0441\u043a\u0443 \u0432\u043b\u0430\u043d\u043e\u0432 \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 MAC \u0430\u0434\u0440\u0435\u0441\u043e\u0432 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0432\u043b\u0430\u043d\u0430         # MAC \u0430\u0434\u0440\u0435\u0441\u0430 \u0432 \u0434\u0435\u0441\u044f\u0442\u0438\u0447\u043d\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435          port_mac_relation = defaultdict(list)          for vlan, devices_dict in vlans.items():             self.log_info(message=f'\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e VLAN {vlan}')             community_with_vlan = f'{COMMUNITY}@{vlan}'             devices_list = [device for device in devices_dict if device in devices_list]              # \u043f\u043e\u043b\u0443\u0447\u0438\u043c bridge ports \u0441 \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e SNMP (\u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 VLAN)             oid_list = ['.1.3.6.1.2.1.17.1.4.1.2']             results = asyncio.run(self.async_bulk_snmp(devices_list, oid_list, community_with_vlan, WORKERS))             for device_ip, device_result in results:                 if type(device_result) != dict:                     # \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e, \u0442\u0430\u043a\u043e\u0433\u043e VLAN \u043d\u0435\u0442 \u043d\u0430 \u044d\u0442\u043e\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435                     continue                 for oid, oid_result in device_result.items():                     for index, index_result in oid_result.items():                         bridge_port = index.split('.')[-1]                         ifindex = str(index_result)                         if ifindex in devices[device_ip]['ifindexes']:                             ifname = devices[device_ip]['ifindexes'][ifindex]                             nb_interface = devices[device_ip]['interfaces'][ifname]                             devices[device_ip]['bridge_ports'][bridge_port] = nb_interface             else:                 oid_list = ['.1.3.6.1.2.1.17.4.3.1.2']                  results = asyncio.run(self.async_bulk_snmp(devices_list, oid_list, community_with_vlan, WORKERS))                 for device_ip, device_result in results:                     nb_device = devices[device_ip]['device']                     nb_vlan = VLAN.objects.get(vid=vlan, site_id=nb_device.site.id)                     if type(device_result) != dict:                         continue                     for oid, oid_result in device_result.items():                         for mac_dec, bridge_port in oid_result.items():                             if str(bridge_port) in devices[device_ip]['bridge_ports']:                                 if (devices[device_ip]['bridge_ports'][str(bridge_port)].name not in cable_set[nb_device.name]                                         and not devices[device_ip]['bridge_ports'][str(bridge_port)]._custom_field_data.get('flag-ignore-mac')):                                     # \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c MAC \u0438\u0437 \u0434\u0435\u0441\u044f\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0430 \u0432 \u0448\u0435\u0441\u0442\u043d\u0430\u0434\u0446\u0430\u0442\u0435\u0440\u0438\u0447\u043d\u044b\u0439                                     mac_hex = ''.join(['{0:x}'.format(int(i)).zfill(2) for i in mac_dec.split('.')[-6:]]).upper()                                     port_mac_relation[devices[device_ip]['bridge_ports'][str(bridge_port)].id].append({                                         'vlan': nb_vlan,                                         'mac': mac_hex,                                         })          # \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a L3 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432         routers = defaultdict(dict)         routers_list = []         device_role = DeviceRole.objects.filter(slug__in=ROUTERS_ROLE_SLUG)         for site in vlans_by_site:             site_id = Site.objects.get(name=site)             nb_devices_in_site = Device.objects.filter(                 site=site_id,                  device_role__in=device_role,                  status=STATUS_ACTIVE,             )             for nb_device in nb_devices_in_site:                 if (nb_device.platform and                              nb_device.platform.napalm_driver and                              nb_device.platform.napalm_driver == 'cisco_iosxe' and                              nb_device.primary_ip4):                      primary_ip = str(nb_device.primary_ip4).split('\/')[0]                     routers_list.append(primary_ip)                     router = routers[primary_ip] = {}                     router['site'] = nb_device.site.name                     router['device'] = nb_device          arp = defaultdict(dict)         # \u043f\u043e\u043b\u0443\u0447\u0438\u043c ARP-\u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0441 \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e SNMP         oid_list = ['.1.3.6.1.2.1.3.1.1.2']         results = asyncio.run(self.async_bulk_snmp(routers_list, oid_list, COMMUNITY, WORKERS))         for device_ip, device_result in results:             site = routers[device_ip]['site']             arp[site] = defaultdict(list)             if type(device_result) != dict:                 self.log_warning(obj=routers[device_ip]['device'],message=f'\u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e SNMP')                 continue             for oid, oid_result in device_result.items():                 for index, index_result in oid_result.items():                     snmp_address = '.'.join(index.split('.')[-4:])                     snmp_mac = ''.join([\"{0:x}\".format(int(i)).zfill(2) for i in index_result]).upper()                     arp[site][snmp_mac].append(snmp_address)          output = ''          for device in devices.values():             nb_device = device['device']             site = nb_device.site.name             output += f'device {nb_device} :'             mac_on_device = ip_on_device = name_on_device = 0             for intf in device['interfaces'].values():                 if len(port_mac_relation[intf.id]) > 0:                     MAConPorts.objects.filter(interface=intf).delete()                 for vlan_and_mac in port_mac_relation[intf.id]:                     mac_on_device += 1                     nb_prefixes = Prefix.objects.filter(vlan_id=vlan_and_mac['vlan'].id)                     addresses = arp[site].get(vlan_and_mac['mac'])                     address_with_prefix = ''                     if nb_prefixes and addresses:                         for nb_prefix in nb_prefixes:                             for address in addresses:                                 if IPv4Address(address) in IPv4Network(str(nb_prefix)):                                     prefixlen = str(nb_prefix).split('\/')[-1]                                     address_with_prefix = f'{address}\/{prefixlen}'                                     break                             else:                                 continue                             break                     if address_with_prefix:                         ip_on_device += 1                         try:                             hostname, aliaslist, ipaddrlist  = socket.gethostbyaddr(address)                             name_on_device += 1                         except:                             hostname=''                         nb_address, created = IPAddress.objects.get_or_create(                             address=address_with_prefix,                             vrf=nb_prefix.vrf,                             defaults={                                 'status': STATUS_STATIC,                                 'dns_name': hostname                             }                         )                         if created:                             self.log_success(obj=nb_address, message=f'\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d IP \u0430\u0434\u0440\u0435\u0441 {hostname}')                         elif nb_address.status != STATUS_DHCP and hostname and nb_address.dns_name != hostname:                             old_hostname = nb_address.dns_name                             nb_address.dns_name = hostname                             nb_address.save()                             self.log_success(obj=nb_address, message=f'\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e DNS name \"{old_hostname}\" -> \"{hostname}\"')                     else:                         nb_address = None                     mac, created = MAConPorts.objects.get_or_create(                         vlan=vlan_and_mac['vlan'],                         mac=vlan_and_mac['mac'],                         defaults={                             'interface': intf,                             'device': nb_device,                             'ipaddress': nb_address,                         }                     )                     if not created:                         updated = False                         if nb_address and mac.ipaddress != nb_address:                             self.log_info(obj=nb_address, message=f'\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441 MAC {mac.mac} \u043f\u043e\u043c\u0435\u043d\u044f\u043b\u043e IP {mac.ipaddress} -> {nb_address}')                             mac.ipaddress = nb_address                             updated = True                         if mac.interface != intf:                             self.log_info(obj=intf, message=f'MAC {mac.mac} \u043f\u0435\u0440\u0435\u0435\u0445\u0430\u043b \u0441 \u043f\u043e\u0440\u0442\u0430 \"{mac.interface}\"')                             mac.interface = intf                             mac.device = nb_device                             updated = True                         if updated:                             mac.save()              output += f\" MAC count - {mac_on_device}, IP count - {ip_on_device}, resolved to hostname - {name_on_device}\\n\"          return output<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p><strong>\u0412\u0430\u0436\u043d\u043e! \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u043e SNMP \u0442\u0430\u0431\u043b\u0438\u0446\u044b MAC \u0441 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u043e\u0432 Cisco \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u0441\u0442\u0440\u043e\u043a\u0435 community \u043d\u043e\u043c\u0435\u0440 VLAN, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0436\u0438\u0432\u0443\u0442 MAC \u0430\u0434\u0440\u0435\u0441\u0430.<\/strong>\u00a0<\/p>\n<p>\u0415\u0449\u0435 \u0443\u0447\u0442\u0435\u043c, \u0447\u0442\u043e \u043d\u0435 \u043f\u0440\u043e \u0432\u0441\u0435 VLAN \u043d\u0430\u0434\u043e \u0437\u043d\u0430\u0442\u044c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0441\u043b\u0438 \u0432 \u043a\u0430\u043a\u043e\u043c-\u0442\u043e VLAN \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u044b \u0431\u0435\u0441\u043f\u0440\u043e\u0432\u043e\u0434\u043d\u044b\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u044b, \u0432 \u043d\u0438\u0445 \u0431\u0435\u0441\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c MAC. \u0422\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0440\u0442\u043e\u0432. \u0417\u043d\u0430\u0447\u0438\u0442 \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 VLAN \u0438 \u043f\u043e\u0440\u0442\u043e\u0432 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u043e\u0432. \u0421\u044e\u0434\u0430 \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u043f\u043e\u0434\u043e\u0439\u0434\u0443\u0442 custom_fields, \u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0441\u043a\u0440\u0438\u043f\u0442\u0430\u0445 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0434\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e, \u0437\u043d\u0430\u0447\u0438\u0442 \u043d\u0430\u0434\u0435\u044f\u0442\u044c\u0441\u044f \u043d\u0430 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u043e\u0435 \u0447\u0443\u0434\u043e.<\/p>\n<p>\u0422\u0430\u043a \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0431\u0443\u0434\u0435\u043c \u0440\u0435\u0430\u043b\u0438\u0441\u0442\u0430\u043c\u0438, \u0434\u043e\u0431\u0430\u0432\u0438\u043c custom_fileds \u043f\u0440\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430. \u041f\u043e\u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u043c \u0432 \u044d\u0442\u043e\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b Django signals, \u043e\u043f\u044f\u0442\u044c \u0436\u0435 \u0432 \u043e\u0431\u0435\u0440\u0442\u043a\u0435 Nautobot.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0432\u044b\u0437\u043e\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0430 \u0432 __init__.py, \u0430 \u0441\u0430\u043c \u0441\u0438\u0433\u043d\u0430\u043b \u0432 signals.py.<\/p>\n<pre><code class=\"python\">__init__.py class NautobotPorthistoryPluginConfig(PluginConfig):   ............ def ready(self): super().ready()     nautobot_database_ready.connect(create_custom_fields_for_porthistory, sender=self)<\/code><\/pre>\n<pre><code class=\"python\">signals.py from nautobot.extras.choices import CustomFieldTypeChoices  def create_custom_fields_for_porthistory(sender, apps, **kwargs):     \"\"\"Create a custom field flag_porthistory for VLAN if it doesn't already exist.\"\"\"     # Use apps.get_model to look up Nautobot core models     ContentType = apps.get_model(\"contenttypes\", \"ContentType\")     CustomField = apps.get_model(\"extras\", \"CustomField\")     VLAN = apps.get_model(\"ipam\", \"VLAN\")     Interface = apps.get_model(\"dcim\", \"Interface\")      # Create custom fields     cf_for_vlan, created = CustomField.objects.update_or_create(         name=\"flag-porthistory\",         defaults={             \"label\": \"Search MACs on ports in this VLAN\",             \"type\": CustomFieldTypeChoices.TYPE_BOOLEAN,         },     )     cf_for_vlan.content_types.set([ContentType.objects.get_for_model(VLAN)])     cf_for_interface, created = CustomField.objects.update_or_create(         name=\"flag-ignore-mac\",         defaults={             \"label\": \"Ignore MACs on this port\",             \"type\": CustomFieldTypeChoices.TYPE_BOOLEAN,         },     )     cf_for_interface.content_types.set([ContentType.objects.get_for_model(Interface)])<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u043e\u0431\u0430\u0432\u044f\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0435 \u043f\u043e\u043b\u044f \u043a \u043c\u043e\u0434\u0435\u043b\u044f\u043c VLAN \u0438 Device.interface. \u041e\u0442\u043c\u0435\u0442\u0438\u0432 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f VLAN \u0433\u0430\u043b\u043e\u0447\u043a\u0443 <em>Search MACs on ports in this VLAN<\/em> \u043c\u044b \u0442\u0435\u043c \u0441\u0430\u043c\u044b\u043c \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u043c \u043d\u0430\u0448\u0435\u043c\u0443 \u0434\u0436\u043e\u0431\u0443 \u0438\u0441\u043a\u0430\u0442\u044c MAC \u0430\u0434\u0440\u0435\u0441\u0430 \u0432 \u044d\u0442\u043e\u043c VLAN.<\/p>\n<p>\u0418\u0442\u0430\u043a, \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0434\u0436\u043e\u0431 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 VLAN, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0411\u0414 \u043d\u0443\u0436\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438. \u041d\u043e \u043a\u0430\u043a \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u044d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435? \u041f\u0435\u0440\u0432\u044b\u0439 \u043f\u0443\u0442\u044c \u043d\u0430\u043c \u0443\u0436\u0435 \u0437\u043d\u0430\u043a\u043e\u043c &#8212; \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0448\u0430\u0431\u043b\u043e\u043d\u044b \u0434\u043b\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0438 IP-\u0430\u0434\u0440\u0435\u0441\u0430. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0442\u043e\u0447\u0435\u0447\u043d\u043e \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0438\u0437 \u0411\u0414 \u0438 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2c7\/66e\/a54\/2c766ea5430d8c7fabd13a1586747223.png\" alt=\"MAC \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430\" title=\"MAC \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430\" width=\"1428\" height=\"485\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2c7\/66e\/a54\/2c766ea5430d8c7fabd13a1586747223.png\"\/><figcaption>MAC \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430<\/figcaption><\/figure>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u0443\u0442\u044c &#8212; \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0432\u044b\u0432\u043e\u0434\u0430 \u0438 \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445. \u042d\u0442\u043e\u0442 \u043f\u0443\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0442\u0435\u0440\u043d\u0438\u0441\u0442, \u0447\u0435\u043c \u043f\u0435\u0440\u0432\u044b\u0439, \u043d\u043e \u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0431\u043e\u043b\u0435\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b.<\/p>\n<p>\u0412 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0443\u0447\u0430\u0441\u0442\u0432\u0443\u044e\u0442 6 \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0445. \u0427\u0438\u0441\u043b\u043e \u0438\u0445 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u0442\u044c, \u0435\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c HTML-\u0448\u0430\u0431\u043b\u043e\u043d\u044b, \u043d\u043e \u044d\u0442\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u0440\u0443\u0433\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f<\/p>\n<p>\u0418\u0442\u0430\u043a, \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443:<\/p>\n<ol>\n<li>\n<p>navigation.py &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 \u0432 \u0432\u044b\u043f\u0430\u0434\u0430\u044e\u0449\u0435\u0435 \u043c\u0435\u043d\u044e Plugins<\/p>\n<\/li>\n<li>\n<p>urls.py &#8212; \u0432\u0435\u0448\u0430\u0435\u0442 \u043d\u0430 url (\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u043f.1) \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a<\/p>\n<\/li>\n<li>\n<p>views.py &#8212; \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 (View). \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0432 \u043e\u0434\u043d\u043e \u0446\u0435\u043b\u043e\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432\u044b\u0432\u043e\u0434\u0430 (\u0442\u0430\u0431\u043b\u0438\u0446\u044b), \u0444\u043e\u0440\u043c\u0443 \u0438 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u044b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438<\/p>\n<\/li>\n<li>\n<p>tables.py &#8212; \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0438\u0437 \u0411\u0414<\/p>\n<\/li>\n<li>\n<p>forms.py &#8212; \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 HTML \u0444\u043e\u0440\u043c\u0443 \u0434\u043b\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435<\/p>\n<\/li>\n<li>\n<p>filters.py &#8212; \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u044b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"python\">navigation.py from nautobot.extras.plugins import PluginMenuItem  menu_items = (     PluginMenuItem(         link = 'plugins:nautobot_porthistory_plugin:history',  # A reverse compatible link to follow.         link_text = 'MAC and IP on switches ports',  # Text to display to user.     ), )  <\/code><\/pre>\n<pre><code class=\"python\">urls.py from django.urls import path  from nautobot_porthistory_plugin import views  urlpatterns = [     path('history\/', views.PortHistoryView.as_view(), name='history'), ]<\/code><\/pre>\n<pre><code class=\"python\">views.py from django.shortcuts import render from nautobot.core.views import generic  from nautobot_porthistory_plugin import models, tables, filters, forms  class PortHistoryView(generic.ObjectListView):     \"\"\"\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 MAC \u0438 IP \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 \u043f\u043e\u0440\u0442\u0430\u0445\"\"\"      queryset = models.MAConPorts.objects.all()     table = tables.PortHistoryTable     filterset = filters.PortHistoryFilterSet     filterset_form = forms.PortHistoryFilterForm     action_buttons = ()<\/code><\/pre>\n<pre><code class=\"python\">tables.py import django_tables2 as tables from django_tables2.utils import A from nautobot.utilities.tables import BaseTable, ToggleColumn  from nautobot_porthistory_plugin import models  class PortHistoryTable(BaseTable):     pk = ToggleColumn()     device = tables.Column(linkify=True)     interface = tables.LinkColumn(orderable=False)     vlan = tables.LinkColumn()     ipaddress = tables.Column(linkify=True, verbose_name=\"IPv4 Address\")      class Meta(BaseTable.Meta):  # pylint: disable=too-few-public-methods         \"\"\"Meta attributes.\"\"\"          model = models.MAConPorts         fields = (             'pk',             'device',             'interface',             'vlan',             'mac',             'ipaddress',             'updated',         )<\/code><\/pre>\n<pre><code class=\"python\">forms.py from django import forms  from nautobot.dcim.models import Region, Site, Device from nautobot.ipam.models import VLAN from nautobot.utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField from nautobot.extras.forms import CustomFieldFilterForm  from nautobot_porthistory_plugin.models import MAConPorts  class PortHistoryFilterForm(BootstrapMixin, forms.Form):     \"\"\"Filter form to filter searches for MAC.\"\"\"      model = MAConPorts     field_order = [\"q\", \"site\", \"device_id\", \"vlan\"]     q = forms.CharField(required=False, label=\"Search MAC\")     site = DynamicModelMultipleChoiceField(         queryset=Site.objects.all(),         to_field_name=\"slug\",         required=False,     )     device_id = DynamicModelMultipleChoiceField(         queryset=Device.objects.all(),         required=False,         label=\"Device\",         query_params={\"site\": \"$site\"},     )     vlan = DynamicModelMultipleChoiceField(         queryset=VLAN.objects.all(),         required=False,         label=\"VLAN\",         query_params={\"site\": \"$site\"},     )<\/code><\/pre>\n<pre><code class=\"python\">filters.py import django_filters from nautobot.dcim.models import Device from nautobot.utilities.filters import BaseFilterSet, MultiValueCharFilter from django.db.models import Q  from nautobot_porthistory_plugin.models import MAConPorts  class PortHistoryFilterSet(BaseFilterSet):     \"\"\"Filter for MAConPorts\"\"\"      q = django_filters.CharFilter(method=\"search\", label=\"Search MAC\")      site = MultiValueCharFilter(         method=\"filter_site\",         field_name=\"pk\",         label=\"site\",     )     device_id = MultiValueCharFilter(         method=\"filter_device_id\",         field_name=\"pk\",         label=\"Device (ID)\",     )     vlan = MultiValueCharFilter(         method=\"filter_vlan\",         field_name=\"pk\",         label=\"VLAN\",     )      class Meta:         \"\"\"Meta attributes for filter.\"\"\"          model = MAConPorts          fields = [             'vlan'         ]      def search(self, queryset, mac, value):         if not value.strip():             return queryset         mac = ''.join(ch for ch in value if ch.isalnum())         mac = ':'.join(mac[i:i+2] for i in range(0,len(mac),2))         return queryset.filter(Q(mac__icontains=mac))      def filter_site(self, queryset, name, id_list):         if not id_list:             return queryset         return queryset.filter(Q(device__site__slug__in=id_list) )      def filter_device_id(self, queryset, name, id_list):         if not id_list:             return queryset         return queryset.filter(Q(device__id__in=id_list) )      def filter_vlan(self, queryset, name, id_list):         if not id_list:             return queryset         return queryset.filter(Q(vlan__id__in=id_list) )<\/code><\/pre>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a\u0430\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043f\u043b\u0430\u0433\u0438\u043d\u0430. \u041c\u043e\u0436\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0441\u0430\u0439\u0442\u0443, \u043f\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443, \u043f\u043e VLAN. \u041c\u043e\u0436\u043d\u043e \u0438\u0441\u043a\u0430\u0442\u044c \u043f\u043e \u0447\u0430\u0441\u0442\u0438 MAC, \u043f\u0440\u0438\u0447\u0435\u043c \u0444\u043e\u0440\u043c\u0430\u0442 \u0432\u0432\u043e\u0434\u0430 \u0432 \u043f\u043e\u043b\u0435 \u043f\u043e\u0438\u0441\u043a\u0430 \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u043e \u043d\u0435 \u0432\u0430\u0436\u0435\u043d, \u0445\u043e\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u043d\u0438\u0436\u043d\u0438\u0435 \u043f\u043e\u0434\u0447\u0435\u0440\u043a\u0438\u0432\u0430\u043d\u0438\u044f, \u0445\u043e\u0442\u044c \u043a\u0430\u043f\u0441\u043e\u043c \u0447\u0435\u0440\u0435\u0437 \u043e\u0434\u043d\u0443.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2a0\/788\/e2b\/2a0788e2b25a315dc7447bd5ba1421a5.png\" width=\"1463\" height=\"617\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2a0\/788\/e2b\/2a0788e2b25a315dc7447bd5ba1421a5.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>\u041a\u0430\u043a\u0438\u0435 \u0432\u044b\u0432\u043e\u0434\u044b \u044f \u0434\u043b\u044f \u0441\u0435\u0431\u044f \u0441\u0434\u0435\u043b\u0430\u043b  <\/h2>\n<p>\u041d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u043f\u043b\u0430\u0433\u0438\u043d \u043d\u0435 \u0442\u0430\u043a \u0438 \u0441\u043b\u043e\u0436\u043d\u043e, \u043a\u0430\u043a \u043c\u043d\u0435 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0432 \u0441\u0430\u043c\u043e\u043c \u043d\u0430\u0447\u0430\u043b\u0435, \u043a\u043e\u0433\u0434\u0430 \u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u0443\u0447\u0430\u043b \u0442\u0435\u043c\u0443 Netbox \u0438 Nautobot. Nautobot\/Netbox \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0441\u043e\u0431\u043e\u0439 \u043e\u0442\u043b\u0438\u0447\u043d\u0443\u044e \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0443 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438. \u041f\u0440\u0438\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u044f \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u0443\u0441\u0438\u043b\u0438\u0439 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0441\u0442\u0438\u0447\u044c \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0432 \u0434\u0435\u043b\u0435 \u0430\u0434\u0430\u043f\u0442\u0430\u0446\u0438\u0438 Nautobot\/Netbox \u043a \u0440\u0435\u0430\u043b\u0438\u044f\u043c \u0441\u0432\u043e\u0435\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0432\u0441\u0451, \u0441\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435.<\/p>\n<p>\u041d\u0443 \u0438 \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u043e\u043c\u0443 \u044d\u0442\u0430 \u0442\u0435\u043c\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430 \u0438 \u043a\u0442\u043e \u0434\u043e\u0447\u0438\u0442\u0430\u043b \u0434\u043e \u043a\u043e\u043d\u0446\u0430, \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043a\u043e\u0434 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 &#8212; <a href=\"https:\/\/github.com\/iontzev\/nautobot-porthistory-plugin\" rel=\"noopener noreferrer nofollow\"><u>https:\/\/github.com\/iontzev\/nautobot-porthistory-plugin<\/u><\/a><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/661281\/\"> https:\/\/habr.com\/ru\/post\/661281\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0415\u0441\u043b\u0438 \u043a\u0442\u043e-\u0442\u043e \u043d\u0435 \u0432 \u043a\u0443\u0440\u0441\u0435, Nautobot &#8212; \u0444\u043e\u0440\u043a Netbox, \u0435\u0433\u043e \u043f\u0440\u043e\u0434\u0432\u0438\u0433\u0430\u0435\u0442 \u0448\u0438\u0440\u043e\u043a\u043e \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0432 \u0443\u0437\u043a\u0438\u0445 \u043a\u0440\u0443\u0433\u0430\u0445 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 NTC (Network to code). \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043f\u043e\u0440\u044b\u0432\u0448\u0438\u0441\u044c \u0432 \u043f\u0430\u043c\u044f\u0442\u0438, \u0432\u044b \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u0442\u0435 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043a\u0440\u0430\u0441\u0438\u0432\u0443\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e \u0433\u043e\u0434\u0430 \u043f\u0440\u043e \u0437\u0430\u0442\u0438\u0440\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u043e\u0432 Netbox &#8212; \u0434\u0435\u043b\u043e \u043a\u0430\u043a \u0440\u0430\u0437 \u043a\u0430\u0441\u0430\u043b\u043e\u0441\u044c Nautobot \u0438 NTC. \u041d\u043e \u0440\u0435\u0447\u044c \u043f\u043e\u0439\u0434\u0435\u0442 \u043d\u0435 \u043e \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0445 \u043f\u0440\u0438\u043b\u0438\u0447\u0438\u044f \u043e\u043f\u0435\u043d\u0441\u043e\u0440\u0441\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430, \u0430 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043b\u0435\u0433\u043a\u043e \u0438 \u043d\u0435\u043f\u0440\u0438\u043d\u0443\u0436\u0434\u0435\u043d\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u043f\u043b\u0430\u0433\u0438\u043d \u0442\u043e\u043c\u0443, \u043a\u0442\u043e \u0432\u044b\u0431\u0440\u0430\u043b \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u0438\u0441\u0442\u0438\u043d\u044b \u0438 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 Nautobot, \u0430 \u043d\u0435 Netbox. \u0425\u043e\u0442\u044f \u044f \u0443\u0432\u0435\u0440\u0435\u043d, \u0447\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043b\u0430\u0433\u0438\u043d\u0430, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043f\u043e\u0439\u0434\u0435\u0442 \u0440\u0435\u0447\u044c \u0434\u0430\u043b\u044c\u0448\u0435, \u0442\u0430\u043a \u0436\u0435 \u043b\u0435\u0433\u043a\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0438 \u0434\u043b\u044f Netbox.<\/p>\n<h2>\u041f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0438 <\/h2>\n<p>\u0412 \u043d\u0430\u0448\u0435\u0439 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u0435\u0442\u0438 \u0438\u043c\u0435\u0435\u0442\u0441\u044f ~50 \u0444\u0438\u043b\u0438\u0430\u043b\u043e\u0432, \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u044b \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u0441 \u043e\u0431\u0449\u0438\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0440\u0442\u043e\u0432 \u043e\u0442 300 \u0434\u043e 500. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u0444\u0438\u043b\u0438\u0430\u043b\u043e\u0432 \u0431\u043e\u043b\u044c\u0448\u0435, \u043d\u043e \u043d\u0430\u0447\u043d\u0435\u043c \u043c\u044b \u0438\u043c\u0435\u043d\u043d\u043e \u0441 \u044d\u0442\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0432\u043e \u0432\u0441\u0435\u0445 \u0438\u0437 \u043d\u0438\u0445 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u0435 Cisco. \u042d\u0442\u043e \u0432\u0430\u0436\u043d\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0430\u0448 \u043f\u043b\u0430\u0433\u0438\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043a\u0430 \u043e\u0434\u043d\u043e\u0432\u0435\u043d\u0434\u043e\u0440\u043d\u044b\u043c.<\/p>\n<p>\u041f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430 \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0437\u043d\u0430\u0442\u044c: <\/p>\n<ol>\n<li>\n<p>\u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0440\u0430\u0437 \u0431\u044b\u043b\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443<\/p>\n<\/li>\n<li>\n<p>\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441 \u043a\u0430\u043a\u0438\u043c MAC\/IP \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0438 \u0432 \u043a\u0430\u043a\u043e\u043c VLAN \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443<\/p>\n<\/li>\n<\/ol>\n<p>\u201c\u041f\u043e\u0441\u0442\u043e\u0439-\u043a\u0430, \u0430\u0432\u0442\u043e\u0440\u201d &#8212; \u0441\u043a\u0430\u0436\u0435\u0442\u0435 \u0432\u044b &#8212; \u201c\u0442\u0430\u043a\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u044f \u0443\u0436\u0435 \u0433\u0434\u0435-\u0442\u043e \u0432\u0438\u0434\u0435\u043b\u201d. \u0421\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0442\u043e\u0447\u043d\u043e, \u044d\u0442\u043e switchmap (\u0433\u0443\u0433\u043b\u0438\u0442\u0441\u044f \u043f\u043e \u0441\u043b\u043e\u0432\u043e\u0441\u043e\u0447\u0435\u0442\u0430\u043d\u0438\u044e cisco switchmap). \u0427\u0442\u043e \u0436, \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u043e\u0431\u0435\u0449\u0430\u043b, \u0447\u0442\u043e \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u043d\u043e\u0432\u0430\u044f \u0438\u0434\u0435\u044f \u0434\u043b\u044f \u043f\u043b\u0430\u0433\u0438\u043d\u0430. \u041d\u043e \u044d\u0442\u0430 \u0438\u0434\u0435\u044f \u043e\u0447\u0435\u043d\u044c \u0445\u043e\u0440\u043e\u0448\u043e \u043b\u043e\u0436\u0438\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Nautobot \u043a\u0430\u043a \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 SoT.<\/p>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u0445\u043e\u0442\u0435\u043b \u043e\u0442\u0434\u0430\u0442\u044c \u0434\u0430\u043d\u044c \u0443\u0432\u0430\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c switchmap \u0438 \u0443\u0432\u0435\u043a\u043e\u0432\u0435\u0447\u0438\u0442\u044c \u0438\u0445 \u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0435 \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438 \u0441\u0432\u043e\u0435\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430. \u041d\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u044e\u0449\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438 &#8212; nautobot-porthistory-plugin (Sorry guys)<\/p>\n<h2>\u0421\u0442\u0435\u043a  <\/h2>\n<p>\u041d\u0443 \u0442\u0443\u0442 \u0432\u0441\u0451 \u043f\u0440\u043e\u0441\u0442\u043e. \u0420\u0430\u0437 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c Nautobot, \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 Python \u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0434\u0443\u043b\u0438 Django. \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432, \u0432\u0441\u0435 \u0432\u043d\u0443\u0442\u0440\u0438 Nautobot. \u0417\u0430\u0431\u0438\u0440\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0431\u0443\u0434\u0435\u043c \u043f\u043e SNMP. \u041a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u044b &#8212; Cisco, \u0432\u0441\u0435 \u0437\u0430\u0432\u0435\u0434\u0435\u043d\u044b \u0432 Nautobot, \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f primary address \u0438 \u0432\u0441\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b, \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0444\u0438\u043b\u0438\u0430\u043b\u0430 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u0432 \u0438 VLAN.<\/p>\n<h2>\u0427\u0442\u043e \u043d\u0430\u0441\u0447\u0435\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438?<\/h2>\n<p>\u0423 Nautobot \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f. \u0415\u0449\u0435 \u0431\u044b, \u0432\u0435\u0434\u044c \u043e\u043d\u0430 \u043f\u043e\u0447\u0442\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0443 Netbox. \u041d\u043e \u0432\u0441\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0438 \u043e\u0442\u043b\u0438\u0447\u0438\u044f \u043e\u0442 Netbox \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u0445\u043e\u0440\u043e\u0448\u043e, \u0437\u0430 \u044d\u0442\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043f\u0430\u0441\u0438\u0431\u043e. \u0420\u0430\u0437\u0434\u0435\u043b \u043f\u0440\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u043e\u0439 &#8212; <a href=\"https:\/\/nautobot.readthedocs.io\/en\/stable\/plugins\/development\/\" rel=\"noopener noreferrer nofollow\">https:\/\/nautobot.readthedocs.io\/en\/stable\/plugins\/development\/<\/a> <\/p>\n<p>\u041d\u043e \u0432\u043e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0435\u0440\u0432\u043e\u0435 \u043f\u0440\u043e\u0447\u0442\u0435\u043d\u0438\u0435 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430 \u043d\u0435 \u0434\u0430\u043b\u043e \u043e\u0442\u0432\u0435\u0442\u044b \u043d\u0430 \u043c\u043d\u043e\u0433\u0438\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b (\u0432\u043f\u0440\u043e\u0447\u0435\u043c, \u043a\u0430\u043a \u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435). \u041c\u043d\u043e\u0433\u0438\u0435 \u0437\u043d\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0432\u044b\u0434\u0435\u0440\u0433\u0438\u0432\u0430\u0442\u044c \u0438\u0437 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0441\u0430\u043c\u043e\u0433\u043e Nautobot, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u043a\u043e\u0438\u0445 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0432\u0441\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0438 \u0431\u043e\u043b\u044c\u0448\u0435. <\/p>\n<h2>\u0418\u0442\u0430\u043a, \u043d\u0430\u0447\u043d\u0435\u043c<\/h2>\n<p>\u0412\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438. \u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0447\u0442\u043e \u043f\u043e\u043b\u0435\u0433\u0447\u0435 &#8212; \u043d\u0430\u0443\u0447\u0438\u043c \u043f\u043b\u0430\u0433\u0438\u043d \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0440\u0442\u043e\u0432 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430, \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u044d\u0442\u0443 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c.<\/p>\n<p>\u041f\u043e \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u044d\u0442\u0430\u043f\u0430 \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u043d\u0430\u0448 \u043f\u043b\u0430\u0433\u0438\u043d \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ul>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0432 \u0411\u0414 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u0445;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0441\u0442 job, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441 \u043d\u0443\u0436\u043d\u043e\u0439 \u043d\u0430\u043c \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u043d\u043e\u0441\u0442\u044c\u044e \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u0443\u044e \u0442\u0430\u0431\u043b\u0438\u0446\u0443;<\/p>\n<\/li>\n<li>\n<p>\u0438\u0437\u043c\u0435\u043d\u0438\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432\u044b\u0432\u043e\u0434\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0435 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u0432\u0438\u0434\u043d\u043e, \u043a\u0430\u043a\u0438\u0435 \u043f\u043e\u0440\u0442\u044b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0434\u043d\u0435\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043a\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u043c \u0441\u0442\u0440\u043e\u0433\u043e \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435 nautobot-plugin-porthistory \u0441\u043a\u0435\u043b\u0435\u0442 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 (\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043f\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c nautobot).<\/p>\n<blockquote>\n<p><em>Nautobot includes a command to help create the plugin directory: nautobot-server startplugin [app_name]<\/em>  <\/p>\n<\/blockquote>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 <code>nautobot-server startplugin nautobot_porthistory_plugin<\/code> \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043d\u0430\u0431\u043e\u0440 \u043f\u0430\u043f\u043e\u043a \u0438 \u0444\u0430\u0439\u043b\u043e\u0432:  <\/p>\n<pre><code class=\"bash\">. \u2514\u2500\u2500 nautobot_porthistory_plugin     \u251c\u2500\u2500 __init__.py     \u251c\u2500\u2500 migrations     \u2502   \u2514\u2500\u2500 __init__.py     \u251c\u2500\u2500 models.py     \u251c\u2500\u2500 navigation.py     \u251c\u2500\u2500 tests     \u2502   \u251c\u2500\u2500 __init__.py     \u2502   \u251c\u2500\u2500 test_models.py     \u2502   \u2514\u2500\u2500 test_views.py     \u251c\u2500\u2500 urls.py     \u2514\u2500\u2500 views.py<\/code><\/pre>\n<p>\u041d\u0443 \u0447\u0442\u043e, \u0444\u0430\u0439\u043b\u044b \u0435\u0441\u0442\u044c, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u043e\u043b\u043d\u0438\u043c \u0438\u0445 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u043c \u043a\u043e\u0434\u043e\u043c.<\/p>\n<p>\u0424\u0430\u0439\u043b \u043f\u0430\u043a\u0435\u0442\u0430\u00a0 __init__.py \u0443\u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043d\u0443\u0436\u043d\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432. \u0412\u0441\u0451, \u0447\u0442\u043e \u043d\u0430\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f, \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u043e\u043f\u0446\u0438\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b \u0434\u043b\u044f \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e.<\/p>\n<pre><code class=\"python\">__init__.py \"\"\"nautobot_porthistory_plugin Plugin Initilization.\"\"\"  from nautobot.extras.plugins import PluginConfig  class NautobotPorthistoryPluginConfig(PluginConfig):     \"\"\"Plugin configuration for the nautobot_porthistory_plugin plugin.\"\"\"      name = \"nautobot_porthistory_plugin\"  # Raw plugin name; same as the plugin's source directory.     verbose_name = \"nautobot_porthistory_plugin\"  # Human-friendly name for the plugin.     base_url = \"nautobot_porthistory_plugin\"  # (Optional) Base path to use for plugin URLs. Defaulting to app_name.     required_settings = []  # A list of any configuration parameters that must be defined by the user.     min_version = \"1.0.0\"  # Minimum version of Nautobot with which the plugin is compatible.     max_version = \"1.999\"  # Maximum version of Nautobot with which the plugin is compatible.     default_settings = {}  # A dictionary of configuration parameters and their default values.     caching_config = {}  # Plugin-specific cache configuration.      # \u0410 \u0432\u043e\u0442 \u0438 \u043d\u0443\u0436\u043d\u0430\u044f \u043d\u0430\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f     required_settings = ['switches_role_slug']     default_settings = {         'min_idle_days': 14,         'snmp_community': 'public',         'workers': 50,     }  config = NautobotPorthistoryPluginConfig<\/code><\/pre>\n<p>\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0432 \u043f\u043b\u0430\u0433\u0438\u043d:<\/p>\n<ul>\n<li>\n<p><code>switches_role_slug<\/code> &#8212; \u0420\u043e\u043b\u044c, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b<\/p>\n<\/li>\n<li>\n<p><code>min_idle_days<\/code> &#8212; \u0415\u0441\u043b\u0438 \u043f\u043e\u0440\u0442 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u0435\u043d\u044c\u0448\u0435 \u0434\u043d\u0435\u0439, \u043e\u043d \u043d\u0430\u0441 \u043d\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442<\/p>\n<\/li>\n<li>\n<p><code>workers<\/code> &#8212; \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044e<\/p>\n<\/li>\n<\/ul>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0432 models.py. \u0412 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 <em>UnusedPorts<\/em> \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f, \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e ID \u0441\u0430\u043c\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430. \u041a\u0430\u043a \u0432\u0438\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e.<\/p>\n<pre><code class=\"python\">models.py \"\"\"Model definition for nautobot_porthistory_plugin.\"\"\"  from django.db import models  from nautobot.core.models import BaseModel from nautobot.dcim.fields import MACAddressCharField  class UnusedPorts(BaseModel):     # \u0414\u0430\u0442\u0430\/\u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e output \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u0430       updated = models.DateTimeField(auto_now=True)     last_output = models.DateTimeField()     interface = models.ForeignKey(         to=\"dcim.Interface\",         on_delete=models.CASCADE,         blank=False,     )          def __str__(self):         return f'{self.interface.name} - {self.last_output}'<\/code><\/pre>\n<p>\u0410\u0440\u0433\u0443\u043c\u0435\u043d\u0442 <code>auto_now=True<\/code><em> <\/em>\u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u043f\u043e\u043b\u0435 <em>updated <\/em>\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438<\/p>\n<p>\u0424\u0430\u0439\u043b\u044b navigation.py, urls.py \u0438 views.py \u043f\u043e\u043a\u0430 \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u044b, \u043e\u0441\u0442\u0430\u0432\u0438\u043c \u0438\u0445 \u0432 \u043f\u0435\u0440\u0432\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435. \u042d\u0442\u0438 \u0442\u0440\u0438 \u0444\u0430\u0439\u043b\u0430 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u044d\u0442\u0430\u043f\u0435, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u043f\u043b\u0430\u0433\u0438\u043d \u0432 \u043c\u0435\u043d\u044e. \u041d\u043e, \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e, \u0435\u0441\u0442\u044c \u043d\u044e\u0430\u043d\u0441. \u0418\u043c\u043f\u043e\u0440\u0442\u044b \u0432 views.py \u201c\u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438\u201d \u0441\u0441\u044b\u043b\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043d\u0435\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 django, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043b\u043e\u0432\u0438\u0442\u044c \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435, \u0437\u0430\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u043c \u0441\u0442\u0440\u043e\u0447\u043a\u0443 <code>from django.views.generic import views<\/code><\/p>\n<p>\u0418\u0442\u0430\u043a, \u0443 \u043d\u0430\u0441 \u0432 \u0411\u0414 \u0435\u0441\u0442\u044c \u0442\u0430\u0431\u043b\u0438\u0446\u0430, \u043d\u043e \u0432 \u043d\u0435\u0439 \u043d\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0445. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0434\u0436\u043e\u0431 (job \u0432 Nautobot &#8212; \u043a\u0430\u043a custom script \u0438 report \u0432 Netbox), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0431\u0435\u0440\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0441 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u043e\u0432, \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0435\u0435 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442 \u0432 \u043d\u0430\u0448\u0435\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0444\u0430\u0439\u043b jobs.py \u0441 \u043a\u043b\u0430\u0441\u0441\u043e\u043c (\u0434\u0436\u043e\u0431\u043e\u043c) <em>UnusedPortsUpdate<\/em><\/p>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0435\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u043e\u0441\u0442\u0430:<\/p>\n<ol>\n<li>\n<p>\u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u043e\u0432 &#8212; \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440 \u043f\u043e d<em>evice_role.slug = switches_role_slug<\/em> \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430<\/p>\n<\/li>\n<li>\n<p>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u043f\u043e SNMP &#8212; uptime \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430<\/p>\n<\/li>\n<li>\n<p>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u043f\u043e SNMP &#8212; \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 ifindex \u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439 \u043f\u043e\u0440\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u044b \u043f\u043e SNMP &#8212; Last output \u043d\u0430 \u043f\u043e\u0440\u0442\u0430\u0445<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 Last output \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u0435\u0441\u0442\u044c \u043b\u0438 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043f\u043e \u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443. \u0415\u0441\u043b\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u0442\u0443 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0430\u0432\u043d\u043e\u0439 \u0434\u0430\u0442\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 (\u043f.2). \u0415\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0435\u0441\u0442\u044c, \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u0442\u0443 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 Last output, \u043f\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432 \u0434\u043d\u0438,\u00a0 \u043c\u0435\u043d\u044c\u0448\u0435, \u0447\u0435\u043c <em>min_idle_days<\/em> \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430, \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0437\u0430\u043f\u0438\u0441\u044c \u043e \u043f\u043e\u0440\u0442\u0435 \u0438\u0437 \u0442\u0430\u0431\u043b\u0438\u0446\u044b. \u0418\u043d\u0430\u0447\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c\/\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u043f\u0438\u0441\u044c.<\/p>\n<\/li>\n<\/ol>\n<p>\u041d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0432 \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c Site, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u0444\u0438\u043b\u0438\u0430\u043b\u0443.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 jobs.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">jobs.py  from nautobot.dcim.models import Device, DeviceRole, Site, Interface from nautobot.extras.jobs import Job, ObjectVar from nautobot.extras.models import Status from django.conf import settings  from nautobot_porthistory_plugin.models import UnusedPorts  import asyncio import aiosnmp  from collections import defaultdict from netutils.interface import canonical_interface_name from datetime import datetime, timedelta  class UnusedPortsUpdate(Job):      class Meta:         name = \"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043d\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u0445\"      site = ObjectVar(         model=Site,         label='\u0411\u042e',         required=False     )      async def bulk_snmp(self, device, oid_list, community):         oid_results = {}         try:             async with aiosnmp.Snmp(                 host=device,                 port=161,                 community=community,                 timeout=5,                 retries=3,                 max_repetitions=10,             ) as snmp:                 oid_bulk_result = {}                 for oid in oid_list:                     reply = await snmp.bulk_walk(oid)                     for index in reply:                         oid_bulk_result[index.oid] = index.value                     oid_results[oid] = oid_bulk_result                  return (device, oid_results)          except Exception as error:             return (device, error)         return (device, None)      async def bulk_snmp_with_semaphore(self, semaphore, function, *args, **kwargs):         async with semaphore:             return await function(*args, **kwargs)      async def async_bulk_snmp(self, devices, oid_list, community, workers):         semaphore = asyncio.Semaphore(workers)         coroutines = [             self.bulk_snmp_with_semaphore(semaphore, self.bulk_snmp, device, oid_list, community)           <\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-332007","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/332007","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=332007"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/332007\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=332007"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=332007"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=332007"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}